1/* 2 * $Id: psf.c,v 1.13 2009-10-16 01:50:50 didg Exp $ 3 * 4 * Copyright (c) 1990,1995 Regents of The University of Michigan. 5 * All Rights Reserved. See COPYRIGHT. 6 * 7 * PostScript Filter, psf. 8 * 9 * Handles both PostScript files and text files. Files with the 10 * '%!' PostScript header are sent directly to the printer, 11 * unmodified. Text files are first converted to PostScript, 12 * then sent. Printers may be directly attached or on an AppleTalk 13 * network. Other media are possible. Currently, psf invokes 14 * pap to send files to AppleTalk-ed printers. Replace the pap* 15 * variables to use another program for communication. psf only 16 * converts plain-text. If called as "tf" or "df", psf will invoke 17 * a troff or dvi to PostScript converter. 18 */ 19 20#ifdef HAVE_CONFIG_H 21#include "config.h" 22#endif /* HAVE_CONFIG_H */ 23 24#define FUCKED 25 26#ifdef HAVE_UNISTD_H 27#include <unistd.h> 28#endif /* HAVE_UNISTD_H */ 29#include <sys/time.h> 30 31/* POSIX.1 sys/wait.h check */ 32#include <sys/types.h> 33#ifdef HAVE_SYS_WAIT_H 34#include <sys/wait.h> 35#endif /* HAVE_SYS_WAIT_H */ 36#ifndef WEXITSTATUS 37#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) 38#endif /* ! WEXITSTATUS */ 39#ifndef WIFEXITED 40#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) 41#endif /* ! WIFEXITED */ 42 43#include <sys/file.h> 44#include <syslog.h> 45#include <atalk/paths.h> 46#include <stdio.h> 47#include <stdlib.h> 48#include <string.h> 49#include <ctype.h> 50#include <signal.h> 51#include <errno.h> 52 53/* Forward Declarations */ 54int pexecv(char *path, char *argv[]); 55int copyio(); 56int textps(); 57 58static char psapath[] = _PATH_PSA; 59static char *psaargv[] = { "psa", NULL, NULL, NULL, NULL }; 60 61/* 62 * If we're not doing accounting, we just call pap as below. 63 * If we are doing accounting, we call pap twice. The first time, 64 * we call it with "-E" in arg 2, pagecount.ps in arg 3, and "-" in 65 * arg 4. The second time, we call it with "-c" in arg 2, pagecount.ps 66 * in arg 3, and 0 in arg 4. 67 */ 68static char pappath[] = _PATH_PAP; 69static char *papargv[] = { "pap", "-sstatus", NULL, NULL, NULL, NULL, NULL, NULL }; 70 71static char revpath[] = _PATH_PSORDER; 72static char *revargv[] = { "psorder", "-d", NULL }; 73 74static char *filtargv[] = { NULL, NULL, NULL }; 75 76static char inbuf[ 1024 * 8 ]; 77static int inlen; 78 79static int literal; 80static int width = 80, length = 66, indent = 0; 81static char *prog, *name, *host; 82 83static struct papersize { 84 int width; 85 int length; 86 float win; 87 float lin; 88} papersizes[] = { 89 { 80, 66, 8.5, 11.0 }, /* US Letter */ 90 { 80, 70, 8.27, 11.69 }, /* A4 */ 91}; 92 93int main( int ac, char **av) 94{ 95 int c, rc, children = 0; 96#ifdef FUCKED 97 int psafileno = 0, multiconn = 0, waitidle = 0, waitidle2 = 0; 98#endif /* FUCKED */ 99 int status; 100 extern char *optarg; 101 extern int optind, opterr; 102 103 opterr = 0; 104 if (( prog = rindex( av[ 0 ], '/' )) == NULL ) { 105 prog = av[ 0 ]; 106 } else { 107 prog++; 108 } 109#ifdef ultrix 110 openlog( prog, LOG_PID ); 111#else /* ultrix */ 112 openlog( prog, LOG_PID, LOG_LPR ); 113#endif /* ultrix */ 114 115 while (( c = getopt( ac, av, "P:C:D:F:L:J:x:y:n:h:w:l:i:c" )) != EOF ) { 116 switch ( c ) { 117 case 'n' : 118 name = optarg; 119 break; 120 121 case 'h' : 122 host = optarg; 123 break; 124 125 case 'w' : 126 width = atoi( optarg ); 127#ifdef ZEROWIDTH 128 /* 129 * Some version of lpd pass 0 for the page width. 130 */ 131 if ( width == 0 ) { 132 width = 80; 133 } 134#endif /* ZEROWIDTH */ 135 break; 136 137 case 'l' : 138 length = atoi( optarg ); 139 break; 140 141 case 'i' : 142 indent = atoi( optarg ); 143 break; 144 145 case 'c' : /* Print control chars */ 146 literal++; 147 break; 148 149 case 'F' : 150 case 'L' : 151 case 'J' : 152 case 'P' : 153 case 'x' : 154 case 'y' : 155 break; 156 157#ifdef notdef 158 default : 159 syslog( LOG_ERR, "bad option: %c", c ); 160 exit( 2 ); 161#endif /* notdef */ 162 } 163 } 164 if ( ac - optind > 1 ) { 165 syslog( LOG_ERR, "Too many arguments" ); 166 exit( 2 ); 167 } 168#ifdef FUCKED 169 if ( index( prog, 'w' )) { 170 waitidle++; 171 } 172 if ( index( prog, 'W' )) { 173 waitidle2++; 174 } 175 if ( index( prog, 'm' )) { 176 multiconn++; 177 } 178#endif /* FUCKED */ 179 180 syslog( LOG_INFO, "starting for %s", name ? name : "?" ); 181 182restart: 183 if (( inlen = read( 0, inbuf, sizeof( inbuf ))) < 0 ) { 184 syslog( LOG_ERR, "read: %s", strerror(errno) ); 185 exit( 1 ); 186 } 187 if ( inlen == 0 ) { /* nothing to be done */ 188 syslog( LOG_INFO, "done" ); 189 exit( 0 ); 190 } 191 192 /* 193 * If we've been given an accounting file, start the accounting 194 * process. 195 */ 196 if ( optind < ac ) { 197 /* build arguments */ 198 psaargv[ 1 ] = av[ optind ]; 199 psaargv[ 2 ] = name; 200 psaargv[ 3 ] = host; 201 if (( c = pexecv( psapath, psaargv )) < 0 ) { 202 syslog( LOG_ERR, "%s: %s", psapath, strerror(errno) ); 203 exit( 2 ); 204 } 205 children++; 206 syslog( LOG_INFO, "accounting with psa[%d]", c ); 207 } 208 209 /* 210 * Check prog's name to decide what programs to execute. 211 */ 212 if ( strstr( prog, "pap" ) != NULL ) { 213 if ( optind < ac ) { /* accounting */ 214#ifdef FUCKED 215 if ( multiconn ) { 216 psafileno = getdtablesize(); 217 psafileno--; 218 dup2( 1, psafileno ); 219 220 if ( waitidle2 ) { 221 papargv[ 2 ] = "-W"; 222 papargv[ 3 ] = "-c"; 223 papargv[ 4 ] = "-E"; 224 papargv[ 5 ] = _PATH_PAGECOUNT; 225 papargv[ 6 ] = "-"; 226 papargv[ 7 ] = NULL; 227 } else if ( waitidle ) { 228 papargv[ 2 ] = "-w"; 229 papargv[ 3 ] = "-c"; 230 papargv[ 4 ] = "-E"; 231 papargv[ 5 ] = _PATH_PAGECOUNT; 232 papargv[ 6 ] = "-"; 233 papargv[ 7 ] = NULL; 234 } else { 235 papargv[ 2 ] = "-c"; 236 papargv[ 3 ] = "-E"; 237 papargv[ 4 ] = _PATH_PAGECOUNT; 238 papargv[ 5 ] = "-"; 239 papargv[ 6 ] = NULL; 240 } 241 } else { 242 /* 243 * This is how it should be done. 244 */ 245 papargv[ 2 ] = "-c"; 246 papargv[ 3 ] = _PATH_PAGECOUNT; 247 papargv[ 4 ] = "-"; 248 papargv[ 5 ] = _PATH_PAGECOUNT; 249 papargv[ 6 ] = NULL; 250 } 251#endif /* FUCKED */ 252 } else { 253 papargv[ 2 ] = "-c"; 254 papargv[ 3 ] = "-E"; 255 papargv[ 4 ] = NULL; 256 } 257 258 if (( c = pexecv( pappath, papargv )) < 0 ) { 259 syslog( LOG_ERR, "%s: %s", pappath, strerror(errno) ); 260 exit( 2 ); 261 } 262 children++; 263 syslog( LOG_INFO, "sending to pap[%d]", c ); 264 } 265 266 /* 267 * Might be a good idea to have both a "forw" and a "rev", so that 268 * reversed documents can be reordered for the printing device. 269 */ 270 if ( strstr( prog, "rev" ) != NULL ) { 271 if (( c = pexecv( revpath, revargv )) < 0 ) { 272 syslog( LOG_ERR, "%s: %s", revpath, strerror(errno) ); 273 exit( 2 ); 274 } 275 syslog( LOG_INFO, "sending to rev[%d]", c ); 276 children++; 277 } 278 279 /* 280 * Invoke an external (script) filter to produce PostScript from 281 * non-text input. 282 */ 283 if ( *prog != 'i' && *prog != 'o' && *( prog + 1 ) == 'f' ) { 284 filtargv[ 0 ] = filtargv[ 1 ] = prog; 285 if (( c = pexecv( _PATH_PSFILTER, filtargv )) < 0 ) { 286 syslog( LOG_ERR, "%s: %s", _PATH_PSFILTER, strerror(errno) ); 287 exit( 2 ); 288 } 289 syslog( LOG_INFO, "external filter[%d]", c ); 290 children++; 291 rc = copyio(); /* external filter */ 292 } else { 293 if ( inlen >= 2 && inbuf[ 0 ] == '%' && inbuf[ 1 ] == '!' ) { 294 syslog( LOG_INFO, "PostScript" ); 295 rc = copyio(); /* PostScript */ 296 } else if ( inlen >= 2 && inbuf[ 0 ] == '\033' && inbuf[ 1 ] == '%' ) { 297 syslog( LOG_INFO, "PostScript w/PJL" ); 298 rc = copyio(); /* PostScript */ 299 } else { 300 syslog( LOG_INFO, "straight text" ); 301 rc = textps(); /* straight text */ 302 } 303 } 304 305#ifdef FUCKED 306 if ( strstr( prog, "pap" ) != NULL && optind < ac && multiconn ) { 307 dup2( psafileno, 1 ); 308 close( psafileno ); 309 papargv[ 2 ] = "-c"; 310 if ( waitidle2 ) { 311 papargv[ 3 ] = "-W"; 312 papargv[ 4 ] = _PATH_PAGECOUNT; 313 papargv[ 5 ] = NULL; 314 } else if ( waitidle ) { 315 papargv[ 3 ] = "-w"; 316 papargv[ 4 ] = _PATH_PAGECOUNT; 317 papargv[ 5 ] = NULL; 318 } else { 319 papargv[ 3 ] = _PATH_PAGECOUNT; 320 papargv[ 4 ] = NULL; 321 } 322 323 if (( c = pexecv( pappath, papargv )) < 0 ) { 324 syslog( LOG_ERR, "%s: %s", pappath, strerror(errno) ); 325 exit( 2 ); 326 } 327 children++; 328 syslog( LOG_INFO, "pagecount with pap[%d]", c ); 329 } 330#endif /* FUCKED */ 331 332 if ( children ) { 333 close( 1 ); 334 } 335 while ( children ) { 336 if (( c = wait3( &status, 0, NULL )) < 0 ) { 337 syslog( LOG_ERR, "wait3: %s", strerror(errno) ); 338 exit( 1 ); 339 } 340 if ( WIFEXITED( status )) { 341#ifndef WEXITSTATUS 342#define WEXITSTATUS(x) ((x).w_status) 343#endif /* WEXITSTATUS */ 344 if ( WEXITSTATUS( status ) != 0 ) { 345 syslog( LOG_ERR, "%d died with %d", c, WEXITSTATUS( status )); 346 exit( WEXITSTATUS( status )); 347 } else { 348 syslog( LOG_INFO, "%d done", c ); 349 children--; 350 } 351 } else { 352 syslog( LOG_ERR, "%d died badly", c ); 353 exit( 1 ); 354 } 355 } 356 357 if ( rc == 3 ) { 358 syslog( LOG_INFO, "pausing" ); 359 kill( getpid(), SIGSTOP ); 360 syslog( LOG_INFO, "restarting" ); 361 goto restart; 362 } 363 364 syslog( LOG_INFO, "done" ); 365 exit( rc ); 366} 367 368int copyio(void) 369{ 370 /* implement the FSM needed to do the suspend. Note that 371 * the last characters will be \031\001 so don't worry 372 * Fun things: 1. \031\001 should not be written to output device 373 * 2. The \031 can be last char of one read, \001 first of next 374 * - we need to write \031 if not followed by \001 375 */ 376 struct timeval tv; 377 fd_set fdset; 378 int ctl = 0; 379 380notdone: 381 do { 382 /* 383 * First, \031 and \001 *must* be the last things in the buffer 384 * (\001 can be the first thing in the next buffer). There's no 385 * need to scan any of the intervening bytes. Second, if there's 386 * more input, the escape sequence was bogus, and we should keep 387 * reading. 388 */ 389 if ( inlen == 1 ) { 390 if ( ctl == 1 ) { 391 if ( inbuf[ 0 ] == '\001' ) { 392 ctl = 2; 393 break; 394 } 395 if ( write( 1, "\031", 1 ) != 1 ) { 396 syslog( LOG_ERR, "write: %s", strerror(errno) ); 397 return( 1 ); 398 } 399 ctl = 0; 400 } 401 402 if ( inbuf[ 0 ] == '\031' ) { 403 ctl = 1; 404 } 405 406 } else { 407 if ( ctl == 1 ) { 408 if ( write( 1, "\031", 1 ) != 1 ) { 409 syslog( LOG_ERR, "write: %s", strerror(errno) ); 410 return( 1 ); 411 } 412 } 413 ctl = 0; 414 if ( inbuf[ inlen - 2 ] == '\031' && 415 inbuf[ inlen - 1 ] == '\001' ) { 416 ctl = 2; 417 } else if ( inbuf[ inlen - 1 ] == '\031' ) { 418 ctl = 1; 419 } 420 } 421 422 inlen -= ctl; 423 if (( inlen > 0 ) && ( write( 1, inbuf, inlen ) != inlen )) { 424 syslog( LOG_ERR, "write: %s", strerror(errno) ); 425 return( 1 ); 426 } 427 if ( ctl == 2 ) { 428 break; 429 } 430 } while (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 ); 431 432 if ( ctl == 2 ) { 433 tv.tv_sec = 2; 434 tv.tv_usec = 0; 435 FD_ZERO( &fdset ); 436 FD_SET( 0, &fdset ); 437 if ( select( 1, &fdset, NULL, NULL, &tv ) != 0 ) { 438 if (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 ) { 439 goto notdone; 440 } 441 } 442 } 443 444 if ( inlen < 0 ) { 445 syslog( LOG_ERR, "read: %s", strerror(errno) ); 446 return( 1 ); 447 } 448 449 if ( ctl == 1 ) { 450 if ( write( 1, "\031", 1 ) != 1 ) { 451 syslog( LOG_ERR, "write: %s", strerror(errno) ); 452 return( 1 ); 453 } 454 } else if ( ctl == 2 ) { 455 return( 3 ); 456 } 457 return( 0 ); 458} 459 460static char *font = "Courier"; 461static int point = 11; 462 463static char pspro[] = "\ 464/GSV save def % global VM\n\ 465/SP {\n\ 466 /SV save def % save vmstate\n\ 467 dup /H exch def % save font height\n\ 468 exch findfont exch scalefont setfont % select font\n\ 469 ( ) stringwidth pop /W exch def % save font width\n\ 470 0.5 sub 72 mul /CY exch def % save start Y\n\ 471 pop 0.5 add 72 mul /CX exch def % save start X\n\ 472 CX CY moveto % make current point\n\ 473} bind def\n\ 474/S /show load def\n\ 475/NL { CX CY H sub dup /CY exch def moveto } bind def\n\ 476/CR { CX CY moveto } bind def\n\ 477/B { W neg 0 rmoveto}bind def\n\ 478/T { W mul 0 rmoveto}bind def\n\ 479/EP { SV restore showpage } bind def\n\ 480%%EndProlog\n"; 481 482int textps(void) 483{ 484 struct papersize papersize; 485 int state = 0, line = 0, col = 0, npages = 0, rc; 486 unsigned int i; 487 char *p, *end; 488 489#define elements(x) (sizeof(x)/sizeof((x)[0])) 490 for ( i = 0; i < elements( papersizes ); i++ ) { 491 if ( width == papersizes[ 0 ].width && 492 length == papersizes[ 0 ].length ) { 493 papersize = papersizes[ i ]; 494 break; 495 } 496 } 497 if ( i >= elements( papersizes )) { 498 papersize = papersizes[ 0 ]; /* default */ 499 } 500 501#define ST_AVAIL (1<<0) 502#define ST_CONTROL (1<<1) 503#define ST_PAGE (1<<2) 504 /* 505 * convert text lines to postscript. 506 * A grungy little state machine. If I was more creative, I could 507 * probably think of a better way of doing this... 508 */ 509 do { 510 p = inbuf; 511 end = inbuf + inlen; 512 while ( p < end ) { 513 if (( state & ST_PAGE ) == 0 && *p != '\031' && *p != '\001' ) { 514 if ( npages == 0 ) { 515 printf( "%%!PS-Adobe-2.0\n%%%%Pages: (atend)\n" ); 516 printf( "%%%%DocumentFonts: %s\n", font ); 517 fflush( stdout ); 518 519 /* output postscript prologue: */ 520 if ( write( 1, pspro, sizeof( pspro ) - 1 ) != 521 sizeof( pspro ) - 1 ) { 522 syslog( LOG_ERR, "write prologue: %s", strerror(errno) ); 523 return( 1 ); 524 } 525 if ( name && host ) { 526 printf( "statusdict /jobname (%s@%s) put\n", name, 527 host ); 528 } 529 } 530 531 printf( "%%%%Page: ? %d\n", ++npages ); 532 printf( "%d %f %f /%s %d SP\n", indent, 533 papersize.win, papersize.lin, font, point ); 534 state |= ST_PAGE; 535 } 536 if ( state & ST_CONTROL && *p != '\001' ) { 537 /* It is a very bad thing to toss a job because it contains 538 * unprintable characters. Instead, we will convert them to 539 * question marks. This is adapted from a solution described 540 * by Werner Eugster <eugster@giub.unibe.ch> on his ApplePrint 541 * webpage (http://www.giub.unibe.ch/~eugster/appleprint.html). 542 * 543 * Note that this is rather ugly code. The same change is 544 * applied identically at two different locations in this file. 545 * It would be better someday to combine the two. 546 */ 547 if ( !literal ) { 548 fprintf( stderr, 549 "unprintable character (0x%x) converted to ?!\n", 550 (unsigned char)*p ); 551 putchar( '?' ); /* Replace unprintable char with a question mark. */ 552 } else { 553 printf( "\\%o", (unsigned char)031 ); 554 } 555 state &= ~ST_CONTROL; 556 col++; 557 } 558 559 switch ( *p ) { 560 case '\n' : /* end of line */ 561 if ( state & ST_AVAIL ) { 562 printf( ")S\n" ); 563 state &= ~ST_AVAIL; 564 } 565 printf( "NL\n" ); 566 line++; 567 col = 0; 568 if ( line >= length ) { 569 printf( "EP\n" ); 570 state &= ~ST_PAGE; 571 line = 0; 572 } 573 break; 574 575 case '\r' : /* carriage return (for overtyping) */ 576 if ( state & ST_AVAIL ) { 577 printf( ")S CR\n" ); 578 state &= ~ST_AVAIL; 579 } 580 col = 0; 581 break; 582 583 case '\f' : /* form feed */ 584 if ( state & ST_AVAIL ) { 585 printf( ")S\n" ); 586 state &= ~ST_AVAIL; 587 } 588 printf( "EP\n" ); 589 state &= ~ST_PAGE; 590 line = 0; 591 col = 0; 592 break; 593 594 case '\b' : /* backspace */ 595 /* show line, back up one character */ 596 if ( state & ST_AVAIL ) { 597 printf( ")S\n" ); 598 state &= ~ST_AVAIL; 599 } 600 printf( "B\n" ); 601 col--; 602 break; 603 604 case '\t' : /* tab */ 605 if ( state & ST_AVAIL ) { 606 printf( ")S\n" ); 607 state &= ~ST_AVAIL; 608 } 609 printf( "%d T\n", 8 - ( col % 8 )); 610 col += 8 - ( col % 8 ); 611 break; 612 613 /* 614 * beginning of lpr control sequence 615 */ 616 case '\031' : 617 state |= ST_CONTROL; 618 break; 619 620 case '\001' : /* lpr control sequence */ 621 if ( state & ST_CONTROL ) { 622 rc = 3; 623 goto out; 624 } 625 /* FALLTHROUGH */ 626 627 case '\\' : 628 case ')' : 629 case '(' : 630 if (( state & ST_AVAIL ) == 0 ) { 631 printf( "(" ); 632 state |= ST_AVAIL; 633 } 634 putchar( '\\' ); 635 /* FALLTHROUGH */ 636 637 default : 638 if (( state & ST_AVAIL ) == 0 ) { 639 printf( "(" ); 640 state |= ST_AVAIL; 641 } 642 if ( !isascii( *p ) || !isprint( *p )) { 643 if ( !literal ) { 644 fprintf( stderr, 645 "unprintable character (0x%x) converted to ?!\n", 646 (unsigned char)*p ); 647 putchar( '?' ); /* Replace unprintable char with a question mark. */ 648 } else { 649 printf( "\\%o", (unsigned char)*p ); 650 } 651 } else { 652 putchar( *p ); 653 } 654 col++; 655 break; 656 } 657 p++; 658 } 659 } while (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 ); 660 if ( inlen < 0 ) { 661 syslog( LOG_ERR, "read: %s", strerror(errno) ); 662 return( 1 ); 663 } 664 rc = 0; 665 666out: 667 if ( state & ST_AVAIL ) { 668 printf( ")S\n" ); 669 state &= ~ST_AVAIL; 670 } 671 672 if ( state & ST_PAGE ) { 673 printf( "EP\n" ); 674 state &= ~ST_PAGE; 675 } 676 677 if ( npages > 0 ) { 678 printf( "%%%%Trailer\nGSV restore\n%%%%Pages: %d\n%%%%EOF\n", npages ); 679 fflush( stdout ); 680 } 681 682 return( rc ); 683} 684 685/* 686 * Interface to pipe and exec, for starting children in pipelines. 687 * 688 * Manipulates file descriptors 0, 1, and 2, such that the new child 689 * is reading from the parent's output. 690 */ 691int pexecv( char *path, char *argv[]) 692{ 693 int fd[ 2 ], c; 694 695 if ( pipe( fd ) < 0 ) { 696 return( -1 ); 697 } 698 699 switch ( c = fork()) { 700 case -1 : 701 return( -1 ); 702 /* NOTREACHED */ 703 704 case 0 : 705 if ( close( fd[ 1 ] ) < 0 ) { 706 return( -1 ); 707 } 708 if ( dup2( fd[ 0 ], 0 ) < 0 ) { 709 return( -1 ); 710 } 711 if ( close( fd[ 0 ] ) < 0 ) { 712 return( -1 ); 713 } 714 execv( path, argv ); 715 return( -1 ); 716 /* NOTREACHED */ 717 718 default : 719 if ( close( fd[ 0 ] ) < 0 ) { 720 return( -1 ); 721 } 722 if ( dup2( fd[ 1 ], 1 ) < 0 ) { 723 return( -1 ); 724 } 725 if ( close( fd[ 1 ] ) < 0 ) { 726 return( -1 ); 727 } 728 return( c ); 729 } 730} 731