diff options
-rw-r--r-- | usr.sbin/lpr/lpd/printjob.c | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/usr.sbin/lpr/lpd/printjob.c b/usr.sbin/lpr/lpd/printjob.c index 9d85f1f3..da99e1f 100644 --- a/usr.sbin/lpr/lpd/printjob.c +++ b/usr.sbin/lpr/lpd/printjob.c @@ -154,6 +154,7 @@ static int sendfile(struct printer *_pp, int _type, char *_file, static int sendit(struct printer *_pp, char *_file); static void sendmail(struct printer *_pp, char *_userid, int _bombed); static void setty(const struct printer *_pp); +static void wait4data(struct printer *_pp, const char *_dfile); void printjob(struct printer *pp) @@ -617,6 +618,9 @@ print(struct printer *pp, int format, char *file) int p[2], retcode, stopped, wstatus, wstatus_set; struct stat stb; + /* Make sure the entire data file has arrived. */ + wait4data(pp, file); + if (lstat(file, &stb) < 0 || (fi = open(file, O_RDONLY)) < 0) { syslog(LOG_INFO, "%s: unable to open %s ('%c' line)", pp->printer, file, format); @@ -997,6 +1001,9 @@ sendfile(struct printer *pp, int type, char *file, char format, int copyreq) char buf[SPL_BUFSIZ], opt_c[4], opt_h[4], opt_n[4]; int copycnt, filtstat, narg, resp, sfd, sfres, sizerr, statrc; + /* Make sure the entire data file has arrived. */ + wait4data(pp, file); + statrc = lstat(file, &stb); if (statrc < 0) { syslog(LOG_ERR, "%s: error from lstat(%s): %m", @@ -1219,6 +1226,99 @@ return_sfres: } /* + * Some print servers send the control-file first, and then start sending the + * matching data file(s). That is not the correct order. If some queue is + * already printing an active job, then when that job is finished the queue + * may proceed to the control file of any incoming print job. This turns + * into a race between the process which is receiving the data file, and the + * process which is actively printing the very same file. When the remote + * server sends files in the wrong order, it is even possible that a queue + * will start to print a data file before the file has been created! + * + * So before we start to print() or send() a data file, we call this routine + * to make sure the data file is not still changing in size. Note that this + * problem will only happen for jobs arriving from a remote host, and that + * the process which has decided to print this job (and is thus making this + * check) is *not* the process which is receiving the job. + * + * A second benefit of this is that any incoming job is guaranteed to appear + * in a queue listing for at least a few seconds after it has arrived. Some + * lpr implementations get confused if they send a job and it disappears + * from the queue before they can check on it. + */ +#define MAXWAIT_ARRIVE 16 /* max to wait for the file to *exist* */ +#define MAXWAIT_4DATA (20*60) /* max to wait for it to stop changing */ +#define MINWAIT_4DATA 4 /* This value must be >= 1 */ +#define DEBUG_MINWAIT 1 +static void +wait4data(struct printer *pp, const char *dfile) +{ + const char *cp; + int statres; + size_t dlen, hlen; + time_t amtslept, checktime; + struct stat statdf; + + /* Skip these checks if the print job is from the local host. */ + dlen = strlen(dfile); + hlen = strlen(local_host); + if (dlen > hlen) { + cp = dfile + dlen - hlen; + if (strcmp(cp, local_host) == 0) + return; + } + + /* + * If this data file does not exist, then wait up to MAXWAIT_ARRIVE + * seconds for it to arrive. + */ + amtslept = 0; + statres = stat(dfile, &statdf); + while (statres < 0 && amtslept < MAXWAIT_ARRIVE) { + if (amtslept == 0) + pstatus(pp, "Waiting for data file from remote host"); + amtslept += MINWAIT_4DATA - sleep(MINWAIT_4DATA); + statres = stat(dfile, &statdf); + } + if (statres < 0) { + /* The file still does not exist, so just give up on it. */ + syslog(LOG_WARNING, "%s: wait4data() abandoned wait for %s", + pp->printer, dfile); + return; + } + + /* + * The file exists, so keep waiting until the data file has not + * changed for some reasonable amount of time. + */ + while (statres == 0 && amtslept < MAXWAIT_4DATA) { + checktime = time(NULL) - MINWAIT_4DATA; + if (statdf.st_mtime <= checktime) + break; + if (amtslept == 0) + pstatus(pp, "Waiting for data file from remote host"); + amtslept += MINWAIT_4DATA - sleep(MINWAIT_4DATA); + statres = stat(dfile, &statdf); + } + + if (statres != 0) + syslog(LOG_WARNING, "%s: %s disappeared during wait4data()", + pp->printer, dfile); + else if (amtslept > MAXWAIT_4DATA) + syslog(LOG_WARNING, + "%s: %s still changing after %lu secs in wait4data()", + pp->printer, dfile, (unsigned long)amtslept); +#if DEBUG_MINWAIT + else if (amtslept > MINWAIT_4DATA) + syslog(LOG_INFO, "%s: slept %lu secs in wait4data(%s)", + pp->printer, (unsigned long)amtslept, dfile); +#endif +} +#undef MAXWAIT_ARRIVE +#undef MAXWAIT_4DATA +#undef MINWAIT_4DATA + +/* * This routine is called to execute one of the filters as was * specified in a printcap entry. While the child-process will read * all of 'infd', it is up to the caller to close that file descriptor |