/** * Locks a read/write lock for writing. Waits until the lock is available. * Reentrant. * * @retval RWL_SUCCESS Success * @retval RWL_INVALID Lock structure is invalid. log_add() called. * @retval RWL_EXIST Lock is locked for reading and the current process is * the one that created it. log_add() called. * @retval RWL_SYSTEM System error. See "errno". log_add() called. Resulting * state of the lock is unspecified. */ srwl_Status srwl_writeLock( srwl_Lock* const lock /**< [in/out] the lock to be locked */) { srwl_Status status = vet(lock); if (RWL_SUCCESS == status) { if (0 < lock->numReadLocks) { LOG_ADD1("Lock is locked for reading; semId=%d", lock->semId); status = RWL_EXIST; } else if (0 < lock->numWriteLocks) { lock->numWriteLocks++; status = RWL_SUCCESS; } else { if (semop(lock->semId, writeLockOps, sizeof(writeLockOps) / sizeof(writeLockOps[0])) == -1) { LOG_SERROR1("Couldn't lock for writing: semId=%d", lock->semId); status = RWL_SYSTEM; } else { lock->numWriteLocks = 1; status = RWL_SUCCESS; } } } return status; }
static void closeSocket( void* const arg) { int sock = *(int*)arg; if (close(sock)) LOG_SERROR1("Couldn't close socket %d", sock); }
/** * Deletes a semaphore set. * * @retval RWL_SUCCESS Success. * @retval RWL_SYSTEM System error. See "errno". log_add() called. */ static srwl_Status deleteSemSet( const int semId /**< [in] semaphore set identifier */) { if (semctl(semId, 0, IPC_RMID) == 0) return RWL_SUCCESS; LOG_SERROR1("Couldn't delete semaphore set: semId=%d", semId); return RWL_SYSTEM; }
/** * Locks a read/write lock for reading. Waits until the lock is available. * Reentrant. * * @retval RWL_SUCCESS Success * @retval RWL_INVALID Lock structure is invalid. log_add() called. * @retval RWL_EXIST Lock is locked for writing and the current process is * the one that created it. log_add() called. * @retval RWL_SYSTEM System error. See "errno". log_add() called. Resulting * state of the lock is unspecified. */ srwl_Status srwl_readLock( srwl_Lock* const lock /**< [in/out] the lock to be locked */) { srwl_Status status = vet(lock); if (RWL_SUCCESS == status) { if (0 < lock->numWriteLocks) { LOG_ADD1("Lock is locked for writing; semId=%d", lock->semId); status = RWL_EXIST; } else if (0 < lock->numReadLocks) { lock->numReadLocks++; status = RWL_SUCCESS; } else { /* * A read-lock is obtained in two steps because the semop(2) * specification doesn't indicate that the operations array is * executed sequentially. */ if (semop(lock->semId, readLockOps, sizeof(readLockOps) / sizeof(readLockOps[0])) == -1) { LOG_SERROR1("Couldn't lock for reading: semId=%d", lock->semId); status = RWL_SYSTEM; } else if (semop(lock->semId, shareOps, sizeof(shareOps) / sizeof(shareOps[0])) == -1) { LOG_SERROR1("Couldn't share read-lock: semId=%d", lock->semId); status = RWL_SYSTEM; } else { lock->numReadLocks = 1; status = RWL_SUCCESS; } } } return status; }
/** * Unlocks a read/write lock. Must be called as many times as the lock was * locked before the lock will be truly unlocked. * * @retval RWL_SUCCESS Success * @retval RWL_INVALID Lock structure is invalid. log_add() called. * @retval RWL_SYSTEM System error. See "errno". log_add() called. Resulting * state of the lock is unspecified. */ srwl_Status srwl_unlock( srwl_Lock* const lock /**< [in/out] the lock to be unlocked */) { srwl_Status status = vet(lock); if (RWL_SUCCESS == status) { if (1 < lock->numWriteLocks) { lock->numWriteLocks--; } else if (1 == lock->numWriteLocks) { if (semop(lock->semId, writeUnlockOps, sizeof(writeUnlockOps) / sizeof(writeUnlockOps[0])) == -1) { LOG_SERROR1("Couldn't unlock write-lock: semId=%d", lock->semId); status = RWL_SYSTEM; } else { lock->numWriteLocks--; } } else if (1 < lock->numReadLocks) { lock->numReadLocks--; } else if (1 == lock->numReadLocks) { if (semop(lock->semId, readUnlockOps, sizeof(readUnlockOps) / sizeof(readUnlockOps[0])) == -1) { LOG_SERROR1("Couldn't unlock read-lock: semId=%d", lock->semId); status = RWL_SYSTEM; } else { lock->numReadLocks--; } } } return status; }
/** * Unconditionally deletes a read/write lock by IPC key. The Semaphore on * which the lock is based is deleted. * * @param key The IPC key * @retval RWL_SUCCESS Success * @retval RWL_EXIST The key has no associated read/write lock * @retval RWL_SYSTEM System error. log_add() called. */ srwl_Status srwl_deleteByKey( const key_t key) { int status = semget(key, 0, read_write); if (-1 == status) { LOG_SERROR0("Couldn't get semaphore set"); status = (ENOENT == errno) ? RWL_EXIST : RWL_SYSTEM; } else if (semctl(status, 0, IPC_RMID)) { LOG_SERROR1("Couldn't delete existing semaphore set %d", status); status = RWL_SYSTEM; } else { status = RWL_SUCCESS; } return status; }
/** * Creates a read/write lock based on creating a new semaphore set. Any previous * semaphore set will be deleted. * * @retval RWL_SUCCESS Success * @retval RWL_SYSTEM System error. See "errno". log_add() called. */ static srwl_Status createLock( const key_t key /**< [in] the key associated with the semaphore */, int* const semId /**< [out] pointer to the semaphore identifier */) { srwl_Status status; int id; (void) deleteSemSet(semget(key, 0, read_write)); log_clear(); id = semget(key, SI_NUM_SEMS, IPC_CREAT | IPC_EXCL | read_write); if (-1 == id) { LOG_SERROR0("Couldn't create semaphore set"); status = RWL_SYSTEM; } else { unsigned short semVal[SI_NUM_SEMS]; union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ } arg; semVal[SI_LOCK] = 1; semVal[SI_NUM_READERS] = 0; arg.array = semVal; if (semctl(id, 0, SETALL, arg) == -1) { LOG_SERROR1("Couldn't initialize semaphore set: semId=%d", id); (void) deleteSemSet(id); status = RWL_SYSTEM; } else { *semId = id; status = RWL_SUCCESS; } } return status; }
/* * Adds an entry to a child-map. * * @retval 0 Success * @retval 1 Usage error. \c log_start() called. * @retval 2 O/S failure. \c log_start() called. */ int cm_add_argv( ChildMap* const map, /**< [in/out] Pointer to the child-map */ const pid_t pid, /**< [in] Process ID of the child. * Must not already exist in map. */ char** const argv) /**< [in] Command-line of the child in * argument vector form. Last pointer * must be NULL. The strings are * defensively copied. */ { int status = 0; /* success */ if (NULL == map || NULL == argv) { status = 1; } else { int i; (void)strBuf_clear(map->buf); for (i = 0; NULL != argv[i]; i++) { if (0 < i) (void)strBuf_appendString(map->buf, " "); if (0 != strBuf_appendString(map->buf, argv[i])) { LOG_SERROR1( "Couldn't append to command-line buffer: \"%s\"", argv[i]); status = 2; break; } } if (0 == status) { const char* command = strBuf_toString(map->buf); status = cm_add_string(map, pid, command); } } /* argv != NULL */ return status; }
/** * Initializes a lock. * * @retval RWL_SUCCESS Success. * @retval RWL_EXIST "create" is false and the semaphore set doesn't exist. * log_add() called. * @retval RWL_SYSTEM System error. log_add() called. */ static srwl_Status initLock( const int create /**< [in] Whether to create the lock. If true, then any previous lock will be deleted. */, int key /**< [in] IPC key for the semaphore */, srwl_Lock** const lock /**< [out] address of pointer to lock */) { srwl_Status status; srwl_Lock* lck; if (!isInitialized) { struct sembuf acquireLock; struct sembuf releaseLock; acquireLock.sem_num = SI_LOCK; acquireLock.sem_op = -1; acquireLock.sem_flg = SEM_UNDO; /* release lock upon exit */ releaseLock.sem_num = SI_LOCK; releaseLock.sem_op = 1; releaseLock.sem_flg = SEM_UNDO; /* undo acquireLock undo */ writeLockOps[0] = acquireLock; writeLockOps[1].sem_num = SI_NUM_READERS; writeLockOps[1].sem_op = 0; writeLockOps[1].sem_flg = 0; readLockOps[0] = acquireLock; readLockOps[1].sem_num = SI_NUM_READERS; readLockOps[1].sem_op = 1; readLockOps[1].sem_flg = SEM_UNDO; /* decrement #readers upon exit */ shareOps[0] = releaseLock; writeUnlockOps[0] = releaseLock; readUnlockOps[0].sem_num = SI_NUM_READERS; readUnlockOps[0].sem_op = -1; readUnlockOps[0].sem_flg = SEM_UNDO; /* undo readLockOps[1] undo */ { mode_t um = umask(0); umask(um); read_write = 0666 & ~um; } isInitialized = 1; } lck = (srwl_Lock*) malloc(sizeof(srwl_Lock)); if (NULL == lck) { LOG_SERROR1("Couldn't allocate %lu bytes for lock", (unsigned long) sizeof(srwl_Lock)); status = RWL_SYSTEM; } else { int semId; status = create ? createLock(key, &semId) : getLock(key, &semId); if (RWL_SUCCESS != status) { free(lck); } else { lck->semId = semId; lck->pid = getpid(); lck->isValid = VALID_STRING; lck->numReadLocks = 0; lck->numWriteLocks = 0; *lock = lck; } } /* "lck" allocated */ return status; }
/*ARGSUSED*/ static int exec_prodput( const product* prod, int argc, char** argv, const void* xprod, size_t xlen) { pid_t pid = 0; if (NULL == execMap) { execMap = cm_new(); if (NULL == execMap) { LOG_ADD0("Couldn't create child-process map for EXEC entries"); log_log(LOG_ERR); pid = -1; } } /* child-process map not allocated */ if (0 == pid) { int waitOnChild = 0; /* default is not to wait */ if (strcmp(argv[0], "-wait") == 0) { waitOnChild = 1; /* => wait for child */ argc--; argv++; } pid = ldmfork(); if (-1 == pid) { LOG_SERROR0("Couldn't fork EXEC process"); log_log(LOG_ERR); } else { if (0 == pid) { /* * Child process. * * Detach the child process from the parents process group?? * * (void) setpgid(0,0); */ const unsigned ulogOptions = ulog_get_options(); const char* ulogIdent = getulogident(); const unsigned ulogFacility = getulogfacility(); const char* ulogPath = getulogpath(); (void)signal(SIGTERM, SIG_DFL); (void)pq_close(pq); /* * It is assumed that the standard input, output, and error * streams are correctly established and should not be * modified. */ /* * Don't let the child process get any inappropriate privileges. */ endpriv(); (void) execvp(argv[0], argv); openulog(ulogIdent, ulogOptions, ulogFacility, ulogPath); LOG_SERROR1("Couldn't execute command \"%s\"", argv[0]); log_log(LOG_ERR); exit(EXIT_FAILURE); } /* child process */ else { /* * Parent process. */ (void)cm_add_argv(execMap, pid, argv); if (!waitOnChild) { udebug(" exec %s[%d]", argv[0], pid); } else { udebug(" exec -wait %s[%d]", argv[0], pid); (void)reap(pid, 0); } } } /* child-process forked */ } /* child-process map allocated */ return -1 == pid ? -1 : 1; }
/* * Returns a new instance of an LDM proxy. Can take a while because it * establishes a connection to the LDM. * * Arguments: * host Identifier of the host on which an LDM server is * running. * instance Pointer to a pointer to the new instance. "*instance" * is set upon successful return. * Returns: * 0 Success. "*instance" is set. * LP_SYSTEM System error. "log_start()" called. * LP_TIMEDOUT Connection attempt timed-out. "log_start()" called. * LP_HOSTUNREACH Host is unreachable. "log_start()" called. * LP_RPC_ERROR RPC error. "log_start()" called. * LP_LDM_ERROR LDM error. "log_start()" called. */ LdmProxyStatus lp_new( const char* const host, LdmProxy** const instance) { LdmProxyStatus status = 0; /* success */ size_t nbytes = sizeof(LdmProxy); LdmProxy* proxy = (LdmProxy*)malloc(nbytes); if (NULL == proxy) { log_serror("Couldn't allocate %lu bytes for new LdmProxy", nbytes); status = LP_SYSTEM; } else { proxy->host = strdup(host); if (NULL == proxy->host) { LOG_SERROR1("Couldn't duplicate string \"%s\"", host); status = LP_SYSTEM; } else { CLIENT* clnt = NULL; ErrorObj* error = ldm_clnttcp_create_vers(host, LDM_PORT, 6, &clnt, NULL, NULL); if (!error) { proxy->version = 6; proxy->hiya = my_hiya_6; proxy->send = my_send_6; proxy->flush = my_flush_6; } else if (LDM_CLNT_BAD_VERSION == err_code(error)) { /* Couldn't connect due to protocol version. */ err_free(error); error = ldm_clnttcp_create_vers(host, LDM_PORT, 5, &clnt, NULL, NULL); if (!error) { proxy->version = 5; proxy->hiya = my_hiya_5; proxy->send = my_send_5; proxy->flush = my_flush_5; } } if (error) { LOG_START1("%s", err_message(error)); err_free(error); free(proxy->host); status = convertStatus(error); } else { proxy->clnt = clnt; proxy->rpcTimeout = rpcTimeout; } } /* "proxy->host" allocated */ if (LP_OK == status) { *instance = proxy; } else { free(proxy); } } /* "proxy" allocated */ return status; }
/** * Reads a NOAAPORT data stream, creates LDM data-products from the stream, and * inserts the data-products into an LDM product-queue. The NOAAPORT data * stream can take the form of multicast UDP packets from (for example) a * Novra S300 DVB-S2 receiver or the standard input stream. * * Usage: * noaaportIngester [-l <em>log</em>] [-n|-v|-x] [-q <em>queue</em>] [-u <em>n</em>] [-m <em>mcastAddr</em>] [-I <em>iface</em>] [-b <em>npages</em>]\n * * Where: * <dl> * <dt>-b <em>npages</em></dt> * <dd>Allocate \e npages pages of memory for the internal buffer.</dd> * * <dt>-I <em>iface</em></dt> * <dd>Listen for multicast packets on interface \e iface.</dd> * * <dt>-l <em>log</em></dt> * <dd>Log to file \e log. The default is to use the system logging daemon * if the current process is a daemon; otherwise, the standard error * stream is used.</dd> * * <dt>-m <em>mcastAddr</em></dt> * <dd>Use the multicast address \e mcastAddr. The default is to * read from the standard input stream.</dd> * * <dt>-n</dt> * <dd>Log messages of level NOTE and higher priority. Each data-product * will generate a log message.</dd> * * <dt>-q <em>queue</em></dt> * <dd>Use \e queue as the pathname of the LDM product-queue. The default * is to use the default LDM pathname of the product-queue.</dd> * * <dt>-u <em>n</em></dt> * <dd>If logging is to the system logging daemon, then use facility * <b>local</b><em>n</em>. The default is to use the LDM facility.</dd> * * <dt>-v</dt> * <dd>Log messages of level INFO and higher priority.</dd> * * <dt>-x</dt> * <dd>Log messages of level DEBUG and higher priority.</dd> * </dl> * * If neither -n, -v, nor -x is specified, then logging will be restricted to * levels ERROR and WARN only. * * @retval 0 if successful. * @retval 1 if an error occurred. At least one error-message will be logged. */ int main( const int argc, /**< [in] Number of arguments */ char* const argv[]) /**< [in] Arguments */ { int status = 0; /* default success */ extern int optind; extern int opterr; int ch; const char* const progName = ubasename(argv[0]); const char* interface = NULL; int logmask = LOG_UPTO(LOG_WARNING); const unsigned logOptions = LOG_CONS | LOG_PID; const char* mcastSpec = NULL; const char* prodQueuePath = NULL; size_t npages = DEFAULT_NPAGES; Fifo* fifo; int ttyFd = open("/dev/tty", O_RDONLY); int processPriority = 0; int idx; const char* logPath = (-1 == ttyFd) ? NULL /* log to system logging daemon */ : "-"; /* log to standard error stream */ (void)close(ttyFd); (void)setulogmask(logmask); status = initLogging(progName, logOptions, logFacility, logPath); opterr = 0; /* no error messages from getopt(3) */ while (0 == status && (ch = getopt(argc, argv, "b:I:l:m:np:q:r:s:t:u:vx")) != -1) { switch (ch) { extern char* optarg; extern int optopt; case 'b': { unsigned long n; if (sscanf(optarg, "%lu", &n) != 1) { LOG_SERROR1("Couldn't decode FIFO size in pages: \"%s\"", optarg); status = 1; } else { npages = n; } break; } case 'I': interface = optarg; break; case 'l': logPath = optarg; status = initLogging(progName, logOptions, logFacility, logPath); break; case 'm': mcastSpec = optarg; break; case 'n': logmask |= LOG_MASK(LOG_NOTICE); (void)setulogmask(logmask); break; case 'p': { char* cp; errno = 0; processPriority = (int)strtol(optarg, &cp, 0); if (0 != errno) { LOG_SERROR1("Couldn't decode priority \"%s\"", optarg); log_log(LOG_ERR); } else { if (processPriority < -20) processPriority = -20; else if (processPriority > 20) processPriority = 20; } break; } case 'q': prodQueuePath = optarg; break; case 'r': #ifdef RETRANS_SUPPORT retrans_xmit_enable = atoi(optarg); if(retrans_xmit_enable == 1) retrans_xmit_enable = OPTION_ENABLE; else retrans_xmit_enable = OPTION_DISABLE; #endif break; case 's': { #ifdef RETRANS_SUPPORT strcpy(sbn_channel_name, optarg); if(!strcmp(optarg,NAME_SBN_TYP_GOES)) { sbn_type = SBN_TYP_GOES; break; } if(!strcmp(optarg,NAME_SBN_TYP_NOAAPORT_OPT)) { sbn_type = SBN_TYP_NOAAPORT_OPT; break; } if(!strcmp(optarg,"NWSTG")) { sbn_type = SBN_TYP_NMC; break; } if(!strcmp(optarg,NAME_SBN_TYP_NMC)) { sbn_type = SBN_TYP_NMC; break; } if(!strcmp(optarg,NAME_SBN_TYP_NMC2)) { sbn_type = SBN_TYP_NMC2; break; } if(!strcmp(optarg,NAME_SBN_TYP_NMC3)) { sbn_type = SBN_TYP_NMC3; break; } if(!strcmp(optarg,NAME_SBN_TYP_NWWS)) { sbn_type = SBN_TYP_NWWS; break; } if(!strcmp(optarg,NAME_SBN_TYP_ADD)) { sbn_type = SBN_TYP_ADD; break; } if(!strcmp(optarg,NAME_SBN_TYP_ENC)) { sbn_type = SBN_TYP_ENC; break; } if(!strcmp(optarg,NAME_SBN_TYP_EXP)) { sbn_type = SBN_TYP_EXP; break; } if(!strcmp(optarg,NAME_SBN_TYP_GRW)) { sbn_type = SBN_TYP_GRW; break; } if(!strcmp(optarg,NAME_SBN_TYP_GRE)) { sbn_type = SBN_TYP_GRE; break; } printf("Operator input: UNKNOWN type must be\n"); printf(" %s, %s, %s, %s, %s, %s, %s, %s, %s, %s or %s \n", NAME_SBN_TYP_NMC, NAME_SBN_TYP_GOES, NAME_SBN_TYP_NOAAPORT_OPT, NAME_SBN_TYP_NMC2, NAME_SBN_TYP_NMC3, NAME_SBN_TYP_NWWS, NAME_SBN_TYP_ADD, NAME_SBN_TYP_ENC, NAME_SBN_TYP_EXP, NAME_SBN_TYP_GRW, NAME_SBN_TYP_GRE); #endif break; } case 't': #ifdef RETRANS_SUPPORT strcpy(transfer_type, optarg); if(!strcmp(transfer_type,"MHS") || !strcmp(transfer_type,"mhs")){ /** Using MHS for communication with NCF **/ }else{ uerror("No other mechanism other than MHS is currently supported\n"); status = 1; } #endif break; case 'u': { int i = atoi(optarg); if (0 > i || 7 < i) { LOG_START1("Invalid logging facility number: %d", i); status = 1; } else { static int logFacilities[] = {LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7}; logFacility = logFacilities[i]; status = initLogging(progName, logOptions, logFacility, logPath); } break; } case 'v': logmask |= LOG_MASK(LOG_INFO); (void)setulogmask(logmask); break; case 'x': logmask |= LOG_MASK(LOG_DEBUG); (void)setulogmask(logmask); break; default: optopt = ch; /*FALLTHROUGH*/ /* no break */ case '?': { uerror("Unknown option: \"%c\"", optopt); status = 1; break; } } /* option character switch */ } /* getopt() loop */ if (0 == status) { if (optind < argc) { uerror("Extraneous command-line argument: \"%s\"", argv[optind]); status = 1; } } if (0 != status) { uerror("Error decoding command-line"); usage(progName); } else { unotice("Starting Up %s", PACKAGE_VERSION); unotice("%s", COPYRIGHT_NOTICE); if ((status = fifoNew(npages, &fifo)) != 0) { LOG_ADD0("Couldn't create FIFO"); log_log(LOG_ERR); } else { LdmProductQueue* prodQueue; if ((status = lpqGet(prodQueuePath, &prodQueue)) != 0) { LOG_ADD0("Couldn't open LDM product-queue"); log_log(LOG_ERR); } else { if (NULL == mcastSpec) { if (0 == (status = spawnProductMaker(NULL, fifo, prodQueue, &productMaker, &productMakerThread))) { status = spawnFileReader(NULL, NULL, fifo, &reader, &readerThread); } } /* reading file */ else { pthread_attr_t attr; if (0 != (status = pthread_attr_init(&attr))) { LOG_ERRNUM0(status, "Couldn't initialize thread attribute"); } else { #ifndef _POSIX_THREAD_PRIORITY_SCHEDULING uwarn("Can't adjust thread priorities due to lack of " "necessary support from environment"); #else /* * In order to not miss any data, the reader thread * should preempt the product-maker thread as soon as * data is available and run as long as data is * available. */ const int SCHED_POLICY = SCHED_FIFO; struct sched_param param; param.sched_priority = sched_get_priority_max(SCHED_POLICY) - 1; (void)pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); (void)pthread_attr_setschedpolicy(&attr, SCHED_POLICY); (void)pthread_attr_setschedparam(&attr, ¶m); (void)pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); #endif #ifdef RETRANS_SUPPORT if (retrans_xmit_enable == OPTION_ENABLE){ /* Copy mcastAddress needed to obtain the cpio entries */ strcpy(mcastAddr, mcastSpec); } #endif if (0 == (status = spawnProductMaker(&attr, fifo, prodQueue, &productMaker, &productMakerThread))) { #ifdef _POSIX_THREAD_PRIORITY_SCHEDULING param.sched_priority++; (void)pthread_attr_setschedparam(&attr, ¶m); #endif status = spawnMulticastReader(&attr, mcastSpec, interface, fifo, &reader, &readerThread); } /* product-maker spawned */ } /* "attr" initialized */ } /* reading multicast packets */ if (0 != status) { log_log(LOG_ERR); status = 1; } else { pthread_t statThread; (void)gettimeofday(&startTime, NULL); reportTime = startTime; (void)pthread_create(&statThread, NULL, reportStatsWhenSignaled, NULL); set_sigactions(); (void)pthread_join(readerThread, NULL); status = readerStatus(reader); (void)pthread_cancel(statThread); (void)pthread_join(statThread, NULL); (void)fifoCloseWhenEmpty(fifo); (void)pthread_join(productMakerThread, NULL); if (0 != status) status = pmStatus(productMaker); reportStats(); readerFree(reader); #ifdef RETRANS_SUPPORT /** Release buffer allocated for retransmission **/ if(retrans_xmit_enable == OPTION_ENABLE){ freeRetransMem(); } #endif } /* "reader" spawned */ (void)lpqClose(prodQueue); } /* "prodQueue" open */ } /* "fifo" created */ } /* command line decoded */ return status; }