Index: head/usr.sbin/lpr/common_source/common.c =================================================================== --- head/usr.sbin/lpr/common_source/common.c (revision 299356) +++ head/usr.sbin/lpr/common_source/common.c (revision 299357) @@ -1,778 +1,778 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static char sccsid[] = "@(#)common.c 8.5 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lp.h" #include "lp.local.h" #include "pathnames.h" /* * Routines and data common to all the line printer functions. */ char line[BUFSIZ]; const char *progname; /* program name */ static int compar(const void *_p1, const void *_p2); /* * isdigit() takes a parameter of 'int', but expect values in the range * of unsigned char. Define a wrapper which takes a value of type 'char', * whether signed or unsigned, and ensure it ends up in the right range. */ #define isdigitch(Anychar) isdigit((u_char)(Anychar)) /* - * Getline reads a line from the control file cfp, removes tabs, converts + * get_line reads a line from the control file cfp, removes tabs, converts * new-line to null and leaves it in line. * Returns 0 at EOF or the number of characters read. */ int -getline(FILE *cfp) +get_line(FILE *cfp) { register int linel = 0; register char *lp = line; register int c; while ((c = getc(cfp)) != '\n' && (size_t)(linel+1) < sizeof(line)) { if (c == EOF) return(0); if (c == '\t') { do { *lp++ = ' '; linel++; } while ((linel & 07) != 0 && (size_t)(linel+1) < sizeof(line)); continue; } *lp++ = c; linel++; } *lp++ = '\0'; return(linel); } /* * Scan the current directory and make a list of daemon files sorted by * creation time. * Return the number of entries and a pointer to the list. */ int getq(const struct printer *pp, struct jobqueue *(*namelist[])) { register struct dirent *d; register struct jobqueue *q, **queue; size_t arraysz, entrysz, nitems; struct stat stbuf; DIR *dirp; int statres; PRIV_START if ((dirp = opendir(pp->spool_dir)) == NULL) { PRIV_END return (-1); } if (fstat(dirfd(dirp), &stbuf) < 0) goto errdone; PRIV_END /* * Estimate the array size by taking the size of the directory file * and dividing it by a multiple of the minimum size entry. */ arraysz = (stbuf.st_size / 24); if (arraysz < 16) arraysz = 16; queue = (struct jobqueue **)malloc(arraysz * sizeof(struct jobqueue *)); if (queue == NULL) goto errdone; nitems = 0; while ((d = readdir(dirp)) != NULL) { if (d->d_name[0] != 'c' || d->d_name[1] != 'f') continue; /* daemon control files only */ PRIV_START statres = stat(d->d_name, &stbuf); PRIV_END if (statres < 0) continue; /* Doesn't exist */ entrysz = sizeof(struct jobqueue) - sizeof(q->job_cfname) + strlen(d->d_name) + 1; q = (struct jobqueue *)malloc(entrysz); if (q == NULL) goto errdone; q->job_matched = 0; q->job_processed = 0; q->job_time = stbuf.st_mtime; strcpy(q->job_cfname, d->d_name); /* * Check to make sure the array has space left and * realloc the maximum size. */ if (++nitems > arraysz) { arraysz *= 2; queue = (struct jobqueue **)realloc((char *)queue, arraysz * sizeof(struct jobqueue *)); if (queue == NULL) goto errdone; } queue[nitems-1] = q; } closedir(dirp); if (nitems) qsort(queue, nitems, sizeof(struct jobqueue *), compar); *namelist = queue; return(nitems); errdone: closedir(dirp); PRIV_END return (-1); } /* * Compare modification times. */ static int compar(const void *p1, const void *p2) { const struct jobqueue *qe1, *qe2; qe1 = *(const struct jobqueue * const *)p1; qe2 = *(const struct jobqueue * const *)p2; if (qe1->job_time < qe2->job_time) return (-1); if (qe1->job_time > qe2->job_time) return (1); /* * At this point, the two files have the same last-modification time. * return a result based on filenames, so that 'cfA001some.host' will * come before 'cfA002some.host'. Since the jobid ('001') will wrap * around when it gets to '999', we also assume that '9xx' jobs are * older than '0xx' jobs. */ if ((qe1->job_cfname[3] == '9') && (qe2->job_cfname[3] == '0')) return (-1); if ((qe1->job_cfname[3] == '0') && (qe2->job_cfname[3] == '9')) return (1); return (strcmp(qe1->job_cfname, qe2->job_cfname)); } /* * A simple routine to determine the job number for a print job based on * the name of its control file. The algorithm used here may look odd, but * the main issue is that all parts of `lpd', `lpc', `lpq' & `lprm' must be * using the same algorithm, whatever that algorithm may be. If the caller * provides a non-null value for ''hostpp', then this returns a pointer to * the start of the hostname (or IP address?) as found in the filename. * * Algorithm: The standard `cf' file has the job number start in position 4, * but some implementations have that as an extra file-sequence letter, and * start the job number in position 5. The job number is usually three bytes, * but may be as many as five. Confusing matters still more, some Windows * print servers will append an IP address to the job number, instead of * the expected hostname. So, if the job number ends with a '.', then * assume the correct jobnum value is the first three digits. */ int calc_jobnum(const char *cfname, const char **hostpp) { int jnum; const char *cp, *numstr, *hoststr; numstr = cfname + 3; if (!isdigitch(*numstr)) numstr++; jnum = 0; for (cp = numstr; (cp < numstr + 5) && isdigitch(*cp); cp++) jnum = jnum * 10 + (*cp - '0'); hoststr = cp; /* * If the filename was built with an IP number instead of a hostname, * then recalculate using only the first three digits found. */ while(isdigitch(*cp)) cp++; if (*cp == '.') { jnum = 0; for (cp = numstr; (cp < numstr + 3) && isdigitch(*cp); cp++) jnum = jnum * 10 + (*cp - '0'); hoststr = cp; } if (hostpp != NULL) *hostpp = hoststr; return (jnum); } /* sleep n milliseconds */ void delay(int millisec) { struct timeval tdelay; if (millisec <= 0 || millisec > 10000) fatal((struct printer *)0, /* fatal() knows how to deal */ "unreasonable delay period (%d)", millisec); tdelay.tv_sec = millisec / 1000; tdelay.tv_usec = millisec * 1000 % 1000000; (void) select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tdelay); } char * lock_file_name(const struct printer *pp, char *buf, size_t len) { static char staticbuf[MAXPATHLEN]; if (buf == NULL) buf = staticbuf; if (len == 0) len = MAXPATHLEN; if (pp->lock_file[0] == '/') strlcpy(buf, pp->lock_file, len); else snprintf(buf, len, "%s/%s", pp->spool_dir, pp->lock_file); return buf; } char * status_file_name(const struct printer *pp, char *buf, size_t len) { static char staticbuf[MAXPATHLEN]; if (buf == NULL) buf = staticbuf; if (len == 0) len = MAXPATHLEN; if (pp->status_file[0] == '/') strlcpy(buf, pp->status_file, len); else snprintf(buf, len, "%s/%s", pp->spool_dir, pp->status_file); return buf; } /* * Routine to change operational state of a print queue. The operational * state is indicated by the access bits on the lock file for the queue. * At present, this is only called from various routines in lpc/cmds.c. * * XXX - Note that this works by changing access-bits on the * file, and you can only do that if you are the owner of * the file, or root. Thus, this won't really work for * userids in the "LPR_OPER" group, unless lpc is running * setuid to root (or maybe setuid to daemon). * Generally lpc is installed setgid to daemon, but does * not run setuid. */ int set_qstate(int action, const char *lfname) { struct stat stbuf; mode_t chgbits, newbits, oldmask; const char *failmsg, *okmsg; static const char *nomsg = "no state msg"; int chres, errsav, fd, res, statres; /* * Find what the current access-bits are. */ memset(&stbuf, 0, sizeof(stbuf)); PRIV_START statres = stat(lfname, &stbuf); errsav = errno; PRIV_END if ((statres < 0) && (errsav != ENOENT)) { printf("\tcannot stat() lock file\n"); return (SQS_STATFAIL); /* NOTREACHED */ } /* * Determine which bit(s) should change for the requested action. */ chgbits = stbuf.st_mode; newbits = LOCK_FILE_MODE; okmsg = NULL; failmsg = NULL; if (action & SQS_QCHANGED) { chgbits |= LFM_RESET_QUE; newbits |= LFM_RESET_QUE; /* The okmsg is not actually printed for this case. */ okmsg = nomsg; failmsg = "set queue-changed"; } if (action & SQS_DISABLEQ) { chgbits |= LFM_QUEUE_DIS; newbits |= LFM_QUEUE_DIS; okmsg = "queuing disabled"; failmsg = "disable queuing"; } if (action & SQS_STOPP) { chgbits |= LFM_PRINT_DIS; newbits |= LFM_PRINT_DIS; okmsg = "printing disabled"; failmsg = "disable printing"; if (action & SQS_DISABLEQ) { okmsg = "printer and queuing disabled"; failmsg = "disable queuing and printing"; } } if (action & SQS_ENABLEQ) { chgbits &= ~LFM_QUEUE_DIS; newbits &= ~LFM_QUEUE_DIS; okmsg = "queuing enabled"; failmsg = "enable queuing"; } if (action & SQS_STARTP) { chgbits &= ~LFM_PRINT_DIS; newbits &= ~LFM_PRINT_DIS; okmsg = "printing enabled"; failmsg = "enable printing"; } if (okmsg == NULL) { /* This routine was called with an invalid action. */ printf("\t\n"); return (SQS_PARMERR); /* NOTREACHED */ } res = 0; if (statres >= 0) { /* The file already exists, so change the access. */ PRIV_START chres = chmod(lfname, chgbits); errsav = errno; PRIV_END res = SQS_CHGOK; if (chres < 0) res = SQS_CHGFAIL; } else if (newbits == LOCK_FILE_MODE) { /* * The file does not exist, but the state requested is * the same as the default state when no file exists. * Thus, there is no need to create the file. */ res = SQS_SKIPCREOK; } else { /* * The file did not exist, so create it with the * appropriate access bits for the requested action. * Push a new umask around that create, to make sure * all the read/write bits are set as desired. */ oldmask = umask(S_IWOTH); PRIV_START fd = open(lfname, O_WRONLY|O_CREAT, newbits); errsav = errno; PRIV_END umask(oldmask); res = SQS_CREFAIL; if (fd >= 0) { res = SQS_CREOK; close(fd); } } switch (res) { case SQS_CHGOK: case SQS_CREOK: case SQS_SKIPCREOK: if (okmsg != nomsg) printf("\t%s\n", okmsg); break; case SQS_CREFAIL: printf("\tcannot create lock file: %s\n", strerror(errsav)); break; default: printf("\tcannot %s: %s\n", failmsg, strerror(errsav)); break; } return (res); } /* routine to get a current timestamp, optionally in a standard-fmt string */ void lpd_gettime(struct timespec *tsp, char *strp, size_t strsize) { struct timespec local_ts; struct timeval btime; char tempstr[TIMESTR_SIZE]; #ifdef STRFTIME_WRONG_z char *destp; #endif if (tsp == NULL) tsp = &local_ts; /* some platforms have a routine called clock_gettime, but the * routine does nothing but return "not implemented". */ memset(tsp, 0, sizeof(struct timespec)); if (clock_gettime(CLOCK_REALTIME, tsp)) { /* nanosec-aware rtn failed, fall back to microsec-aware rtn */ memset(tsp, 0, sizeof(struct timespec)); gettimeofday(&btime, NULL); tsp->tv_sec = btime.tv_sec; tsp->tv_nsec = btime.tv_usec * 1000; } /* caller may not need a character-ized version */ if ((strp == NULL) || (strsize < 1)) return; strftime(tempstr, TIMESTR_SIZE, LPD_TIMESTAMP_PATTERN, localtime(&tsp->tv_sec)); /* * This check is for implementations of strftime which treat %z * (timezone as [+-]hhmm ) like %Z (timezone as characters), or * completely ignore %z. This section is not needed on freebsd. * I'm not sure this is completely right, but it should work OK * for EST and EDT... */ #ifdef STRFTIME_WRONG_z destp = strrchr(tempstr, ':'); if (destp != NULL) { destp += 3; if ((*destp != '+') && (*destp != '-')) { char savday[6]; int tzmin = timezone / 60; int tzhr = tzmin / 60; if (daylight) tzhr--; strcpy(savday, destp + strlen(destp) - 4); snprintf(destp, (destp - tempstr), "%+03d%02d", (-1*tzhr), tzmin % 60); strcat(destp, savday); } } #endif if (strsize > TIMESTR_SIZE) { strsize = TIMESTR_SIZE; strp[TIMESTR_SIZE+1] = '\0'; } strlcpy(strp, tempstr, strsize); } /* routines for writing transfer-statistic records */ void trstat_init(struct printer *pp, const char *fname, int filenum) { register const char *srcp; register char *destp, *endp; /* * Figure out the job id of this file. The filename should be * 'cf', 'df', or maybe 'tf', followed by a letter (or sometimes * two), followed by the jobnum, followed by a hostname. * The jobnum is usually 3 digits, but might be as many as 5. * Note that some care has to be taken parsing this, as the * filename could be coming from a remote-host, and thus might * not look anything like what is expected... */ memset(pp->jobnum, 0, sizeof(pp->jobnum)); pp->jobnum[0] = '0'; srcp = strchr(fname, '/'); if (srcp == NULL) srcp = fname; destp = &(pp->jobnum[0]); endp = destp + 5; while (*srcp != '\0' && (*srcp < '0' || *srcp > '9')) srcp++; while (*srcp >= '0' && *srcp <= '9' && destp < endp) *(destp++) = *(srcp++); /* get the starting time in both numeric and string formats, and * save those away along with the file-number */ pp->jobdfnum = filenum; lpd_gettime(&pp->tr_start, pp->tr_timestr, (size_t)TIMESTR_SIZE); return; } void trstat_write(struct printer *pp, tr_sendrecv sendrecv, size_t bytecnt, const char *userid, const char *otherhost, const char *orighost) { #define STATLINE_SIZE 1024 double trtime; size_t remspace; int statfile; char thishost[MAXHOSTNAMELEN], statline[STATLINE_SIZE]; char *eostat; const char *lprhost, *recvdev, *recvhost, *rectype; const char *sendhost, *statfname; #define UPD_EOSTAT(xStr) do { \ eostat = strchr(xStr, '\0'); \ remspace = eostat - xStr; \ } while(0) lpd_gettime(&pp->tr_done, NULL, (size_t)0); trtime = DIFFTIME_TS(pp->tr_done, pp->tr_start); gethostname(thishost, sizeof(thishost)); lprhost = sendhost = recvhost = recvdev = NULL; switch (sendrecv) { case TR_SENDING: rectype = "send"; statfname = pp->stat_send; sendhost = thishost; recvhost = otherhost; break; case TR_RECVING: rectype = "recv"; statfname = pp->stat_recv; sendhost = otherhost; recvhost = thishost; break; case TR_PRINTING: /* * This case is for copying to a device (presumably local, * though filters using things like 'net/CAP' can confuse * this assumption...). */ rectype = "prnt"; statfname = pp->stat_send; sendhost = thishost; recvdev = _PATH_DEFDEVLP; if (pp->lp) recvdev = pp->lp; break; default: /* internal error... should we syslog/printf an error? */ return; } if (statfname == NULL) return; /* * the original-host and userid are found out by reading thru the * cf (control-file) for the job. Unfortunately, on incoming jobs * the df's (data-files) are sent before the matching cf, so the * orighost & userid are generally not-available for incoming jobs. * * (it would be nice to create a work-around for that..) */ if (orighost && (*orighost != '\0')) lprhost = orighost; else lprhost = ".na."; if (*userid == '\0') userid = NULL; /* * Format of statline. * Some of the keywords listed here are not implemented here, but * they are listed to reserve the meaning for a given keyword. * Fields are separated by a blank. The fields in statline are: * - time the transfer started * - name of the printer queue (the short-name...) * - hostname the file originally came from (the * 'lpr host'), if known, or "_na_" if not known. * - id of job from that host (generally three digits) * - file count (# of file within job) * - 4-byte field indicating the type of transfer * statistics record. "send" means it's from the * host sending a datafile, "recv" means it's from * a host as it receives a datafile. * user= - user who sent the job (if known) * secs= - seconds it took to transfer the file * bytes= - number of bytes transferred (ie, "bytecount") * bps=e - Bytes/sec (if the transfer was "big enough" * for this to be useful) * ! top= - type of printer (if the type is defined in * printcap, and if this statline is for sending * a file to that ptr) * ! qls= - queue-length at start of send/print-ing a job * ! qle= - queue-length at end of send/print-ing a job * sip= - IP address of sending host, only included when * receiving a job. * shost= - sending host (if that does != the original host) * rhost= - hostname receiving the file (ie, "destination") * rdev= - device receiving the file, when the file is being * send to a device instead of a remote host. * * Note: A single print job may be transferred multiple times. The * original 'lpr' occurs on one host, and that original host might * send to some interim host (or print server). That interim host * might turn around and send the job to yet another host (most likely * the real printer). The 'shost=' parameter is only included if the * sending host for this particular transfer is NOT the same as the * host which did the original 'lpr'. * * Many values have 'something=' tags before them, because they are * in some sense "optional", or their order may vary. "Optional" may * mean in the sense that different SITES might choose to have other * fields in the record, or that some fields are only included under * some circumstances. Programs processing these records should not * assume the order or existence of any of these keyword fields. */ snprintf(statline, STATLINE_SIZE, "%s %s %s %s %03ld %s", pp->tr_timestr, pp->printer, lprhost, pp->jobnum, pp->jobdfnum, rectype); UPD_EOSTAT(statline); if (userid != NULL) { snprintf(eostat, remspace, " user=%s", userid); UPD_EOSTAT(statline); } snprintf(eostat, remspace, " secs=%#.2f bytes=%lu", trtime, (unsigned long)bytecnt); UPD_EOSTAT(statline); /* * The bps field duplicates info from bytes and secs, so do * not bother to include it for very small files. */ if ((bytecnt > 25000) && (trtime > 1.1)) { snprintf(eostat, remspace, " bps=%#.2e", ((double)bytecnt/trtime)); UPD_EOSTAT(statline); } if (sendrecv == TR_RECVING) { if (remspace > 5+strlen(from_ip) ) { snprintf(eostat, remspace, " sip=%s", from_ip); UPD_EOSTAT(statline); } } if (0 != strcmp(lprhost, sendhost)) { if (remspace > 7+strlen(sendhost) ) { snprintf(eostat, remspace, " shost=%s", sendhost); UPD_EOSTAT(statline); } } if (recvhost) { if (remspace > 7+strlen(recvhost) ) { snprintf(eostat, remspace, " rhost=%s", recvhost); UPD_EOSTAT(statline); } } if (recvdev) { if (remspace > 6+strlen(recvdev) ) { snprintf(eostat, remspace, " rdev=%s", recvdev); UPD_EOSTAT(statline); } } if (remspace > 1) { strcpy(eostat, "\n"); } else { /* probably should back up to just before the final " x=".. */ strcpy(statline+STATLINE_SIZE-2, "\n"); } statfile = open(statfname, O_WRONLY|O_APPEND, 0664); if (statfile < 0) { /* statfile was given, but we can't open it. should we * syslog/printf this as an error? */ return; } write(statfile, statline, strlen(statline)); close(statfile); return; #undef UPD_EOSTAT } #include void fatal(const struct printer *pp, const char *msg, ...) { va_list ap; va_start(ap, msg); /* this error message is being sent to the 'from_host' */ if (from_host != local_host) (void)printf("%s: ", local_host); (void)printf("%s: ", progname); if (pp && pp->printer) (void)printf("%s: ", pp->printer); (void)vprintf(msg, ap); va_end(ap); (void)putchar('\n'); exit(1); } /* * Close all file descriptors from START on up. */ void closeallfds(int start) { int stop; if (USE_CLOSEFROM) /* The faster, modern solution */ closefrom(start); else { /* This older logic can be pretty awful on some OS's. The * getdtablesize() might return ``infinity'', and then this * will waste a lot of time closing file descriptors which * had never been open()-ed. */ stop = getdtablesize(); for (; start < stop; start++) close(start); } } Index: head/usr.sbin/lpr/common_source/displayq.c =================================================================== --- head/usr.sbin/lpr/common_source/displayq.c (revision 299356) +++ head/usr.sbin/lpr/common_source/displayq.c (revision 299357) @@ -1,636 +1,636 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static char sccsid[] = "@(#)displayq.c 8.4 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #define psignal foil_gcc_psignal #define sys_siglist foil_gcc_siglist #include #undef psignal #undef sys_siglist #include "lp.h" #include "lp.local.h" #include "pathnames.h" /* * Routines to display the state of the queue. */ #define JOBCOL 40 /* column for job # in -l format */ #define OWNCOL 7 /* start of Owner column in normal */ #define SIZCOL 62 /* start of Size column in normal */ /* * isprint() takes a parameter of 'int', but expect values in the range * of unsigned char. Define a wrapper which takes a value of type 'char', * whether signed or unsigned, and ensure it ends up in the right range. */ #define isprintch(Anychar) isprint((u_char)(Anychar)) /* * Stuff for handling job specifications */ static int col; /* column on screen */ static char current[MAXNAMLEN+1]; /* current file being printed */ static char file[MAXNAMLEN+1]; /* print file name */ static int first; /* first file in ``files'' column? */ static int garbage; /* # of garbage cf files */ static int lflag; /* long output option */ static int rank; /* order to be printed (-1=none, 0=active) */ static long totsize; /* total print job size in bytes */ static const char *head0 = "Rank Owner Job Files"; static const char *head1 = "Total Size\n"; static void alarmhandler(int _signo); static void filtered_write(char *_obuffer, int _wlen, FILE *_wstream); static void daemonwarn(const struct printer *_pp); /* * Display the current state of the queue. Format = 1 if long format. */ void displayq(struct printer *pp, int format) { register struct jobqueue *q; register int i, nitems, fd, ret; char *cp, *endp; struct jobqueue **queue; struct stat statb; FILE *fp; void (*savealrm)(int); lflag = format; totsize = 0; rank = -1; if ((cp = checkremote(pp))) { printf("Warning: %s\n", cp); free(cp); } /* * Print out local queue * Find all the control files in the spooling directory */ PRIV_START if (chdir(pp->spool_dir) < 0) fatal(pp, "cannot chdir to spooling directory: %s", strerror(errno)); PRIV_END if ((nitems = getq(pp, &queue)) < 0) fatal(pp, "cannot examine spooling area\n"); PRIV_START ret = stat(pp->lock_file, &statb); PRIV_END if (ret >= 0) { if (statb.st_mode & LFM_PRINT_DIS) { if (pp->remote) printf("%s: ", local_host); printf("Warning: %s is down: ", pp->printer); PRIV_START fd = open(pp->status_file, O_RDONLY|O_SHLOCK); PRIV_END if (fd >= 0) { while ((i = read(fd, line, sizeof(line))) > 0) (void) fwrite(line, 1, i, stdout); (void) close(fd); /* unlocks as well */ } else putchar('\n'); } if (statb.st_mode & LFM_QUEUE_DIS) { if (pp->remote) printf("%s: ", local_host); printf("Warning: %s queue is turned off\n", pp->printer); } } if (nitems) { PRIV_START fp = fopen(pp->lock_file, "r"); PRIV_END if (fp == NULL) daemonwarn(pp); else { /* get daemon pid */ cp = current; endp = cp + sizeof(current) - 1; while ((i = getc(fp)) != EOF && i != '\n') { if (cp < endp) *cp++ = i; } *cp = '\0'; i = atoi(current); if (i <= 0) { ret = -1; } else { PRIV_START ret = kill(i, 0); PRIV_END } if (ret < 0) { daemonwarn(pp); } else { /* read current file name */ cp = current; endp = cp + sizeof(current) - 1; while ((i = getc(fp)) != EOF && i != '\n') { if (cp < endp) *cp++ = i; } *cp = '\0'; /* * Print the status file. */ if (pp->remote) printf("%s: ", local_host); PRIV_START fd = open(pp->status_file, O_RDONLY|O_SHLOCK); PRIV_END if (fd >= 0) { while ((i = read(fd, line, sizeof(line))) > 0) fwrite(line, 1, i, stdout); close(fd); /* unlocks as well */ } else putchar('\n'); } (void) fclose(fp); } /* * Now, examine the control files and print out the jobs to * be done for each user. */ if (!lflag) header(); for (i = 0; i < nitems; i++) { q = queue[i]; inform(pp, q->job_cfname); free(q); } free(queue); } if (!pp->remote) { if (nitems == 0) puts("no entries"); return; } /* * Print foreign queue * Note that a file in transit may show up in either queue. */ if (nitems) putchar('\n'); (void) snprintf(line, sizeof(line), "%c%s", format ? '\4' : '\3', pp->remote_queue); cp = line; for (i = 0; i < requests && cp-line+10 < sizeof(line) - 1; i++) { cp += strlen(cp); (void) sprintf(cp, " %d", requ[i]); } for (i = 0; i < users && cp - line + 1 + strlen(user[i]) < sizeof(line) - 1; i++) { cp += strlen(cp); *cp++ = ' '; (void) strcpy(cp, user[i]); } strcat(line, "\n"); savealrm = signal(SIGALRM, alarmhandler); alarm(pp->conn_timeout); fd = getport(pp, pp->remote_host, 0); alarm(0); (void)signal(SIGALRM, savealrm); if (fd < 0) { if (from_host != local_host) printf("%s: ", local_host); printf("connection to %s is down\n", pp->remote_host); } else { i = strlen(line); if (write(fd, line, i) != i) fatal(pp, "Lost connection"); while ((i = read(fd, line, sizeof(line))) > 0) filtered_write(line, i, stdout); filtered_write(NULL, -1, stdout); (void) close(fd); } } /* * The lpq-info read from remote hosts may contain unprintable characters, * or carriage-returns instead of line-feeds. Clean those up before echoing * the lpq-info line(s) to stdout. The info may also be missing any kind of * end-of-line character. This also turns CRLF and LFCR into a plain LF. * * This routine may be called multiple times to process a single set of * information, and after a set is finished this routine must be called * one extra time with NULL specified as the buffer address. */ static void filtered_write(char *wbuffer, int wlen, FILE *wstream) { static char lastchar, savedchar; char *chkptr, *dest_end, *dest_ch, *nxtptr, *w_end; int destlen; char destbuf[BUFSIZ]; if (wbuffer == NULL) { if (savedchar != '\0') { if (savedchar == '\r') savedchar = '\n'; fputc(savedchar, wstream); lastchar = savedchar; savedchar = '\0'; } if (lastchar != '\0' && lastchar != '\n') fputc('\n', wstream); lastchar = '\0'; return; } dest_ch = &destbuf[0]; dest_end = dest_ch + sizeof(destbuf); chkptr = wbuffer; w_end = wbuffer + wlen; lastchar = '\0'; if (savedchar != '\0') { chkptr = &savedchar; nxtptr = wbuffer; } else nxtptr = chkptr + 1; while (chkptr < w_end) { if (nxtptr < w_end) { if ((*chkptr == '\r' && *nxtptr == '\n') || (*chkptr == '\n' && *nxtptr == '\r')) { *dest_ch++ = '\n'; /* want to skip past that second character */ nxtptr++; goto check_next; } } else { /* This is the last byte in the buffer given on this * call, so check if it could be the first-byte of a * significant two-byte sequence. If it is, then * don't write it out now, but save for checking in * the next call. */ savedchar = '\0'; if (*chkptr == '\r' || *chkptr == '\n') { savedchar = *chkptr; break; } } if (*chkptr == '\r') *dest_ch++ = '\n'; #if 0 /* XXX - don't translate unprintable characters (yet) */ else if (*chkptr != '\t' && *chkptr != '\n' && !isprintch(*chkptr)) *dest_ch++ = '?'; #endif else *dest_ch++ = *chkptr; check_next: chkptr = nxtptr; nxtptr = chkptr + 1; if (dest_ch >= dest_end) { destlen = dest_ch - &destbuf[0]; fwrite(destbuf, 1, destlen, wstream); lastchar = destbuf[destlen - 1]; dest_ch = &destbuf[0]; } } destlen = dest_ch - &destbuf[0]; if (destlen > 0) { fwrite(destbuf, 1, destlen, wstream); lastchar = destbuf[destlen - 1]; } } /* * Print a warning message if there is no daemon present. */ static void daemonwarn(const struct printer *pp) { if (pp->remote) printf("%s: ", local_host); puts("Warning: no daemon present"); current[0] = '\0'; } /* * Print the header for the short listing format */ void header(void) { printf("%s", head0); col = strlen(head0)+1; blankfill(SIZCOL); printf("%s", head1); } void inform(const struct printer *pp, char *cf) { int copycnt, jnum; char savedname[MAXPATHLEN+1]; FILE *cfp; /* * There's a chance the control file has gone away * in the meantime; if this is the case just keep going */ PRIV_START if ((cfp = fopen(cf, "r")) == NULL) return; PRIV_END if (rank < 0) rank = 0; if (pp->remote || garbage || strcmp(cf, current)) rank++; /* * The cf-file may include commands to print more than one datafile * from the user. For each datafile, the cf-file contains at least * one line which starts with some format-specifier ('a'-'z'), and * a second line ('N'ame) which indicates the original name the user * specified for that file. There can be multiple format-spec lines * for a single Name-line, if the user requested multiple copies of * that file. Standard lpr puts the format-spec line(s) before the * Name-line, while lprNG puts the Name-line before the format-spec * line(s). This section needs to handle the lines in either order. */ copycnt = 0; file[0] = '\0'; savedname[0] = '\0'; jnum = calc_jobnum(cf, NULL); - while (getline(cfp)) { + while (get_line(cfp)) { switch (line[0]) { case 'P': /* Was this file specified in the user's list? */ if (!inlist(line+1, cf)) { fclose(cfp); return; } if (lflag) { printf("\n%s: ", line+1); col = strlen(line+1) + 2; prank(rank); blankfill(JOBCOL); printf(" [job %s]\n", cf+3); } else { col = 0; prank(rank); blankfill(OWNCOL); printf("%-10s %-3d ", line+1, jnum); col += 16; first = 1; } continue; default: /* some format specifer and file name? */ if (line[0] < 'a' || line[0] > 'z') break; if (copycnt == 0 || strcmp(file, line+1) != 0) { strlcpy(file, line + 1, sizeof(file)); } copycnt++; /* - * deliberately 'continue' to another getline(), so + * deliberately 'continue' to another get_line(), so * all format-spec lines for this datafile are read * in and counted before calling show() */ continue; case 'N': strlcpy(savedname, line + 1, sizeof(savedname)); break; } if ((file[0] != '\0') && (savedname[0] != '\0')) { show(savedname, file, copycnt); copycnt = 0; file[0] = '\0'; savedname[0] = '\0'; } } fclose(cfp); /* check for a file which hasn't been shown yet */ if (file[0] != '\0') { if (savedname[0] == '\0') { /* a safeguard in case the N-ame line is missing */ strlcpy(savedname, file, sizeof(savedname)); } show(savedname, file, copycnt); } if (!lflag) { blankfill(SIZCOL); printf("%ld bytes\n", totsize); totsize = 0; } } int inlist(char *uname, char *cfile) { int *r, jnum; char **u; const char *cfhost; if (users == 0 && requests == 0) return(1); /* * Check to see if it's in the user list */ for (u = user; u < &user[users]; u++) if (!strcmp(*u, uname)) return(1); /* * Check the request list */ jnum = calc_jobnum(cfile, &cfhost); for (r = requ; r < &requ[requests]; r++) if (*r == jnum && !strcmp(cfhost, from_host)) return(1); return(0); } void show(const char *nfile, const char *datafile, int copies) { if (strcmp(nfile, " ") == 0) nfile = "(standard input)"; if (lflag) ldump(nfile, datafile, copies); else dump(nfile, datafile, copies); } /* * Fill the line with blanks to the specified column */ void blankfill(int tocol) { while (col++ < tocol) putchar(' '); } /* * Give the abbreviated dump of the file names */ void dump(const char *nfile, const char *datafile, int copies) { struct stat lbuf; const char etctmpl[] = ", ..."; char etc[sizeof(etctmpl)]; char *lastsep; short fill, nlen; short rem, remetc; /* * Print as many filenames as will fit * (leaving room for the 'total size' field) */ fill = first ? 0 : 2; /* fill space for ``, '' */ nlen = strlen(nfile); rem = SIZCOL - 1 - col; if (nlen + fill > rem) { if (first) { /* print the right-most part of the name */ printf("...%s ", &nfile[3+nlen-rem]); col = SIZCOL; } else if (rem > 0) { /* fit as much of the etc-string as we can */ remetc = rem; if (rem > strlen(etctmpl)) remetc = strlen(etctmpl); etc[0] = '\0'; strncat(etc, etctmpl, remetc); printf("%s", etc); col += remetc; rem -= remetc; /* room for the last segment of this filename? */ lastsep = strrchr(nfile, '/'); if ((lastsep != NULL) && (rem > strlen(lastsep))) { /* print the right-most part of this name */ printf("%s", lastsep); col += strlen(lastsep); } else { /* do not pack any more names in here */ blankfill(SIZCOL); } } } else { if (!first) printf(", "); printf("%s", nfile); col += nlen + fill; } first = 0; PRIV_START if (*datafile && !stat(datafile, &lbuf)) totsize += copies * lbuf.st_size; PRIV_END } /* * Print the long info about the file */ void ldump(const char *nfile, const char *datafile, int copies) { struct stat lbuf; putchar('\t'); if (copies > 1) printf("%-2d copies of %-19s", copies, nfile); else printf("%-32s", nfile); if (*datafile && !stat(datafile, &lbuf)) printf(" %qd bytes", (long long) lbuf.st_size); else printf(" ??? bytes"); putchar('\n'); } /* * Print the job's rank in the queue, * update col for screen management */ void prank(int n) { char rline[100]; static const char *r[] = { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; if (n == 0) { printf("active"); col += 6; return; } if ((n/10)%10 == 1) (void)snprintf(rline, sizeof(rline), "%dth", n); else (void)snprintf(rline, sizeof(rline), "%d%s", n, r[n%10]); col += strlen(rline); printf("%s", rline); } void alarmhandler(int signo __unused) { /* the signal is ignored */ /* (the '__unused' is just to avoid a compile-time warning) */ } Index: head/usr.sbin/lpr/common_source/lp.h =================================================================== --- head/usr.sbin/lpr/common_source/lp.h (revision 299356) +++ head/usr.sbin/lpr/common_source/lp.h (revision 299357) @@ -1,317 +1,317 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * From: @(#)lp.h 8.2 (Berkeley) 4/28/95 * $FreeBSD$ */ #include #include #include /* * All this information used to be in global static variables shared * mysteriously by various parts of the lpr/lpd suite. * This structure attempts to centralize all these declarations in the * hope that they can later be made more dynamic. */ enum lpd_filters { LPF_CIFPLOT, LPF_DVI, LPF_GRAPH, LPF_INPUT, LPF_DITROFF, LPF_OUTPUT, LPF_FORTRAN, LPF_TROFF, LPF_RASTER, LPF_COUNT }; /* NB: there is a table in common.c giving the mapping from capability names */ struct printer { char *printer; /* printer name */ int remote; /* true if RM points to a remote host */ int rp_matches_local; /* true if rp has same name as us */ int tof; /* true if we are at top-of-form */ /* ------------------------------------------------------ */ char *acct_file; /* AF: accounting file */ long baud_rate; /* BR: baud rate if lp is a tty */ char *filters[LPF_COUNT]; /* CF, DF, GF, IF, NF, OF, RF, TF, VF */ long conn_timeout; /* CT: TCP connection timeout */ long daemon_user; /* DU: daemon user id -- XXX belongs ???? */ char *form_feed; /* FF: form feed */ long header_last; /* HL: print header last */ char *log_file; /* LF: log file */ char *lock_file; /* LO: lock file */ char *lp; /* LP: device name or network address */ long max_copies; /* MC: maximum number of copies allowed */ long max_blocks; /* MX: maximum number of blocks to copy */ long price100; /* PC: price per 100 units of output */ long page_length; /* PL: page length */ long page_width; /* PW: page width */ long page_pwidth; /* PX: page width in pixels */ long page_plength; /* PY: page length in pixels */ long resend_copies; /* RC: resend copies to remote host */ char *restrict_grp; /* RG: restricted group */ char *remote_host; /* RM: remote machine name */ char *remote_queue; /* RP: remote printer name */ long restricted; /* RS: restricted to those with local accts */ long rw; /* RW: open LP for reading and writing */ long short_banner; /* SB: short banner */ long no_copies; /* SC: suppress multiple copies */ char *spool_dir; /* SD: spool directory */ long no_formfeed; /* SF: suppress FF on each print job */ long no_header; /* SH: suppress header page */ char *stat_recv; /* SR: statistics file, receiving jobs */ char *stat_send; /* SS: statistics file, sending jobs */ char *status_file; /* ST: status file name */ char *trailer; /* TR: trailer string send when Q empties */ char *mode_set; /* MS: mode set, a la stty */ /* variables used by trstat*() to keep statistics on file transfers */ #define JOBNUM_SIZE 8 char jobnum[JOBNUM_SIZE]; long jobdfnum; /* current datafile number within job */ struct timespec tr_start, tr_done; #define TIMESTR_SIZE 40 /* holds result from LPD_TIMESTAMP_PATTERN */ char tr_timestr[TIMESTR_SIZE]; #define DIFFTIME_TS(endTS,startTS) \ ((double)(endTS.tv_sec - startTS.tv_sec) \ + (endTS.tv_nsec - startTS.tv_nsec) * 1.0e-9) }; /* * Lists of user names and job numbers, for the benefit of the structs * defined below. We use TAILQs so that requests don't get mysteriously * reversed in process. */ struct req_user { TAILQ_ENTRY(req_user) ru_link; /* macro glue */ char ru_uname[1]; /* name of user */ }; TAILQ_HEAD(req_user_head, req_user); struct req_file { TAILQ_ENTRY(req_file) rf_link; /* macro glue */ char rf_type; /* type (lowercase cf file letter) of file */ char *rf_prettyname; /* user-visible name of file */ char rf_fname[1]; /* name of file */ }; TAILQ_HEAD(req_file_head, req_file); struct req_jobid { TAILQ_ENTRY(req_jobid) rj_link; /* macro glue */ int rj_job; /* job number */ }; TAILQ_HEAD(req_jobid_head, req_jobid); /* * Encapsulate all the information relevant to a request in the * lpr/lpd protocol. */ enum req_type { REQ_START, REQ_RECVJOB, REQ_LIST, REQ_DELETE }; struct request { enum req_type type; /* what sort of request is this? */ struct printer prtr; /* which printer is it for? */ int remote; /* did request arrive over network? */ char *logname; /* login name of requesting user */ char *authname; /* authenticated identity of requesting user */ char *prettyname; /* ``pretty'' name of requesting user */ int privileged; /* was the request from a privileged user? */ void *authinfo; /* authentication information */ int authentic; /* was the request securely authenticated? */ /* Information for queries and deletes... */ int nusers; /* length of following list... */ struct req_user_head users; /* list of users to query/delete */ int njobids; /* length of following list... */ struct req_jobid_head jobids; /* list of jobids to query/delete */ }; /* * Global definitions for the line printer system. */ extern char line[BUFSIZ]; extern const char *progname; /* program name (lpr, lpq, etc) */ /* * 'local_host' is the name of the machine that lpd (lpr, whatever) * is actually running on. * * 'from_host' will point to the 'host' variable when receiving a job * from a user on the same host, or "somewhere else" when receiving a * job from a remote host. If 'from_host != local_host', then 'from_ip' * is the character representation of the IP address of from_host (note * that string could be an IPv6 address). * * Also note that when 'from_host' is not pointing at 'local_host', the * string it is pointing at may be as long as NI_MAXHOST (which is very * likely to be much longer than MAXHOSTNAMELEN). */ extern char local_host[MAXHOSTNAMELEN]; extern const char *from_host; /* client's machine name */ extern const char *from_ip; /* client machine's IP address */ extern int requ[]; /* job number of spool entries */ extern int requests; /* # of spool requests */ extern char *user[]; /* users to process */ extern int users; /* # of users in user array */ extern char *person; /* name of person doing lprm */ extern u_char family; /* address family */ /* * Structure used for building a sorted list of control files. * The job_processed value can be used by callers of getq(), to keep * track of whatever processing they are doing. */ struct jobqueue { time_t job_time; /* last-mod time of cf-file */ int job_matched; /* used by match_jobspec() */ int job_processed; /* set to zero by getq() */ char job_cfname[MAXNAMLEN+1]; /* control file name */ }; /* lpr/lpd generates readable timestamps for logfiles, etc. Have all those * timestamps be in the same format wrt strftime(). This is ISO 8601 format, * with the addition of an easy-readable day-of-the-week field. Note that * '%T' = '%H:%M:%S', and that '%z' is not available on all platforms. */ #define LPD_TIMESTAMP_PATTERN "%Y-%m-%dT%T%z %a" /* * Codes to indicate which statistic records trstat_write should write. */ typedef enum { TR_SENDING, TR_RECVING, TR_PRINTING } tr_sendrecv; /* * Error codes for our mini printcap library. */ #define PCAPERR_TCLOOP (-3) #define PCAPERR_OSERR (-2) #define PCAPERR_NOTFOUND (-1) #define PCAPERR_SUCCESS 0 #define PCAPERR_TCOPEN 1 /* * File modes for the various status files maintained by lpd. */ #define LOCK_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) #define LFM_PRINT_DIS (S_IXUSR) #define LFM_QUEUE_DIS (S_IXGRP) #define LFM_RESET_QUE (S_IXOTH) #define STAT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) #define LOG_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) #define TEMP_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) /* * Bit-flags for set_qstate() actions, followed by the return values. */ #define SQS_DISABLEQ 0x01 /* Disable the queuing of new jobs */ #define SQS_STOPP 0x02 /* Stop the printing of jobs */ #define SQS_ENABLEQ 0x10 /* Enable the queuing of new jobs */ #define SQS_STARTP 0x20 /* Start the printing of jobs */ #define SQS_QCHANGED 0x80 /* The queue has changed (new jobs, etc) */ #define SQS_PARMERR -9 /* Invalid parameters from caller */ #define SQS_CREFAIL -3 /* File did not exist, and create failed */ #define SQS_CHGFAIL -2 /* File exists, but unable to change state */ #define SQS_STATFAIL -1 /* Unable to stat() the lock file */ #define SQS_CHGOK 1 /* File existed, and the state was changed */ #define SQS_CREOK 2 /* File did not exist, but was created OK */ #define SQS_SKIPCREOK 3 /* File did not exist, and there was */ /* no need to create it */ /* * Command codes used in the protocol. */ #define CMD_CHECK_QUE '\1' #define CMD_TAKE_THIS '\2' #define CMD_SHOWQ_SHORT '\3' #define CMD_SHOWQ_LONG '\4' #define CMD_RMJOB '\5' /* * seteuid() macros. */ extern uid_t uid, euid; #define PRIV_START { \ if (seteuid(euid) != 0) err(1, "seteuid failed"); \ } #define PRIV_END { \ if (seteuid(uid) != 0) err(1, "seteuid failed"); \ } #include "lp.cdefs.h" /* A cross-platform version of */ __BEGIN_DECLS struct dirent; void blankfill(int _tocol); int calc_jobnum(const char *_cfname, const char **_hostpp); char *checkremote(struct printer *_pp); int chk(char *_file); void closeallfds(int _start); void delay(int _millisec); void displayq(struct printer *_pp, int _format); void dump(const char *_nfile, const char *_datafile, int _copies); void fatal(const struct printer *_pp, const char *_msg, ...) __printflike(2, 3); int firstprinter(struct printer *_pp, int *_error); void free_printer(struct printer *_pp); void free_request(struct request *_rp); -int getline(FILE *_cfp); +int get_line(FILE *_cfp); int getport(const struct printer *_pp, const char *_rhost, int _rport); int getprintcap(const char *_printer, struct printer *_pp); int getq(const struct printer *_pp, struct jobqueue *(*_namelist[])); void header(void); void inform(const struct printer *_pp, char *_cf); void init_printer(struct printer *_pp); void init_request(struct request *_rp); int inlist(char *_uname, char *_cfile); int iscf(const struct dirent *_d); void ldump(const char *_nfile, const char *_datafile, int _copies); void lastprinter(void); int lockchk(struct printer *_pp, char *_slockf); char *lock_file_name(const struct printer *_pp, char *_buf, size_t _len); void lpd_gettime(struct timespec *_tsp, char *_strp, size_t _strsize); int nextprinter(struct printer *_pp, int *_error); const char *pcaperr(int _error); void prank(int _n); void process(const struct printer *_pp, char *_file); void rmjob(const char *_printer); void rmremote(const struct printer *_pp); void setprintcap(char *_newfile); int set_qstate(int _action, const char *_lfname); void show(const char *_nfile, const char *_datafile, int _copies); int startdaemon(const struct printer *_pp); char *status_file_name(const struct printer *_pp, char *_buf, size_t _len); void trstat_init(struct printer *_pp, const char *_fname, int _filenum); void trstat_write(struct printer *_pp, tr_sendrecv _sendrecv, size_t _bytecnt, const char *_userid, const char *_otherhost, const char *_orighost); ssize_t writel(int _strm, ...); __END_DECLS Index: head/usr.sbin/lpr/common_source/rmjob.c =================================================================== --- head/usr.sbin/lpr/common_source/rmjob.c (revision 299356) +++ head/usr.sbin/lpr/common_source/rmjob.c (revision 299357) @@ -1,392 +1,392 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static char sccsid[] = "@(#)rmjob.c 8.2 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #define psignal foil_gcc_psignal #define sys_siglist foil_gcc_siglist #include #undef psignal #undef sys_siglist #include "lp.h" #include "lp.local.h" #include "pathnames.h" /* * rmjob - remove the specified jobs from the queue. */ /* * Stuff for handling lprm specifications */ static char root[] = "root"; static int all = 0; /* eliminate all files (root only) */ static int cur_daemon; /* daemon's pid */ static char current[7+MAXHOSTNAMELEN]; /* active control file name */ static void alarmhandler(int _signo); static void do_unlink(char *_file); static int isowner(char *_owner, char *_file, const char *_cfhost); void rmjob(const char *printer) { register int i, nitems; int assassinated = 0; struct dirent **files; char *cp; struct printer myprinter, *pp = &myprinter; init_printer(pp); if ((i = getprintcap(printer, pp)) < 0) fatal(pp, "getprintcap: %s", pcaperr(i)); if ((cp = checkremote(pp))) { printf("Warning: %s\n", cp); free(cp); } /* * If the format was `lprm -' and the user isn't the super-user, * then fake things to look like he said `lprm user'. */ if (users < 0) { if (getuid() == 0) all = 1; /* all files in local queue */ else { user[0] = person; users = 1; } } if (!strcmp(person, "-all")) { if (from_host == local_host) fatal(pp, "The login name \"-all\" is reserved"); all = 1; /* all those from 'from_host' */ person = root; } PRIV_START if (chdir(pp->spool_dir) < 0) fatal(pp, "cannot chdir to spool directory"); if ((nitems = scandir(".", &files, iscf, NULL)) < 0) fatal(pp, "cannot access spool directory"); PRIV_END if (nitems) { /* * Check for an active printer daemon (in which case we * kill it if it is reading our file) then remove stuff * (after which we have to restart the daemon). */ if (lockchk(pp, pp->lock_file) && chk(current)) { PRIV_START assassinated = kill(cur_daemon, SIGINT) == 0; PRIV_END if (!assassinated) fatal(pp, "cannot kill printer daemon"); } /* * process the files */ for (i = 0; i < nitems; i++) process(pp, files[i]->d_name); } rmremote(pp); /* * Restart the printer daemon if it was killed */ if (assassinated && !startdaemon(pp)) fatal(pp, "cannot restart printer daemon\n"); exit(0); } /* * Process a lock file: collect the pid of the active * daemon and the file name of the active spool entry. * Return boolean indicating existence of a lock file. */ int lockchk(struct printer *pp, char *slockf) { register FILE *fp; register int i, n; PRIV_START if ((fp = fopen(slockf, "r")) == NULL) { if (errno == EACCES) fatal(pp, "%s: %s", slockf, strerror(errno)); else return(0); } PRIV_END - if (!getline(fp)) { + if (!get_line(fp)) { (void) fclose(fp); return(0); /* no daemon present */ } cur_daemon = atoi(line); if (kill(cur_daemon, 0) < 0 && errno != EPERM) { (void) fclose(fp); return(0); /* no daemon present */ } for (i = 1; (n = fread(current, sizeof(char), sizeof(current), fp)) <= 0; i++) { if (i > 5) { n = 1; break; } sleep(i); } current[n-1] = '\0'; (void) fclose(fp); return(1); } /* * Process a control file. */ void process(const struct printer *pp, char *file) { FILE *cfp; if (!chk(file)) return; PRIV_START if ((cfp = fopen(file, "r")) == NULL) fatal(pp, "cannot open %s", file); PRIV_END - while (getline(cfp)) { + while (get_line(cfp)) { switch (line[0]) { case 'U': /* unlink associated files */ if (strchr(line+1, '/') || strncmp(line+1, "df", 2)) break; do_unlink(line+1); } } (void) fclose(cfp); do_unlink(file); } static void do_unlink(char *file) { int ret; if (from_host != local_host) printf("%s: ", local_host); PRIV_START ret = unlink(file); PRIV_END printf(ret ? "cannot dequeue %s\n" : "%s dequeued\n", file); } /* * Do the dirty work in checking */ int chk(char *file) { int *r, jnum; char **u; const char *cfhost; FILE *cfp; /* * Check for valid cf file name (mostly checking current). */ if (strlen(file) < 7 || file[0] != 'c' || file[1] != 'f') return(0); jnum = calc_jobnum(file, &cfhost); if (all && (from_host == local_host || !strcmp(from_host, cfhost))) return(1); /* * get the owner's name from the control file. */ PRIV_START if ((cfp = fopen(file, "r")) == NULL) return(0); PRIV_END - while (getline(cfp)) { + while (get_line(cfp)) { if (line[0] == 'P') break; } (void) fclose(cfp); if (line[0] != 'P') return(0); if (users == 0 && requests == 0) return(!strcmp(file, current) && isowner(line+1, file, cfhost)); /* * Check the request list */ for (r = requ; r < &requ[requests]; r++) if (*r == jnum && isowner(line+1, file, cfhost)) return(1); /* * Check to see if it's in the user list */ for (u = user; u < &user[users]; u++) if (!strcmp(*u, line+1) && isowner(line+1, file, cfhost)) return(1); return(0); } /* * If root is removing a file on the local machine, allow it. * If root is removing a file from a remote machine, only allow * files sent from the remote machine to be removed. * Normal users can only remove the file from where it was sent. */ static int isowner(char *owner, char *file, const char *cfhost) { if (!strcmp(person, root) && (from_host == local_host || !strcmp(from_host, cfhost))) return (1); if (!strcmp(person, owner) && !strcmp(from_host, cfhost)) return (1); if (from_host != local_host) printf("%s: ", local_host); printf("%s: Permission denied\n", file); return(0); } /* * Check to see if we are sending files to a remote machine. If we are, * then try removing files on the remote machine. */ void rmremote(const struct printer *pp) { int i, elem, firstreq, niov, rem, totlen; char buf[BUFSIZ]; void (*savealrm)(int); struct iovec *iov; if (!pp->remote) return; /* not sending to a remote machine */ /* * Flush stdout so the user can see what has been deleted * while we wait (possibly) for the connection. */ fflush(stdout); /* * Counting: * 4 == "\5" + remote_queue + " " + person * 2 * users == " " + user[i] for each user * requests == asprintf results for each request * 1 == "\n" * Although laborious, doing it this way makes it possible for * us to process requests of indeterminate length without * applying an arbitrary limit. Arbitrary Limits Are Bad (tm). */ if (users > 0) niov = 4 + 2 * users + requests + 1; else niov = 4 + requests + 1; iov = malloc(niov * sizeof *iov); if (iov == NULL) fatal(pp, "out of memory in rmremote()"); iov[0].iov_base = "\5"; iov[1].iov_base = pp->remote_queue; iov[2].iov_base = " "; iov[3].iov_base = all ? "-all" : person; elem = 4; for (i = 0; i < users; i++) { iov[elem].iov_base = " "; iov[elem + 1].iov_base = user[i]; elem += 2; } firstreq = elem; for (i = 0; i < requests; i++) { asprintf((char **)&iov[elem].iov_base, " %d", requ[i]); if (iov[elem].iov_base == 0) fatal(pp, "out of memory in rmremote()"); elem++; } iov[elem++].iov_base = "\n"; for (totlen = i = 0; i < niov; i++) totlen += (iov[i].iov_len = strlen(iov[i].iov_base)); savealrm = signal(SIGALRM, alarmhandler); alarm(pp->conn_timeout); rem = getport(pp, pp->remote_host, 0); (void)signal(SIGALRM, savealrm); if (rem < 0) { if (from_host != local_host) printf("%s: ", local_host); printf("connection to %s is down\n", pp->remote_host); } else { if (writev(rem, iov, niov) != totlen) fatal(pp, "Lost connection"); while ((i = read(rem, buf, sizeof(buf))) > 0) (void) fwrite(buf, 1, i, stdout); (void) close(rem); } for (i = 0; i < requests; i++) free(iov[firstreq + i].iov_base); free(iov); } /* * Return 1 if the filename begins with 'cf' */ int iscf(const struct dirent *d) { return(d->d_name[0] == 'c' && d->d_name[1] == 'f'); } void alarmhandler(int signo __unused) { /* the signal is ignored */ /* (the '__unused' is just to avoid a compile-time warning) */ } Index: head/usr.sbin/lpr/lpc/cmds.c =================================================================== --- head/usr.sbin/lpr/lpc/cmds.c (revision 299356) +++ head/usr.sbin/lpr/lpc/cmds.c (revision 299357) @@ -1,1330 +1,1330 @@ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #if 0 #ifndef lint static char sccsid[] = "@(#)cmds.c 8.2 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include "lp.cdefs.h" /* A cross-platform version of */ __FBSDID("$FreeBSD$"); /* * lpc -- line printer control program -- commands: */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lp.h" #include "lp.local.h" #include "lpc.h" #include "extern.h" #include "pathnames.h" /* * Return values from kill_qtask(). */ #define KQT_LFERROR -2 #define KQT_KILLFAIL -1 #define KQT_NODAEMON 0 #define KQT_KILLOK 1 static char *args2line(int argc, char **argv); static int doarg(char *_job); static int doselect(const struct dirent *_d); static int kill_qtask(const char *lf); static int sortq(const struct dirent **a, const struct dirent **b); static int touch(struct jobqueue *_jq); static void unlinkf(char *_name); static void upstat(struct printer *_pp, const char *_msg, int _notify); static void wrapup_clean(int _laststatus); /* * generic framework for commands which operate on all or a specified * set of printers */ enum qsel_val { /* how a given ptr was selected */ QSEL_UNKNOWN = -1, /* ... not selected yet */ QSEL_BYNAME = 0, /* ... user specifed it by name */ QSEL_ALL = 1 /* ... user wants "all" printers */ /* (with more to come) */ }; static enum qsel_val generic_qselect; /* indicates how ptr was selected */ static int generic_initerr; /* result of initrtn processing */ static char *generic_cmdname; static char *generic_msg; /* if a -msg was specified */ static char *generic_nullarg; static void (*generic_wrapup)(int _last_status); /* perform rtn wrap-up */ void generic(void (*specificrtn)(struct printer *_pp), int cmdopts, void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[]) { int cmdstatus, more, targc; struct printer myprinter, *pp; char **margv, **targv; if (argc == 1) { /* * Usage needs a special case for 'down': The user must * either include `-msg', or only the first parameter * that they give will be processed as a printer name. */ printf("usage: %s {all | printer ...}", argv[0]); if (strcmp(argv[0], "down") == 0) { printf(" -msg [ ...]\n"); printf(" or: down {all | printer} [ ...]"); } else if (cmdopts & LPC_MSGOPT) printf(" [-msg ...]"); printf("\n"); return; } /* The first argument is the command name. */ generic_cmdname = *argv++; argc--; /* * The initialization routine for a command might set a generic * "wrapup" routine, which should be called after processing all * the printers in the command. This might print summary info. * * Note that the initialization routine may also parse (and * nullify) some of the parameters given on the command, leaving * only the parameters which have to do with printer names. */ pp = &myprinter; generic_wrapup = NULL; generic_qselect = QSEL_UNKNOWN; cmdstatus = 0; /* this just needs to be a distinct value of type 'char *' */ if (generic_nullarg == NULL) generic_nullarg = strdup(""); /* * Some commands accept a -msg argument, which indicates that * all remaining arguments should be combined into a string. */ generic_msg = NULL; if (cmdopts & LPC_MSGOPT) { targc = argc; targv = argv; for (; targc > 0; targc--, targv++) { if (strcmp(*targv, "-msg") == 0) { argc -= targc; generic_msg = args2line(targc - 1, targv + 1); break; } } if (argc < 1) { printf("error: No printer name(s) specified before" " '-msg'.\n"); printf("usage: %s {all | printer ...}", generic_cmdname); printf(" [-msg ...]\n"); return; } } /* call initialization routine, if there is one for this cmd */ if (initrtn != NULL) { generic_initerr = 0; (*initrtn)(argc, argv); if (generic_initerr) return; /* * The initrtn may have null'ed out some of the parameters. * Compact the parameter list to remove those nulls, and * correct the arg-count. */ targc = argc; targv = argv; margv = argv; argc = 0; for (; targc > 0; targc--, targv++) { if (*targv != generic_nullarg) { if (targv != margv) *margv = *targv; margv++; argc++; } } } if (argc == 1 && strcmp(*argv, "all") == 0) { generic_qselect = QSEL_ALL; more = firstprinter(pp, &cmdstatus); if (cmdstatus) goto looperr; while (more) { (*specificrtn)(pp); do { more = nextprinter(pp, &cmdstatus); looperr: switch (cmdstatus) { case PCAPERR_TCOPEN: printf("warning: %s: unresolved " "tc= reference(s) ", pp->printer); case PCAPERR_SUCCESS: break; default: fatal(pp, "%s", pcaperr(cmdstatus)); } } while (more && cmdstatus); } goto wrapup; } generic_qselect = QSEL_BYNAME; /* specifically-named ptrs */ for (; argc > 0; argc--, argv++) { init_printer(pp); cmdstatus = getprintcap(*argv, pp); switch (cmdstatus) { default: fatal(pp, "%s", pcaperr(cmdstatus)); case PCAPERR_NOTFOUND: printf("unknown printer %s\n", *argv); continue; case PCAPERR_TCOPEN: printf("warning: %s: unresolved tc= reference(s)\n", *argv); break; case PCAPERR_SUCCESS: break; } (*specificrtn)(pp); } wrapup: if (generic_wrapup) { (*generic_wrapup)(cmdstatus); } free_printer(pp); if (generic_msg) free(generic_msg); } /* * Convert an argv-array of character strings into a single string. */ static char * args2line(int argc, char **argv) { char *cp1, *cend; const char *cp2; char buf[1024]; if (argc <= 0) return strdup("\n"); cp1 = buf; cend = buf + sizeof(buf) - 1; /* save room for '\0' */ while (--argc >= 0) { cp2 = *argv++; while ((cp1 < cend) && (*cp1++ = *cp2++)) ; cp1[-1] = ' '; } cp1[-1] = '\n'; *cp1 = '\0'; return strdup(buf); } /* * Kill the current daemon, to stop printing of the active job. */ static int kill_qtask(const char *lf) { FILE *fp; pid_t pid; int errsav, killres, lockres, res; PRIV_START fp = fopen(lf, "r"); errsav = errno; PRIV_END res = KQT_NODAEMON; if (fp == NULL) { /* * If there is no lock file, then there is no daemon to * kill. Any other error return means there is some * kind of problem with the lock file. */ if (errsav != ENOENT) res = KQT_LFERROR; goto killdone; } /* If the lock file is empty, then there is no daemon to kill */ - if (getline(fp) == 0) + if (get_line(fp) == 0) goto killdone; /* * If the file can be locked without blocking, then there * no daemon to kill, or we should not try to kill it. * * XXX - not sure I understand the reasoning behind this... */ lockres = flock(fileno(fp), LOCK_SH|LOCK_NB); (void) fclose(fp); if (lockres == 0) goto killdone; pid = atoi(line); if (pid < 0) { /* * If we got a negative pid, then the contents of the * lock file is not valid. */ res = KQT_LFERROR; goto killdone; } PRIV_END killres = kill(pid, SIGTERM); errsav = errno; PRIV_END if (killres == 0) { res = KQT_KILLOK; printf("\tdaemon (pid %d) killed\n", pid); } else if (errno == ESRCH) { res = KQT_NODAEMON; } else { res = KQT_KILLFAIL; printf("\tWarning: daemon (pid %d) not killed:\n", pid); printf("\t %s\n", strerror(errsav)); } killdone: switch (res) { case KQT_LFERROR: printf("\tcannot open lock file: %s\n", strerror(errsav)); break; case KQT_NODAEMON: printf("\tno daemon to abort\n"); break; case KQT_KILLFAIL: case KQT_KILLOK: /* These two already printed messages to the user. */ break; default: printf("\t\n"); break; } return (res); } /* * Write a message into the status file. */ static void upstat(struct printer *pp, const char *msg, int notifyuser) { int fd; char statfile[MAXPATHLEN]; status_file_name(pp, statfile, sizeof statfile); umask(0); PRIV_START fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE); PRIV_END if (fd < 0) { printf("\tcannot create status file: %s\n", strerror(errno)); return; } (void) ftruncate(fd, 0); if (msg == NULL) (void) write(fd, "\n", 1); else (void) write(fd, msg, strlen(msg)); (void) close(fd); if (notifyuser) { if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0)) printf("\tstatus message is now set to nothing.\n"); else printf("\tstatus message is now: %s", msg); } } /* * kill an existing daemon and disable printing. */ void abort_q(struct printer *pp) { int killres, setres; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); /* * Turn on the owner execute bit of the lock file to disable printing. */ setres = set_qstate(SQS_STOPP, lf); /* * If set_qstate found that there already was a lock file, then * call a routine which will read that lock file and kill the * lpd-process which is listed in that lock file. If the lock * file did not exist, then either there is no daemon running * for this queue, or there is one running but *it* could not * write a lock file (which means we can not determine the * process id of that lpd-process). */ switch (setres) { case SQS_CHGOK: case SQS_CHGFAIL: /* Kill the process */ killres = kill_qtask(lf); break; case SQS_CREOK: case SQS_CREFAIL: printf("\tno daemon to abort\n"); break; case SQS_STATFAIL: printf("\tassuming no daemon to abort\n"); break; default: printf("\t\n", setres); break; } if (setres >= 0) upstat(pp, "printing disabled\n", 0); } /* * "global" variables for all the routines related to 'clean' and 'tclean' */ static time_t cln_now; /* current time */ static double cln_minage; /* minimum age before file is removed */ static long cln_sizecnt; /* amount of space freed up */ static int cln_debug; /* print extra debugging msgs */ static int cln_filecnt; /* number of files destroyed */ static int cln_foundcore; /* found a core file! */ static int cln_queuecnt; /* number of queues checked */ static int cln_testonly; /* remove-files vs just-print-info */ static int doselect(const struct dirent *d) { int c = d->d_name[0]; if ((c == 'c' || c == 'd' || c == 'r' || c == 't') && d->d_name[1] == 'f') return 1; if (c == 'c') { if (!strcmp(d->d_name, "core")) cln_foundcore = 1; } if (c == 'e') { if (!strncmp(d->d_name, "errs.", 5)) return 1; } return 0; } /* * Comparison routine that clean_q() uses for scandir. * * The purpose of this sort is to have all `df' files end up immediately * after the matching `cf' file. For files matching `cf', `df', `rf', or * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or * `tf', and then by the sequence letter (which is A-Z, or a-z). This * routine may also see filenames which do not start with `cf', `df', `rf', * or `tf' (such as `errs.*'), and those are simply sorted by the full * filename. * * XXX * This assumes that all control files start with `cfA*', and it turns * out there are a few implementations of lpr which will create `cfB*' * filenames (they will have datafile names which start with `dfB*'). */ static int sortq(const struct dirent **a, const struct dirent **b) { const int a_lt_b = -1, a_gt_b = 1, cat_other = 10; const char *fname_a, *fname_b, *jnum_a, *jnum_b; int cat_a, cat_b, ch, res, seq_a, seq_b; fname_a = (*a)->d_name; fname_b = (*b)->d_name; /* * First separate filenames into categories. Categories are * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in * that order. It is critical that the mapping be exactly the * same for 'a' vs 'b', so define a macro for the job. * * [aside: the standard `cf' file has the jobnumber start in * position 4, but some implementations have that as an extra * file-sequence letter, and start the job number in position 5.] */ #define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \ cat_X = cat_other; \ ch = *(fname_X + 2); \ jnum_X = fname_X + 3; \ seq_X = 0; \ if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \ seq_X = ch; \ if (*fname_X == 'c') \ cat_X = 1; \ else if (*fname_X == 'd') \ cat_X = 2; \ else if (*fname_X == 'r') \ cat_X = 3; \ else if (*fname_X == 't') \ cat_X = 4; \ if (cat_X != cat_other) { \ ch = *jnum_X; \ if (!isdigit(ch)) { \ if (isalpha(ch)) { \ jnum_X++; \ ch = *jnum_X; \ seq_X = (seq_X << 8) + ch; \ } \ if (!isdigit(ch)) \ cat_X = cat_other; \ } \ } \ } \ } while (0) MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a); MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b); #undef MAP_TO_CAT /* First handle all cases which have "other" files */ if ((cat_a >= cat_other) || (cat_b >= cat_other)) { /* for two "other" files, just compare the full name */ if (cat_a == cat_b) res = strcmp(fname_a, fname_b); else if (cat_a < cat_b) res = a_lt_b; else res = a_gt_b; goto have_res; } /* * At this point, we know both files are legitimate `cf', `df', `rf', * or `tf' files. Compare them by job-number and machine name. */ res = strcmp(jnum_a, jnum_b); if (res != 0) goto have_res; /* * We have two files which belong to the same job. Sort based * on the category of file (`c' before `d', etc). */ if (cat_a < cat_b) { res = a_lt_b; goto have_res; } else if (cat_a > cat_b) { res = a_gt_b; goto have_res; } /* * Two files in the same category for a single job. Sort based * on the sequence letter(s). (usually `A' through `Z', etc). */ if (seq_a < seq_b) { res = a_lt_b; goto have_res; } else if (seq_a > seq_b) { res = a_gt_b; goto have_res; } /* * Given that the filenames in a directory are unique, this SHOULD * never happen (unless there are logic errors in this routine). * But if it does happen, we must return "is equal" or the caller * might see inconsistent results in the sorting order, and that * can trigger other problems. */ printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b); printf("\t*** cat %d == %d ; seq = %d %d\n", cat_a, cat_b, seq_a, seq_b); res = 0; have_res: return res; } /* * Remove all spool files and temporaries from the spooling area. * Or, perhaps: * Remove incomplete jobs from spooling area. */ void clean_gi(int argc, char *argv[]) { /* init some fields before 'clean' is called for each queue */ cln_queuecnt = 0; cln_now = time(NULL); cln_minage = 3600.0; /* only delete files >1h old */ cln_filecnt = 0; cln_sizecnt = 0; cln_debug = 0; cln_testonly = 0; generic_wrapup = &wrapup_clean; /* see if there are any options specified before the ptr list */ for (; argc > 0; argc--, argv++) { if (**argv != '-') break; if (strcmp(*argv, "-d") == 0) { /* just an example of an option... */ cln_debug++; *argv = generic_nullarg; /* "erase" it */ } else { printf("Invalid option '%s'\n", *argv); generic_initerr = 1; } } return; } void tclean_gi(int argc, char *argv[]) { /* only difference between 'clean' and 'tclean' is one value */ /* (...and the fact that 'clean' is priv and 'tclean' is not) */ clean_gi(argc, argv); cln_testonly = 1; return; } void clean_q(struct printer *pp) { char *cp, *cp1, *lp; struct dirent **queue; size_t linerem; int didhead, i, n, nitems, rmcp; cln_queuecnt++; didhead = 0; if (generic_qselect == QSEL_BYNAME) { printf("%s:\n", pp->printer); didhead = 1; } lp = line; cp = pp->spool_dir; while (lp < &line[sizeof(line) - 1]) { if ((*lp++ = *cp++) == 0) break; } lp[-1] = '/'; linerem = sizeof(line) - (lp - line); cln_foundcore = 0; PRIV_START nitems = scandir(pp->spool_dir, &queue, doselect, sortq); PRIV_END if (nitems < 0) { if (!didhead) { printf("%s:\n", pp->printer); didhead = 1; } printf("\tcannot examine spool directory\n"); return; } if (cln_foundcore) { if (!didhead) { printf("%s:\n", pp->printer); didhead = 1; } printf("\t** found a core file in %s !\n", pp->spool_dir); } if (nitems == 0) return; if (!didhead) printf("%s:\n", pp->printer); if (cln_debug) { printf("\t** ----- Sorted list of files being checked:\n"); i = 0; do { cp = queue[i]->d_name; printf("\t** [%3d] = %s\n", i, cp); } while (++i < nitems); printf("\t** ----- end of sorted list\n"); } i = 0; do { cp = queue[i]->d_name; rmcp = 0; if (*cp == 'c') { /* * A control file. Look for matching data-files. */ /* XXX * Note the logic here assumes that the hostname * part of cf-filenames match the hostname part * in df-filenames, and that is not necessarily * true (eg: for multi-homed hosts). This needs * some further thought... */ n = 0; while (i + 1 < nitems) { cp1 = queue[i + 1]->d_name; if (*cp1 != 'd' || strcmp(cp + 3, cp1 + 3)) break; i++; n++; } if (n == 0) { rmcp = 1; } } else if (*cp == 'e') { /* * Must be an errrs or email temp file. */ rmcp = 1; } else { /* * Must be a df with no cf (otherwise, it would have * been skipped above) or an rf or tf file (which can * always be removed if it is old enough). */ rmcp = 1; } if (rmcp) { if (strlen(cp) >= linerem) { printf("\t** internal error: 'line' overflow!\n"); printf("\t** spooldir = %s\n", pp->spool_dir); printf("\t** cp = %s\n", cp); return; } strlcpy(lp, cp, linerem); unlinkf(line); } } while (++i < nitems); } static void wrapup_clean(int laststatus __unused) { printf("Checked %d queues, and ", cln_queuecnt); if (cln_filecnt < 1) { printf("no cruft was found\n"); return; } if (cln_testonly) { printf("would have "); } printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt); } static void unlinkf(char *name) { struct stat stbuf; double agemod, agestat; int res; char linkbuf[BUFSIZ]; /* * We have to use lstat() instead of stat(), in case this is a df* * "file" which is really a symlink due to 'lpr -s' processing. In * that case, we need to check the last-mod time of the symlink, and * not the file that the symlink is pointed at. */ PRIV_START res = lstat(name, &stbuf); PRIV_END if (res < 0) { printf("\terror return from stat(%s):\n", name); printf("\t %s\n", strerror(errno)); return; } agemod = difftime(cln_now, stbuf.st_mtime); agestat = difftime(cln_now, stbuf.st_ctime); if (cln_debug > 1) { /* this debugging-aid probably is not needed any more... */ printf("\t\t modify age=%g secs, stat age=%g secs\n", agemod, agestat); } if ((agemod <= cln_minage) && (agestat <= cln_minage)) return; /* * if this file is a symlink, then find out the target of the * symlink before unlink-ing the file itself */ if (S_ISLNK(stbuf.st_mode)) { PRIV_START res = readlink(name, linkbuf, sizeof(linkbuf)); PRIV_END if (res < 0) { printf("\terror return from readlink(%s):\n", name); printf("\t %s\n", strerror(errno)); return; } if (res == sizeof(linkbuf)) res--; linkbuf[res] = '\0'; } cln_filecnt++; cln_sizecnt += stbuf.st_size; if (cln_testonly) { printf("\twould remove %s\n", name); if (S_ISLNK(stbuf.st_mode)) { printf("\t (which is a symlink to %s)\n", linkbuf); } } else { PRIV_START res = unlink(name); PRIV_END if (res < 0) printf("\tcannot remove %s (!)\n", name); else printf("\tremoved %s\n", name); /* XXX * Note that for a df* file, this code should also check to see * if it is a symlink to some other file, and if the original * lpr command included '-r' ("remove file"). Of course, this * code would not be removing the df* file unless there was no * matching cf* file, and without the cf* file it is currently * impossible to determine if '-r' had been specified... * * As a result of this quandry, we may be leaving behind a * user's file that was supposed to have been removed after * being printed. This may effect services such as CAP or * samba, if they were configured to use 'lpr -r', and if * datafiles are not being properly removed. */ if (S_ISLNK(stbuf.st_mode)) { printf("\t (which was a symlink to %s)\n", linkbuf); } } } /* * Enable queuing to the printer (allow lpr to add new jobs to the queue). */ void enable_q(struct printer *pp) { int setres; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); setres = set_qstate(SQS_ENABLEQ, lf); } /* * Disable queuing. */ void disable_q(struct printer *pp) { int setres; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); setres = set_qstate(SQS_DISABLEQ, lf); } /* * Disable queuing and printing and put a message into the status file * (reason for being down). If the user specified `-msg', then use * everything after that as the message for the status file. If the * user did NOT specify `-msg', then the command should take the first * parameter as the printer name, and all remaining parameters as the * message for the status file. (This is to be compatible with the * original definition of 'down', which was implemented long before * `-msg' was around). */ void down_gi(int argc, char *argv[]) { /* If `-msg' was specified, then this routine has nothing to do. */ if (generic_msg != NULL) return; /* * If the user only gave one parameter, then use a default msg. * (if argc == 1 at this point, then *argv == name of printer). */ if (argc == 1) { generic_msg = strdup("printing disabled\n"); return; } /* * The user specified multiple parameters, and did not specify * `-msg'. Build a message from all the parameters after the * first one (and nullify those parameters so generic-processing * will not process them as printer-queue names). */ argc--; argv++; generic_msg = args2line(argc, argv); for (; argc > 0; argc--, argv++) *argv = generic_nullarg; /* "erase" it */ } void down_q(struct printer *pp) { int setres; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); setres = set_qstate(SQS_DISABLEQ+SQS_STOPP, lf); if (setres >= 0) upstat(pp, generic_msg, 1); } /* * Exit lpc */ void quit(int argc __unused, char *argv[] __unused) { exit(0); } /* * Kill and restart the daemon. */ void restart_q(struct printer *pp) { int killres, setres, startok; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); killres = kill_qtask(lf); /* * XXX - if the kill worked, we should probably sleep for * a second or so before trying to restart the queue. */ /* make sure the queue is set to print jobs */ setres = set_qstate(SQS_STARTP, lf); PRIV_START startok = startdaemon(pp); PRIV_END if (!startok) printf("\tcouldn't restart daemon\n"); else printf("\tdaemon restarted\n"); } /* * Set the status message of each queue listed. Requires a "-msg" * parameter to indicate the end of the queue list and start of msg text. */ void setstatus_gi(int argc __unused, char *argv[] __unused) { if (generic_msg == NULL) { printf("You must specify '-msg' before the text of the new status message.\n"); generic_initerr = 1; } } void setstatus_q(struct printer *pp) { struct stat stbuf; int not_shown; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); upstat(pp, generic_msg, 1); /* * Warn the user if 'lpq' will not display this new status-message. * Note that if lock file does not exist, then the queue is enabled * for both queuing and printing. */ not_shown = 1; if (stat(lf, &stbuf) >= 0) { if (stbuf.st_mode & LFM_PRINT_DIS) not_shown = 0; } if (not_shown) { printf("\tnote: This queue currently has printing enabled,\n"); printf("\t so this -msg will only be shown by 'lpq' if\n"); printf("\t a job is actively printing on it.\n"); } } /* * Enable printing on the specified printer and startup the daemon. */ void start_q(struct printer *pp) { int setres, startok; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); setres = set_qstate(SQS_STARTP, lf); PRIV_START startok = startdaemon(pp); PRIV_END if (!startok) printf("\tcouldn't start daemon\n"); else printf("\tdaemon started\n"); PRIV_END } /* * Print the status of the printer queue. */ void status(struct printer *pp) { struct stat stbuf; register int fd, i; register struct dirent *dp; DIR *dirp; char file[MAXPATHLEN]; printf("%s:\n", pp->printer); lock_file_name(pp, file, sizeof file); if (stat(file, &stbuf) >= 0) { printf("\tqueuing is %s\n", ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled" : "enabled")); printf("\tprinting is %s\n", ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled" : "enabled")); } else { printf("\tqueuing is enabled\n"); printf("\tprinting is enabled\n"); } if ((dirp = opendir(pp->spool_dir)) == NULL) { printf("\tcannot examine spool directory\n"); return; } i = 0; while ((dp = readdir(dirp)) != NULL) { if (*dp->d_name == 'c' && dp->d_name[1] == 'f') i++; } closedir(dirp); if (i == 0) printf("\tno entries in spool area\n"); else if (i == 1) printf("\t1 entry in spool area\n"); else printf("\t%d entries in spool area\n", i); fd = open(file, O_RDONLY); if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) { (void) close(fd); /* unlocks as well */ printf("\tprinter idle\n"); return; } (void) close(fd); /* print out the contents of the status file, if it exists */ status_file_name(pp, file, sizeof file); fd = open(file, O_RDONLY|O_SHLOCK); if (fd >= 0) { (void) fstat(fd, &stbuf); if (stbuf.st_size > 0) { putchar('\t'); while ((i = read(fd, line, sizeof(line))) > 0) (void) fwrite(line, 1, i, stdout); } (void) close(fd); /* unlocks as well */ } } /* * Stop the specified daemon after completing the current job and disable * printing. */ void stop_q(struct printer *pp) { int setres; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); setres = set_qstate(SQS_STOPP, lf); if (setres >= 0) upstat(pp, "printing disabled\n", 0); } struct jobqueue **queue; int nitems; time_t mtime; /* * Put the specified jobs at the top of printer queue. */ void topq(int argc, char *argv[]) { register int i; struct stat stbuf; int cmdstatus, changed; struct printer myprinter, *pp = &myprinter; if (argc < 3) { printf("usage: topq printer [jobnum ...] [user ...]\n"); return; } --argc; ++argv; init_printer(pp); cmdstatus = getprintcap(*argv, pp); switch(cmdstatus) { default: fatal(pp, "%s", pcaperr(cmdstatus)); case PCAPERR_NOTFOUND: printf("unknown printer %s\n", *argv); return; case PCAPERR_TCOPEN: printf("warning: %s: unresolved tc= reference(s)", *argv); break; case PCAPERR_SUCCESS: break; } printf("%s:\n", pp->printer); PRIV_START if (chdir(pp->spool_dir) < 0) { printf("\tcannot chdir to %s\n", pp->spool_dir); goto out; } PRIV_END nitems = getq(pp, &queue); if (nitems == 0) return; changed = 0; mtime = queue[0]->job_time; for (i = argc; --i; ) { if (doarg(argv[i]) == 0) { printf("\tjob %s is not in the queue\n", argv[i]); continue; } else changed++; } for (i = 0; i < nitems; i++) free(queue[i]); free(queue); if (!changed) { printf("\tqueue order unchanged\n"); return; } /* * Turn on the public execute bit of the lock file to * get lpd to rebuild the queue after the current job. */ PRIV_START if (changed && stat(pp->lock_file, &stbuf) >= 0) (void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE); out: PRIV_END } /* * Reposition the job by changing the modification time of * the control file. */ static int touch(struct jobqueue *jq) { struct timeval tvp[2]; int ret; tvp[0].tv_sec = tvp[1].tv_sec = --mtime; tvp[0].tv_usec = tvp[1].tv_usec = 0; PRIV_START ret = utimes(jq->job_cfname, tvp); PRIV_END return (ret); } /* * Checks if specified job name is in the printer's queue. * Returns: negative (-1) if argument name is not in the queue. */ static int doarg(char *job) { register struct jobqueue **qq; register int jobnum, n; register char *cp, *machine; int cnt = 0; FILE *fp; /* * Look for a job item consisting of system name, colon, number * (example: ucbarpa:114) */ if ((cp = strchr(job, ':')) != NULL) { machine = job; *cp++ = '\0'; job = cp; } else machine = NULL; /* * Check for job specified by number (example: 112 or 235ucbarpa). */ if (isdigit(*job)) { jobnum = 0; do jobnum = jobnum * 10 + (*job++ - '0'); while (isdigit(*job)); for (qq = queue + nitems; --qq >= queue; ) { n = 0; for (cp = (*qq)->job_cfname+3; isdigit(*cp); ) n = n * 10 + (*cp++ - '0'); if (jobnum != n) continue; if (*job && strcmp(job, cp) != 0) continue; if (machine != NULL && strcmp(machine, cp) != 0) continue; if (touch(*qq) == 0) { printf("\tmoved %s\n", (*qq)->job_cfname); cnt++; } } return(cnt); } /* * Process item consisting of owner's name (example: henry). */ for (qq = queue + nitems; --qq >= queue; ) { PRIV_START fp = fopen((*qq)->job_cfname, "r"); PRIV_END if (fp == NULL) continue; - while (getline(fp) > 0) + while (get_line(fp) > 0) if (line[0] == 'P') break; (void) fclose(fp); if (line[0] != 'P' || strcmp(job, line+1) != 0) continue; if (touch(*qq) == 0) { printf("\tmoved %s\n", (*qq)->job_cfname); cnt++; } } return(cnt); } /* * Enable both queuing & printing, and start printer (undo `down'). */ void up_q(struct printer *pp) { int setres, startok; char lf[MAXPATHLEN]; lock_file_name(pp, lf, sizeof lf); printf("%s:\n", pp->printer); setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf); PRIV_START startok = startdaemon(pp); PRIV_END if (!startok) printf("\tcouldn't start daemon\n"); else printf("\tdaemon started\n"); }