xref: /minix/minix/commands/lpd/lpd.c (revision 7f5f010b)
1 /*	lpd 1.6 - Printer daemon			Author: Kees J. Bot
2  *								3 Dec 1989
3  */
4 #define nil 0
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <limits.h>
8 #include <string.h>
9 #include <errno.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <dirent.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <termcap.h>
16 
17 char PRINTER[] =	"/dev/lp";
18 char SPOOL[] =		"/usr/spool/lpd";
19 char LOG[] =		"/dev/log";
20 
21 void report(char *mess)
22 {
23 	fprintf(stderr, "lpd: %s: %s\n", mess, strerror(errno));
24 }
25 
26 void fatal(char *mess)
27 {
28 	report(mess);
29 	exit(1);
30 }
31 
32 char jobX[] = "jobXXXXXX";
33 char tmpX[] = "tmpXXXXXX";
34 
35 void spoolerr(char *file)
36 {
37 	unlink(jobX);
38 	unlink(tmpX);
39 	fatal(file);
40 }
41 
42 void spool(char *path)
43 /* Place a file into the spool directory, either by copying it, or by leaving
44  * a reference.
45  */
46 {
47 	char *file;
48 	int j, u;
49 
50 	mktemp(jobX);
51 	file= mktemp(tmpX);
52 
53 	if (path[0] == '/') {
54 		int f;
55 
56 		if ((f= open(path, O_RDONLY)) >= 0) {
57 			close(f);
58 			file= path;
59 		}
60 	}
61 	if (file != path) {
62 		int c;
63 		FILE *t;
64 
65 		if ((t= fopen(tmpX, "w")) == nil) spoolerr(tmpX);
66 
67 		while ((c= getchar()) != EOF && putc(c, t) != EOF) {}
68 
69 		if (ferror(stdin)) spoolerr(path);
70 
71 		if (ferror(t) || fclose(t) == EOF) spoolerr(tmpX);
72 
73 		fclose(stdin);
74 	}
75 
76 	if ((j= open(jobX, O_WRONLY|O_CREAT|O_EXCL, 0000)) < 0) spoolerr(jobX);
77 
78 	u= getuid();
79 	if (write(j, file, strlen(file)+1) < 0
80 		|| write(j, &u, sizeof(u)) < 0
81 		|| write(j, path, strlen(path)+1) < 0
82 		|| close(j) < 0
83 		|| chmod(jobX, 0600) < 0
84 	) spoolerr(jobX);
85 }
86 
87 struct job {
88 	struct job *next;
89 	time_t age;
90 	char name[sizeof(jobX)];
91 } *jobs = nil;
92 
93 int job(void)
94 /* Look for print jobs in the spool directory.  Make a list of them sorted
95  * by age.  Return true iff the list is nonempty.
96  */
97 {
98 	DIR *spool;
99 	struct dirent *entry;
100 	struct job *newjob, **ajob;
101 	struct stat st;
102 
103 	if (jobs != nil) return 1;
104 
105 	if ((spool= opendir(".")) == nil) fatal(SPOOL);
106 
107 	while ((entry= readdir(spool)) != nil) {
108 		if (strncmp(entry->d_name, "job", 3) != 0) continue;
109 
110 		if (stat(entry->d_name, &st) < 0
111 			|| (st.st_mode & 0777) == 0000) continue;
112 
113 		if ((newjob= malloc(sizeof(*newjob))) == nil) fatal("malloc()");
114 		newjob->age = st.st_mtime;
115 		strcpy(newjob->name, entry->d_name);
116 
117 		ajob= &jobs;
118 		while (*ajob != nil && (*ajob)->age < newjob->age)
119 			ajob= &(*ajob)->next;
120 
121 		newjob->next= *ajob;
122 		*ajob= newjob;
123 	}
124 	closedir(spool);
125 
126 	return jobs != nil;
127 }
128 
129 /* What to do with control-X:
130  * 0 ignore,
131  * 1 give up on controlling the printer, assume user knows how printer works,
132  * 2 print.
133  */
134 char control[] = {
135 	0, 1, 1, 1, 1, 1, 1, 0,		/* \0, \a  don't show.	*/
136 	2, 2, 2, 1, 2, 2, 1, 1,		/* \b, \t, \n, \f, \r	*/
137 	1, 1, 1, 1, 1, 1, 1, 1,
138 	1, 1, 1, 1, 1, 1, 1, 1
139 };
140 
141 int lp;
142 char buf[BUFSIZ];
143 int count, column, line, ncols = 80, nlines = 66;
144 
145 int flush(void)
146 /* Copy the characters in the output buffer to the printer, with retries if
147  * out of paper.
148  */
149 {
150 	char *bp= buf;
151 
152 	while (count > 0) {
153 		int retry = 0, complain = 0;
154 		int r;
155 
156 		while ((r= write(lp, bp, count)) < 0) {
157 			if (errno != EAGAIN) fatal(PRINTER);
158 			if (retry == complain) {
159 				fprintf(stderr,
160 					"lpd: %s: Printer out of paper\n",
161 					PRINTER);
162 				complain= retry + 60;
163 			}
164 			sleep(1);
165 			retry++;
166 		}
167 		bp+= r;
168 		count-= r;
169 	}
170 	count = 0;
171 }
172 
173 void put(int c)
174 /* Send characters to the output buffer to be printed and do so if the buffer
175  * is full.  Track the position of the write-head in `column' and `line'.
176  */
177 {
178 	buf[count++] = c;
179 
180 	switch (c) {
181 	case '\f':
182 		column = 0;
183 		line = 0;
184 		break;
185 	case '\r':
186 		column = 0;
187 		break;
188 	case '\n':
189 		line++;
190 		break;
191 	case '\b':
192 		column--;
193 		break;
194 	default:
195 		if (++column > ncols) { line++; column= 1; }
196 	}
197 	if (line == nlines) line= 0;
198 
199 	if (count == BUFSIZ) flush();
200 }
201 
202 void print(FILE *f)
203 /* Send the contents of an open file to the printer.  Expand tabs and change
204  * linefeed to a carriage-return linefeed sequence.  Print a formfeed at the
205  * end if needed to reach the top of the next page.  If a control character
206  * is printed that we do not know about, then the user is assumed to know
207  * what they are doing, so output processing is disabled.
208  */
209 {
210 	int c;
211 
212 	count= column= line= 0;
213 
214 	while ((c= getc(f)) != EOF) {
215 		if (c < ' ') {
216 			switch (control[c]) {
217 			case 0:	continue;	/* Ignore this one. */
218 			case 1:
219 				/* Can't handle this junk, assume smart user. */
220 				do {
221 					buf[count++] = c;
222 					if (count == BUFSIZ) flush();
223 				} while ((c= getc(f)) != EOF);
224 
225 				flush();
226 				return;
227 			case 2: /* fine */;
228 			}
229 		}
230 
231 		switch (c) {
232 		case '\n':
233 			put('\r');
234 			put('\n');
235 			break;
236 		case '\t':
237 			do {
238 				put(' ');
239 			} while (column & 07);
240 			break;
241 		case '\b':
242 			if (column > 0) put(c);
243 			break;
244 		default:
245 			put(c);
246 		}
247 	}
248 	if (column > 0) { put('\r'); put('\n'); }
249 	if (line > 0) put('\f');
250 	flush();
251 	return;
252 }
253 
254 void joberr(char *job)
255 {
256 	fprintf(stderr, "lpd: something is wrong with %s\n", job);
257 
258 	if (unlink(job) < 0) fatal("can't remove it");
259 }
260 
261 void work(void)
262 /* Print all the jobs in the job list. */
263 {
264 	FILE *j, *f;
265 	char file[PATH_MAX+1], *pf=file;
266 	int c;
267 	struct job *job;
268 
269 	job= jobs;
270 	jobs= jobs->next;
271 
272 	if ((j= fopen(job->name, "r")) == nil) {
273 		joberr(job->name);
274 		return;
275 	}
276 
277 	do {
278 		if (pf == file + sizeof(file) || (c= getc(j)) == EOF) {
279 			fclose(j);
280 			joberr(job->name);
281 			return;
282 		}
283 		*pf++ = c;
284 	} while (c != 0);
285 
286 	fclose(j);
287 
288 	if ((f= fopen(file, "r")) == nil)
289 		fprintf(stderr, "lpd: can't read %s\n", file);
290 	else {
291 		print(f);
292 		fclose(f);
293 	}
294 	if (file[0] != '/' && unlink(file) < 0) report(file);
295 
296 	if (unlink(job->name) < 0) fatal(job->name);
297 	free(job);
298 }
299 
300 void getcap(void)
301 /* Find the line printer dimensions in the termcap database under "lp". */
302 {
303 	char printcap[1024];
304 	int n;
305 
306 	if (tgetent(printcap, "lp") == 1) {
307 		if ((n= tgetnum("co")) > 0) ncols= n;
308 		if ((n= tgetnum("li")) > 0) nlines= n;
309 	}
310 }
311 
312 void haunt(void)
313 /* Become a daemon, print jobs while there are any, exit. */
314 {
315 	int fd;
316 
317 	if ((fd= open("/dev/tty", O_RDONLY)) != -1) {
318 		/* We have a controlling tty!  Disconnect. */
319 		close(fd);
320 
321 		switch(fork()) {
322 		case -1:	fatal("can't fork");
323 		case  0:	break;
324 		default:	exit(0);
325 		}
326 
327 		if ((fd= open("/dev/null", O_RDONLY)) < 0) fatal("/dev/null");
328 		dup2(fd, 0);
329 		close(fd);
330 		if ((fd= open(LOG, O_WRONLY)) < 0) fatal(LOG);
331 		dup2(fd, 1);
332 		dup2(fd, 2);
333 		close(fd);
334 		setsid();
335 	}
336 
337 	getcap();
338 
339 	do {
340 		if ((lp= open(PRINTER, O_WRONLY)) < 0) {
341 			/* Another lpd? */
342 			if (errno == EBUSY) exit(0);
343 			fatal(PRINTER);
344 		}
345 
346 		while (job()) work();
347 
348 		close(lp);
349 	} while (job());
350 }
351 
352 int main(int argc, char **argv)
353 {
354 	if (argc > 2) {
355 		fprintf(stderr, "Usage: %s [path | stdin < path]\n", argv[0]);
356 		exit(1);
357 	}
358 
359 	umask(0077);
360 
361 	if (chdir(SPOOL) < 0) fatal(SPOOL);
362 
363 	if (argc == 2) spool(argv[1]);
364 
365 	haunt();
366 }
367