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