* Copyright (c) 1983 Regents of the University of California.
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)printjob.c 5.13 (Berkeley) %G%";
* printjob -- print jobs in the queue.
* NOTE: the lock file is used to pass information to lpq and lprm.
* it does not need to be removed because file locks are dynamic.
#define DORETURN 0 /* absorb fork error */
#define DOABORT 1 /* abort if dofork fails */
char title
[80]; /* ``pr'' title */
FILE *cfp
; /* control file */
int pfd
; /* printer file descriptor */
int ofd
; /* output filter file descriptor */
int lfd
; /* lock file descriptor */
int pid
; /* pid of lpd process */
int prchild
; /* id of pr process */
int child
; /* id of any filters */
int ofilter
; /* id of output filter, if any */
int tof
; /* true if at top of form */
int remote
; /* true if sending files to remote */
dev_t fdev
; /* device of file pointed to by symlink */
ino_t fino
; /* inode of file pointed to by symlink */
char fromhost
[32]; /* user's host machine */
char logname
[32]; /* user's login name */
char jobname
[100]; /* job or file name */
char class[32]; /* classification field */
char width
[10] = "-w"; /* page width in characters */
char length
[10] = "-l"; /* page length in lines */
char pxwidth
[10] = "-x"; /* page width in pixels */
char pxlength
[10] = "-y"; /* page length in pixels */
char indent
[10] = "-i0"; /* indentation size in characters */
char tempfile
[] = "errsXXXXXX"; /* file name for filter output */
register struct queue
*q
, **qp
;
init(); /* set up capabilities */
(void) write(1, "", 1); /* ack that daemon is started */
(void) close(2); /* set up log file */
if (open(LF
, O_WRONLY
|O_APPEND
, 0664) < 0) {
syslog(LOG_ERR
, "%s: %m", LF
);
(void) open(_PATH_DEVNULL
, O_WRONLY
);
pid
= getpid(); /* for use with lprm */
signal(SIGQUIT
, abortpr
);
signal(SIGTERM
, abortpr
);
* uses short form file names
syslog(LOG_ERR
, "%s: %m", SD
);
if (stat(LO
, &stb
) == 0 && (stb
.st_mode
& 0100))
exit(0); /* printing disabled */
lfd
= open(LO
, O_WRONLY
|O_CREAT
, 0644);
syslog(LOG_ERR
, "%s: %s: %m", printer
, LO
);
if (flock(lfd
, LOCK_EX
|LOCK_NB
) < 0) {
if (errno
== EWOULDBLOCK
) /* active deamon present */
syslog(LOG_ERR
, "%s: %s: %m", printer
, LO
);
* write process id for others to know
sprintf(line
, "%u\n", pid
);
pidoff
= i
= strlen(line
);
if (write(lfd
, line
, i
) != i
) {
syslog(LOG_ERR
, "%s: %s: %m", printer
, LO
);
* search the spool directory for work and sort by queue order.
if ((nitems
= getq(&queue
)) < 0) {
syslog(LOG_ERR
, "%s: can't scan %s", printer
, SD
);
if (nitems
== 0) /* no work to do */
if (stb
.st_mode
& 01) { /* reset queue flag */
if (fchmod(lfd
, stb
.st_mode
& 0776) < 0)
syslog(LOG_ERR
, "%s: %s: %m", printer
, LO
);
openpr(); /* open printer or remote */
* we found something to do now do it --
* write the name of the current control file into the lock file
* so the spool queue program can tell what we're working on
for (qp
= queue
; nitems
--; free((char *) q
)) {
if (stat(q
->q_name
, &stb
) < 0)
(void) lseek(lfd
, pidoff
, 0);
(void) sprintf(line
, "%s\n", q
->q_name
);
if (write(lfd
, line
, i
) != i
)
syslog(LOG_ERR
, "%s: %s: %m", printer
, LO
);
* Check to see if we are supposed to stop printing or
* if we are to rebuild the queue.
if (fstat(lfd
, &stb
) == 0) {
/* stop printing before starting next job? */
/* rebuild queue (after lpc topq) */
for (free((char *) q
); nitems
--; free((char *) q
))
if (fchmod(lfd
, stb
.st_mode
& 0776) < 0)
syslog(LOG_WARNING
, "%s: %s: %m",
if (i
== OK
) /* file ok and printed */
else if (i
== REPRINT
) { /* try reprinting the job */
syslog(LOG_INFO
, "restarting %s", printer
);
kill(ofilter
, SIGCONT
); /* to be sure */
while ((i
= wait(0)) > 0 && i
!= ofilter
)
(void) close(pfd
); /* close printer */
if (ftruncate(lfd
, pidoff
) < 0)
syslog(LOG_WARNING
, "%s: %s: %m", printer
, LO
);
openpr(); /* try to reopen printer */
* search the spool directory for more work.
if ((nitems
= getq(&queue
)) < 0) {
syslog(LOG_ERR
, "%s: can't scan %s", printer
, SD
);
if (nitems
== 0) { /* no more work to do */
if (count
> 0) { /* Files actually printed */
(void) write(ofd
, FF
, strlen(FF
));
if (TR
!= NULL
) /* output trailer */
(void) write(ofd
, TR
, strlen(TR
));
char fonts
[4][50]; /* fonts for troff */
* The remaining part is the reading of the control file (cf)
* and performing the various actions.
* open control file; ignore if no longer there.
if ((cfp
= fopen(file
, "r")) == NULL
) {
syslog(LOG_INFO
, "%s: %s: %m", printer
, file
);
strcpy(fonts
[i
], ifonts
[i
]);
* read the control file for work to do
* file format -- first character in the line is a command
* rest of the line is the argument.
* S -- "stat info" for symbolic link protection
* J -- "job name" on banner page
* C -- "class name" on banner page
* L -- "literal" user's name to print on banner
* H -- "host name" of machine where lpr was done
* P -- "person" user's login name
* I -- "indent" amount to indent output
* f -- "file name" name of text file to print
* l -- "file name" text file with control chars
* p -- "file name" text file to print with pr(1)
* t -- "file name" troff(1) file to print
* n -- "file name" ditroff(1) file to print
* d -- "file name" dvi file to print
* g -- "file name" plot(1G) file to print
* v -- "file name" plain raster file to print
* c -- "file name" cifplot file to print
* 1 -- "R font file" for troff
* 2 -- "I font file" for troff
* 3 -- "B font file" for troff
* 4 -- "S font file" for troff
* N -- "name" of file (used by lpq)
* U -- "unlink" name of file to remove
* (after we print it. (Pass 2 only)).
* M -- "mail" to user when done printing
* getline reads a line and expands tabs to blanks
strcpy(fromhost
, line
+1);
strncpy(class, line
+1, sizeof(class)-1);
strncpy(logname
, line
+1, sizeof(logname
)-1);
if (RS
) { /* restricted */
if (getpwnam(logname
) == (struct passwd
*)0) {
sendmail(line
+1, bombed
);
while (*cp
>= '0' && *cp
<= '9')
i
= i
* 10 + (*cp
++ - '0');
while (*cp
>= '0' && *cp
<= '9')
i
= i
* 10 + (*cp
++ - '0');
strncpy(jobname
, line
+1, sizeof(jobname
)-1);
strncpy(class, line
+1, sizeof(class)-1);
else if (class[0] == '\0')
gethostname(class, sizeof(class));
case 'T': /* header title for pr */
strncpy(title
, line
+1, sizeof(title
)-1);
case 'L': /* identification line */
case '1': /* troff fonts */
strcpy(fonts
[line
[0]-'1'], line
+1);
case 'W': /* page width */
strncpy(width
+2, line
+1, sizeof(width
)-3);
case 'I': /* indent amount */
strncpy(indent
+2, line
+1, sizeof(indent
)-3);
default: /* some file to print */
switch (i
= print(line
[0], line
+1)) {
sendmail(logname
, bombed
);
case 'L': /* identification line */
if (bombed
< NOACCT
) /* already sent if >= NOACCT */
sendmail(line
+1, bombed
);
* clean-up in case another control file exists
return(bombed
== OK
? OK
: ERROR
);
* Set up the chain [ PR [ | {IF, OF} ] ] or {IF, RF, TF, NF, DF, CF, VF}.
* Return -1 if a non-recoverable error occured,
* 2 if the filter detected some errors (but printed the job anyway),
* 1 if we should try to reprint this job and
* Note: all filters take stdin as the file, stdout as the printer,
* stderr as the log file, and must not ignore SIGINT.
char *av
[15], buf
[BUFSIZ
];
int pid
, p
[2], stopped
= 0;
if (lstat(file
, &stb
) < 0 || (fi
= open(file
, O_RDONLY
)) < 0)
* Check to see if data file is a symbolic link. If so, it should
* still point to the same file or someone is trying to print
* something he shouldn't.
if ((stb
.st_mode
& S_IFMT
) == S_IFLNK
&& fstat(fi
, &stb
) == 0 &&
(stb
.st_dev
!= fdev
|| stb
.st_ino
!= fino
))
if (!SF
&& !tof
) { /* start on a fresh page */
(void) write(ofd
, FF
, strlen(FF
));
if (IF
== NULL
&& (format
== 'f' || format
== 'l')) {
while ((n
= read(fi
, buf
, BUFSIZ
)) > 0)
if (write(ofd
, buf
, n
) != n
) {
case 'p': /* print file using 'pr' */
if (IF
== NULL
) { /* use output filter */
av
[4] = *title
? title
: " ";
if ((prchild
= dofork(DORETURN
)) == 0) { /* child */
dup2(fi
, 0); /* file is stdin */
dup2(p
[1], 1); /* pipe is stdout */
for (n
= 3; n
< NOFILE
; n
++)
execl(_PATH_PR
, "pr", width
, length
,
"-h", *title
? title
: " ", 0);
syslog(LOG_ERR
, "cannot execl %s", _PATH_PR
);
(void) close(p
[1]); /* close output side */
fi
= p
[0]; /* use pipe for input */
case 'f': /* print plain text file */
case 'l': /* like 'f' but pass control characters */
case 'r': /* print a fortran text file */
case 't': /* print troff output */
case 'n': /* print ditroff output */
case 'd': /* print tex output */
(void) unlink(".railmag");
if ((fo
= creat(".railmag", FILMOD
)) < 0) {
syslog(LOG_ERR
, "%s: cannot create .railmag", printer
);
(void) unlink(".railmag");
for (n
= 0; n
< 4; n
++) {
(void) write(fo
, _PATH_VFONT
, 15);
(void) write(fo
, fonts
[n
], strlen(fonts
[n
]));
(void) write(fo
, "\n", 1);
prog
= (format
== 't') ? TF
: (format
== 'n') ? NF
: DF
;
case 'c': /* print cifplot output */
case 'g': /* print plot(1G) output */
case 'v': /* print raster output */
syslog(LOG_ERR
, "%s: illegal format character '%c'",
if ((av
[0] = rindex(prog
, '/')) != NULL
)
if (ofilter
> 0) { /* stop output filter */
wait3((int *)&status
, WUNTRACED
, 0)) > 0 && pid
!= ofilter
)
if (status
.w_stopval
!= WSTOPPED
) {
syslog(LOG_WARNING
, "%s: output filter died (%d)",
printer
, status
.w_retcode
);
if ((child
= dofork(DORETURN
)) == 0) { /* child */
n
= open(tempfile
, O_WRONLY
|O_CREAT
|O_TRUNC
, 0664);
for (n
= 3; n
< NOFILE
; n
++)
syslog(LOG_ERR
, "cannot execv %s", prog
);
while ((pid
= wait((int *)&status
)) > 0 && pid
!= child
)
if (stopped
) { /* restart output filter */
if (kill(ofilter
, SIGCONT
) < 0) {
syslog(LOG_ERR
, "cannot restart output filter");
/* Copy filter output to "lf" logfile */
if (fp
= fopen(tempfile
, "r")) {
while (fgets(buf
, sizeof(buf
), fp
))
if (!WIFEXITED(status
)) {
syslog(LOG_WARNING
, "%s: Daemon filter '%c' terminated (%d)",
printer
, format
, status
.w_termsig
);
switch (status
.w_retcode
) {
syslog(LOG_WARNING
, "%s: Daemon filter '%c' exited (%d)",
printer
, format
, status
.w_retcode
);
* Send the daemon control file (cf) and any data files.
* Return -1 if a non-recoverable error occured, 1 if a recoverable error and
register int i
, err
= OK
;
if ((cfp
= fopen(file
, "r")) == NULL
)
* read the control file for work to do
* file format -- first character in the line is a command
* rest of the line is the argument.
* commands of interest are:
* a-z -- "file name" name of file to print
* U -- "unlink" name of file to remove
* (after we print it. (Pass 2 only)).
while (*cp
>= '0' && *cp
<= '9')
i
= i
* 10 + (*cp
++ - '0');
while (*cp
>= '0' && *cp
<= '9')
i
= i
* 10 + (*cp
++ - '0');
if (line
[0] >= 'a' && line
[0] <= 'z') {
switch (sendfile('\3', last
+1)) {
sendmail(logname
, ACCESS
);
if (err
== OK
&& sendfile('\2', file
) > 0) {
* clean-up in case another control file exists
* Send a data file to the remote machine and spool it.
* Return positive if we should try resending.
if (lstat(file
, &stb
) < 0 || (f
= open(file
, O_RDONLY
)) < 0)
* Check to see if data file is a symbolic link. If so, it should
* still point to the same file or someone is trying to print something
if ((stb
.st_mode
& S_IFMT
) == S_IFLNK
&& fstat(f
, &stb
) == 0 &&
(stb
.st_dev
!= fdev
|| stb
.st_ino
!= fino
))
(void) sprintf(buf
, "%c%d %s\n", type
, stb
.st_size
, file
);
if (write(pfd
, buf
, amt
) != amt
||
(resp
= response()) < 0 || resp
== '\1') {
status("no space on remote; waiting for queue to drain");
syslog(LOG_ALERT
, "%s: can't send to %s; queue full",
status("sending to %s", RM
);
for (i
= 0; i
< stb
.st_size
; i
+= BUFSIZ
) {
if (i
+ amt
> stb
.st_size
)
if (sizerr
== 0 && read(f
, buf
, amt
) != amt
)
if (write(pfd
, buf
, amt
) != amt
) {
syslog(LOG_INFO
, "%s: %s: changed size", printer
, file
);
/* tell recvjob to ignore this file */
(void) write(pfd
, "\1", 1);
if (write(pfd
, "", 1) != 1 || response())
* Check to make sure there have been no errors and that both programs
* are in sync with eachother.
* Return non-zero if the connection was lost.
if (read(pfd
, &resp
, 1) != 1) {
syslog(LOG_INFO
, "%s: lost connection", printer
);
(void) write(ofd
, FF
, strlen(FF
));
if (SB
) { /* short banner only */
(void) write(ofd
, class, strlen(class));
(void) write(ofd
, ":", 1);
(void) write(ofd
, name1
, strlen(name1
));
(void) write(ofd
, " Job: ", 7);
(void) write(ofd
, name2
, strlen(name2
));
(void) write(ofd
, " Date: ", 8);
(void) write(ofd
, ctime(&tvec
), 24);
(void) write(ofd
, "\n", 1);
} else { /* normal banner */
(void) write(ofd
, "\n\n\n", 3);
scan_out(ofd
, name1
, '\0');
(void) write(ofd
, "\n\n", 2);
scan_out(ofd
, name2
, '\0');
(void) write(ofd
,"\n\n\n",3);
scan_out(ofd
, class, '\0');
(void) write(ofd
, "\n\n\n\n\t\t\t\t\tJob: ", 15);
(void) write(ofd
, name2
, strlen(name2
));
(void) write(ofd
, "\n\t\t\t\t\tDate: ", 12);
(void) write(ofd
, ctime(&tvec
), 24);
(void) write(ofd
, "\n", 1);
(void) write(ofd
, FF
, strlen(FF
));
for (scnwidth
= WIDTH
; --scnwidth
;) {
*p
++ = key
& 0200 ? c
: BACKGND
;
#define TRC(q) (((q)-' ')&0177)
scan_out(scfd
, scsp
, dlm
)
char outbuf
[LINELEN
+1], *sp
, c
, cc
;
extern char scnkey
[][HEIGHT
]; /* in lpdchar.c */
for (scnhgt
= 0; scnhgt
++ < HEIGHT
+DROP
; ) {
d
= dropit(c
= TRC(cc
= *sp
++));
if ((!d
&& scnhgt
> HEIGHT
) || (scnhgt
<= DROP
&& d
))
strp
= scnline(scnkey
[c
][scnhgt
-1-d
], strp
, cc
);
if (*sp
== dlm
|| *sp
== '\0' || nchrs
++ >= PW
/(WIDTH
+1)-1)
while (*--strp
== BACKGND
&& strp
>= outbuf
)
(void) write(scfd
, outbuf
, strp
-outbuf
);
* tell people about job completion
if ((s
= dofork(DORETURN
)) == 0) { /* child */
for (i
= 3; i
< NOFILE
; i
++)
if ((cp
= rindex(_PATH_SENDMAIL
, '/')) != NULL
)
sprintf(buf
, "%s@%s", user
, fromhost
);
execl(_PATH_SENDMAIL
, cp
, buf
, 0);
} else if (s
> 0) { /* parent */
printf("To: %s@%s\n", user
, fromhost
);
printf("Subject: printer job\n\n");
printf("Your printer job ");
printf("(%s) ", jobname
);
printf("\ncompleted successfully\n");
printf("\ncould not be printed\n");
printf("\ncould not be printed without an account on %s\n", host
);
if (stat(tempfile
, &stb
) < 0 || stb
.st_size
== 0 ||
(fp
= fopen(tempfile
, "r")) == NULL
) {
printf("\nwas printed but had some errors\n");
printf("\nwas printed but had the following errors:\n");
while ((i
= getc(fp
)) != EOF
)
printf("\nwas not printed because it was not linked to the original file\n");
* dofork - fork with retries on failure
for (i
= 0; i
< 20; i
++) {
if ((pid
= fork()) < 0) {
* Child should run as daemon instead of root
syslog(LOG_ERR
, "can't fork");
syslog(LOG_ERR
, "bad action (%d) to dofork", action
);
* Kill child processes to abort current job.
if ((status
= pgetent(line
, printer
)) < 0) {
syslog(LOG_ERR
, "can't open printer description file");
} else if (status
== 0) {
syslog(LOG_ERR
, "unknown printer: %s", printer
);
if ((LP
= pgetstr("lp", &bp
)) == NULL
)
if ((RP
= pgetstr("rp", &bp
)) == NULL
)
if ((LO
= pgetstr("lo", &bp
)) == NULL
)
if ((ST
= pgetstr("st", &bp
)) == NULL
)
if ((LF
= pgetstr("lf", &bp
)) == NULL
)
if ((SD
= pgetstr("sd", &bp
)) == NULL
)
if ((DU
= pgetnum("du")) < 0)
if ((FF
= pgetstr("ff", &bp
)) == NULL
)
if ((PW
= pgetnum("pw")) < 0)
sprintf(&width
[2], "%d", PW
);
if ((PL
= pgetnum("pl")) < 0)
sprintf(&length
[2], "%d", PL
);
if ((PX
= pgetnum("px")) < 0)
sprintf(&pxwidth
[2], "%d", PX
);
if ((PY
= pgetnum("py")) < 0)
sprintf(&pxlength
[2], "%d", PY
);
if ((FC
= pgetnum("fc")) < 0)
if ((FS
= pgetnum("fs")) < 0)
if ((XC
= pgetnum("xc")) < 0)
if ((XS
= pgetnum("xs")) < 0)
* Acquire line printer or remote connection.
for (i
= 1; ; i
= i
< 32 ? i
<< 1 : i
) {
pfd
= open(LP
, RW
? O_RDWR
: O_WRONLY
);
syslog(LOG_ERR
, "%s: %m", LP
);
status("waiting for %s to become ready (offline ?)", printer
);
status("%s is ready and printing", printer
);
for (i
= 1; ; i
= i
< 256 ? i
<< 1 : i
) {
(void) sprintf(line
, "\2%s\n", RP
);
if (write(pfd
, line
, n
) == n
&&
(resp
= response()) == '\0')
status("waiting for %s to come up", RM
);
status("waiting for queue to be enabled on %s", RM
);
status("sending to %s", RM
);
syslog(LOG_ERR
, "%s: no line printer device or host name",
* Start up an output filter, if needed.
if ((ofilter
= dofork(DOABORT
)) == 0) { /* child */
dup2(p
[0], 0); /* pipe is std in */
dup2(pfd
, 1); /* printer is std out */
for (i
= 3; i
< NOFILE
; i
++)
if ((cp
= rindex(OF
, '/')) == NULL
)
execl(OF
, cp
, width
, length
, 0);
syslog(LOG_ERR
, "%s: %s: %m", printer
, OF
);
(void) close(p
[0]); /* close input side */
ofd
= p
[1]; /* use pipe for output */
register struct bauds
*bp
;
if (ioctl(pfd
, TIOCEXCL
, (char *)0) < 0) {
syslog(LOG_ERR
, "%s: ioctl(TIOCEXCL): %m", printer
);
if (ioctl(pfd
, TIOCGETP
, (char *)&ttybuf
) < 0) {
syslog(LOG_ERR
, "%s: ioctl(TIOCGETP): %m", printer
);
for (bp
= bauds
; bp
->baud
; bp
++)
syslog(LOG_ERR
, "%s: illegal baud rate %d", printer
, BR
);
ttybuf
.sg_ispeed
= ttybuf
.sg_ospeed
= bp
->speed
;
if (ioctl(pfd
, TIOCSETP
, (char *)&ttybuf
) < 0) {
syslog(LOG_ERR
, "%s: ioctl(TIOCSETP): %m", printer
);
if (ioctl(pfd
, TIOCLBIC
, &XC
) < 0) {
syslog(LOG_ERR
, "%s: ioctl(TIOCLBIC): %m", printer
);
if (ioctl(pfd
, TIOCLBIS
, &XS
) < 0) {
syslog(LOG_ERR
, "%s: ioctl(TIOCLBIS): %m", printer
);
fd
= open(ST
, O_WRONLY
|O_CREAT
, 0664);
if (fd
< 0 || flock(fd
, LOCK_EX
) < 0) {
syslog(LOG_ERR
, "%s: %s: %m", printer
, ST
);
sprintf(buf
, msg
, a1
, a2
, a3
);
(void) write(fd
, buf
, strlen(buf
));