| /* | 
 |  * | 
 |  * download - host resident font downloader | 
 |  * | 
 |  * Prepends host resident fonts to PostScript input files. The program assumes | 
 |  * the input files are part of a single PostScript job and that requested fonts | 
 |  * can be downloaded at the start of each input file. Downloaded fonts are the | 
 |  * ones named in a %%DocumentFonts: comment and listed in a special map table. | 
 |  * Map table pathnames (supplied using the -m option) that begin with a / are | 
 |  * taken as is. Otherwise the final pathname is built using *hostfontdir (-H | 
 |  * option), *mapname (-m option), and *suffix. | 
 |  * | 
 |  * The map table consists of fontname-filename pairs, separated by white space. | 
 |  * Comments are introduced by % (as in PostScript) and extend to the end of the | 
 |  * current line. The only fonts that can be downloaded are the ones listed in | 
 |  * the active map table that point the program to a readable Unix file. A request | 
 |  * for an unlisted font or inaccessible file is ignored. All font requests are | 
 |  * ignored if the map table can't be read. In that case the program simply copies | 
 |  * the input files to stdout. | 
 |  * | 
 |  * An example (but not one to follow) of what can be in a map table is, | 
 |  * | 
 |  *	% | 
 |  *	% Map requests for Bookman-Light to file *hostfontdir/KR | 
 |  *	% | 
 |  * | 
 |  *	  Bookman-Light		KR	% Keeping everything (including the map | 
 |  *					% table) in *hostfontdir seems like the | 
 |  *					% cleanest approach. | 
 |  * | 
 |  *	% | 
 |  *	% Map Palatino-Roman to file *hostfontdir/palatino/Roman | 
 |  *	% | 
 |  *	  Palatino-Roman	palatino/Roman | 
 |  * | 
 |  *	% Map ZapfDingbats to file /usr/lib/host/dingbats | 
 |  * | 
 |  *	  ZapfDingbats		/usr/lib/host/dingbats | 
 |  * | 
 |  * Once again, file names that begin with a / are taken as is. All others have | 
 |  * *hostfontdir/ prepended to the file string associated with a particular font. | 
 |  * | 
 |  * Map table can be associated with a printer model (e.g. a LaserWriter), a | 
 |  * printer destination, or whatever - the choice is up to an administrator. | 
 |  * By destination may be best if your spooler is running several private | 
 |  * printers. Host resident fonts are usually purchased under a license that | 
 |  * restricts their use to a limited number of printers. A font licensed for | 
 |  * a single printer should only be used on that printer. | 
 |  * | 
 |  * Was written quickly, so there's much room for improvement. Undoubtedly should | 
 |  * be a more general program (e.g. scan for other comments). | 
 |  * | 
 |  */ | 
 |  | 
 | #include <u.h> | 
 | #include <stdio.h> | 
 | #include <signal.h> | 
 | #include <sys/types.h> | 
 | #include <fcntl.h> | 
 | #include <sys/stat.h> | 
 | #include <string.h> | 
 | #include <stdlib.h> | 
 | #include <unistd.h> | 
 | #include <libc.h> | 
 |  | 
 | #include "../common/ext.h" | 
 | #include "comments.h"			/* PostScript file structuring comments */ | 
 | #include "gen.h"			/* general purpose definitions */ | 
 | #include "path.h"			/* for temporary directory */ | 
 | #include "ext.h"			/* external variable declarations */ | 
 | #include "download.h"			/* a few special definitions */ | 
 |  | 
 | char	*temp_dir = TEMPDIR;		/* temp directory - for copying stdin */ | 
 | char	*hostfontdir = HOSTDIR;		/* host resident directory */ | 
 | char	*mapname = "map";		/* map table - usually in *hostfontdir */ | 
 | char	*suffix = "";			/* appended to the map table pathname */ | 
 | Map	*map = NULL;			/* device font map table */ | 
 | char	*stringspace = NULL;		/* for storing font and file strings */ | 
 | int	next = 0;			/* next free slot in map[] */ | 
 |  | 
 | char	*residentfonts = NULL;		/* list of printer resident fonts */ | 
 | char	*printer = NULL;		/* printer name - only for Unix 4.0 lp */ | 
 |  | 
 | char	buf[2048];			/* input file line buffer */ | 
 | char	*comment = DOCUMENTFONTS;	/* look for this comment */ | 
 | int	atend = FALSE;			/* TRUE only if a comment says so */ | 
 |  | 
 | FILE	*fp_in;				/* next input file */ | 
 | FILE	*fp_temp = NULL;		/* for copying stdin */ | 
 |  | 
 | void init_signals(void); | 
 | void options(void); | 
 | void readmap(void); | 
 | void readresident(void); | 
 | void arguments(void); | 
 | void done(void); | 
 | void download(void); | 
 | int lookup(char *font); | 
 | void copyfonts(char *list); | 
 | void copyinput(void); | 
 | extern int cat(char *file); | 
 | extern void error(int errtype, char *fmt, ...); | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | int | 
 | main(agc, agv) | 
 |  | 
 |     int		agc; | 
 |     char	*agv[]; | 
 |  | 
 | { | 
 |  | 
 | /* | 
 |  * | 
 |  * Host resident font downloader. The input files are assumed to be part of a | 
 |  * single PostScript job. | 
 |  * | 
 |  */ | 
 |  | 
 |     argc = agc;				/* other routines may want them */ | 
 |     argv = agv; | 
 |  | 
 |     hostfontdir = unsharp(hostfontdir); | 
 |  | 
 |     fp_in = stdin; | 
 |  | 
 |     prog_name = argv[0];		/* just for error messages */ | 
 |  | 
 |     init_signals();			/* sets up interrupt handling */ | 
 |     options();				/* first get command line options */ | 
 |     readmap();				/* read the font map table */ | 
 |     readresident();			/* and the optional resident font list */ | 
 |     arguments();			/* then process non-option arguments */ | 
 |     done();				/* and clean things up */ | 
 |     exit(x_stat);			/* not much could be wrong */ | 
 |  | 
 | }   /* End of main */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | init_signals(void) | 
 | { | 
 |  | 
 | /* | 
 |  * | 
 |  * Makes sure we handle interrupts properly. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( signal(SIGINT, interrupt) == SIG_IGN ) { | 
 | 	signal(SIGINT, SIG_IGN); | 
 | 	signal(SIGQUIT, SIG_IGN); | 
 | 	signal(SIGHUP, SIG_IGN); | 
 |     } else { | 
 | 	signal(SIGHUP, interrupt); | 
 | 	signal(SIGQUIT, interrupt); | 
 |     }   /* End else */ | 
 |  | 
 |     signal(SIGTERM, interrupt); | 
 |  | 
 | }   /* End of init_signals */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | options(void) | 
 | { | 
 |  | 
 |     int		ch;			/* return value from getopt() */ | 
 |     char	*optnames = "c:fm:p:r:H:T:DI"; | 
 |  | 
 |     extern char	*optarg;		/* used by getopt() */ | 
 |     extern int	optind; | 
 |  | 
 | /* | 
 |  * | 
 |  * Reads and processes the command line options. | 
 |  * | 
 |  */ | 
 |  | 
 |     while ( (ch = getopt(argc, argv, optnames)) != EOF ) { | 
 | 	switch ( ch ) { | 
 | 	    case 'c':			/* look for this comment */ | 
 | 		    comment = optarg; | 
 | 		    break; | 
 |  | 
 | 	    case 'f':			/* force a complete input file scan */ | 
 | 		    atend = TRUE; | 
 | 		    break; | 
 |  | 
 | 	    case 'm':			/* printer map table name */ | 
 | 		    mapname = optarg; | 
 | 		    break; | 
 |  | 
 | 	    case 'p':			/* printer name - for Unix 4.0 lp */ | 
 | 		    printer = optarg; | 
 | 		    break; | 
 |  | 
 | 	    case 'r':			/* resident font list */ | 
 | 		    residentfonts = optarg; | 
 | 		    break; | 
 |  | 
 | 	    case 'H':			/* host resident font directory */ | 
 | 		    hostfontdir = optarg; | 
 | 		    break; | 
 |  | 
 | 	    case 'T':			/* temporary file directory */ | 
 | 		    temp_dir = optarg; | 
 | 		    break; | 
 |  | 
 | 	    case 'D':			/* debug flag */ | 
 | 		    debug = ON; | 
 | 		    break; | 
 |  | 
 | 	    case 'I':			/* ignore FATAL errors */ | 
 | 		    ignore = ON; | 
 | 		    break; | 
 |  | 
 | 	    case '?':			/* don't understand the option */ | 
 | 		    error(FATAL, ""); | 
 | 		    break; | 
 |  | 
 | 	    default:			/* don't know what to do for ch */ | 
 | 		    error(FATAL, "missing case for option %c\n", ch); | 
 | 		    break; | 
 | 	}   /* End switch */ | 
 |     }   /* End while */ | 
 |  | 
 |     argc -= optind;			/* get ready for non-option args */ | 
 |     argv += optind; | 
 |  | 
 | }   /* End of options */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | readmap(void) | 
 | { | 
 |  | 
 |     char	*path; | 
 |     char	*ptr; | 
 |     int		fd; | 
 |     struct stat	sbuf; | 
 |  | 
 | /* | 
 |  * | 
 |  * Initializes the map table by reading an ASCII mapping file. If mapname begins | 
 |  * with a / it's the map table. Otherwise hostfontdir, mapname, and suffix are | 
 |  * combined to build the final pathname. If we can open the file we read it all | 
 |  * into memory, erase comments, and separate the font and file name pairs. When | 
 |  * we leave next points to the next free slot in the map[] array. If it's zero | 
 |  * nothing was in the file or we couldn't open it. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( hostfontdir == NULL || mapname == NULL ) | 
 | 	return; | 
 |  | 
 |     if ( *mapname != '/' ) { | 
 | 	if ( (path = (char *)malloc(strlen(hostfontdir) + strlen(mapname) + | 
 | 						strlen(suffix) + 2)) == NULL ) | 
 | 	    error(FATAL, "no memory"); | 
 | 	sprintf(path, "%s/%s%s", hostfontdir, mapname, suffix); | 
 |     } else path = mapname; | 
 |  | 
 |     if ( (fd = open(path, 0)) != -1 ) { | 
 | 	if ( fstat(fd, &sbuf) == -1 ) | 
 | 	    error(FATAL, "can't fstat %s", path); | 
 | 	if ( (stringspace = (char *)malloc(sbuf.st_size + 2)) == NULL ) | 
 | 	    error(FATAL, "no memory"); | 
 | 	if ( read(fd, stringspace, sbuf.st_size) == -1 ) | 
 | 	    error(FATAL, "can't read %s", path); | 
 | 	close(fd); | 
 |  | 
 | 	stringspace[sbuf.st_size] = '\n';	/* just to be safe */ | 
 | 	stringspace[sbuf.st_size+1] = '\0'; | 
 | 	for ( ptr = stringspace; *ptr != '\0'; ptr++ )	/* erase comments */ | 
 | 	    if ( *ptr == '%' ) | 
 | 		for ( ; *ptr != '\n' ; ptr++ ) | 
 | 		    *ptr = ' '; | 
 |  | 
 | 	for ( ptr = stringspace; ; next++ ) { | 
 | 	    if ( (next % 50) == 0 ) | 
 | 		map = allocate(map, next+50); | 
 | 	    map[next].downloaded = FALSE; | 
 | 	    map[next].font = strtok(ptr, " \t\n"); | 
 | 	    map[next].file = strtok(ptr = NULL, " \t\n"); | 
 | 	    if ( map[next].font == NULL ) | 
 | 		break; | 
 | 	    if ( map[next].file == NULL ) | 
 | 		error(FATAL, "map table format error - check %s", path); | 
 | 	}   /* End for */ | 
 |     }	/* End if */ | 
 |  | 
 | }   /* End of readmap */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | readresident(void) | 
 | { | 
 |  | 
 |     FILE	*fp; | 
 |     char	*path; | 
 |     int		ch; | 
 |     int		n; | 
 |  | 
 | /* | 
 |  * | 
 |  * Reads a file that lists the resident fonts for a particular printer and marks | 
 |  * each font as already downloaded. Nothing's done if the file can't be read or | 
 |  * there's no mapping file. Comments, as in the map file, begin with a % and | 
 |  * extend to the end of the line. Added for Unix 4.0 lp. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( next == 0 || (printer == NULL && residentfonts == NULL) ) | 
 | 	return; | 
 |  | 
 |     if ( printer != NULL ) {		/* use Unix 4.0 lp pathnames */ | 
 | 	sprintf(buf, "%s/printers/%s", HOSTDIR, printer); | 
 | 	path = unsharp(buf); | 
 |     } else path = residentfonts; | 
 |  | 
 |     if ( (fp = fopen(path, "r")) != NULL ) { | 
 | 	while ( fscanf(fp, "%s", buf) != EOF ) | 
 | 	    if ( buf[0] == '%' ) | 
 | 		while ( (ch = getc(fp)) != EOF && ch != '\n' ) ; | 
 | 	    else if ( (n = lookup(buf)) < next ) | 
 | 		map[n].downloaded = TRUE; | 
 | 	fclose(fp); | 
 |     }	/* End if */ | 
 |  | 
 | }   /* End of readresident */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | arguments(void) | 
 | { | 
 |  | 
 | /* | 
 |  * | 
 |  * Makes sure all the non-option command line arguments are processed. If we get | 
 |  * here and there aren't any arguments left, or if '-' is one of the input files | 
 |  * we'll translate stdin. Assumes input files are part of a single PostScript | 
 |  * job and fonts can be downloaded at the start of each file. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( argc < 1 ) | 
 | 	download(); | 
 |     else { | 
 | 	while ( argc > 0 ) { | 
 | 	    fp_temp = NULL; | 
 | 	    if ( strcmp(*argv, "-") == 0 ) | 
 | 		fp_in = stdin; | 
 | 	    else if ( (fp_in = fopen(*argv, "r")) == NULL ) | 
 | 		error(FATAL, "can't open %s", *argv); | 
 | 	    download(); | 
 | 	    if ( fp_in != stdin ) | 
 | 		fclose(fp_in); | 
 | 	    if ( fp_temp != NULL ) | 
 | 		fclose(fp_temp); | 
 | 	    argc--; | 
 | 	    argv++; | 
 | 	}   /* End while */ | 
 |     }	/* End else */ | 
 |  | 
 | }   /* End of arguments */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | done(void) | 
 | { | 
 |  | 
 | /* | 
 |  * | 
 |  * Clean things up before we quit. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( temp_file != NULL ) | 
 | 	unlink(temp_file); | 
 |  | 
 | }   /* End of done */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | download(void) | 
 | { | 
 |  | 
 |     int		infontlist = FALSE; | 
 |  | 
 | /* | 
 |  * | 
 |  * If next is zero the map table is empty and all we do is copy the input file | 
 |  * to stdout. Otherwise we read the input file looking for %%DocumentFonts: or | 
 |  * continuation comments, add any accessible fonts to the output file, and then | 
 |  * append the input file. When reading stdin we append lines to fp_temp and | 
 |  * recover them when we're ready to copy the input file. fp_temp will often | 
 |  * only contain part of stdin - if there's no %%DocumentFonts: (atend) comment | 
 |  * we stop reading fp_in after the header. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( next > 0 ) { | 
 | 	if ( fp_in == stdin ) { | 
 | 	    if ( (temp_file = tempnam(temp_dir, "post")) == NULL ) | 
 | 		error(FATAL, "can't generate temp file name"); | 
 | 	    if ( (fp_temp = fopen(temp_file, "w+r")) == NULL ) | 
 | 		error(FATAL, "can't open %s", temp_file); | 
 | 	    unlink(temp_file); | 
 | 	}   /* End if */ | 
 |  | 
 | 	while ( fgets(buf, sizeof(buf), fp_in) != NULL ) { | 
 | 	    if ( fp_temp != NULL ) | 
 | 		fprintf(fp_temp, "%s", buf); | 
 | 	    if ( buf[0] != '%' || buf[1] != '%' ) { | 
 | 		if ( (buf[0] != '%' || buf[1] != '!') && atend == FALSE ) | 
 | 		    break; | 
 | 		infontlist = FALSE; | 
 | 	    } else if ( strncmp(buf, comment, strlen(comment)) == 0 ) { | 
 | 		copyfonts(buf); | 
 | 		infontlist = TRUE; | 
 | 	    } else if ( buf[2] == '+' && infontlist == TRUE ) | 
 | 		copyfonts(buf); | 
 | 	    else infontlist = FALSE; | 
 | 	}   /* End while */ | 
 |     }	/* End if */ | 
 |  | 
 |     copyinput(); | 
 |  | 
 | }   /* End of download */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | copyfonts(list) | 
 |  | 
 |     char	*list; | 
 |  | 
 | { | 
 |  | 
 |     char	*font; | 
 |     char	*path; | 
 |     int		n; | 
 |  | 
 | /* | 
 |  * | 
 |  * list points to a %%DocumentFonts: or continuation comment. What follows the | 
 |  * the keyword will be a list of fonts separated by white space (or (atend)). | 
 |  * Look for each font in the map table and if it's found copy the font file to | 
 |  * stdout (once only). | 
 |  * | 
 |  */ | 
 |  | 
 |     strtok(list, " \n");		/* skip to the font list */ | 
 |  | 
 |     while ( (font = strtok(NULL, " \t\n")) != NULL ) { | 
 | 	if ( strcmp(font, ATEND) == 0 ) { | 
 | 	    atend = TRUE; | 
 | 	    break; | 
 | 	}   /* End if */ | 
 | 	if ( (n = lookup(font)) < next ) { | 
 | 	    if ( *map[n].file != '/' ) { | 
 | 		if ( (path = (char *)malloc(strlen(hostfontdir)+strlen(map[n].file)+2)) == NULL ) | 
 | 		    error(FATAL, "no memory"); | 
 | 		sprintf(path, "%s/%s", hostfontdir, map[n].file); | 
 | 		cat(path); | 
 | 		free(path); | 
 | 	    } else cat(map[n].file); | 
 | 	    map[n].downloaded = TRUE; | 
 | 	}   /* End if */ | 
 |     }	/* End while */ | 
 |  | 
 | }   /* End of copyfonts */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | void | 
 | copyinput(void) | 
 | { | 
 |  | 
 | /* | 
 |  * | 
 |  * Copies the input file to stdout. If fp_temp isn't NULL seek to the start and | 
 |  * add it to the output file - it's a partial (or complete) copy of stdin made | 
 |  * by download(). Then copy fp_in, but only seek to the start if it's not stdin. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( fp_temp != NULL ) { | 
 | 	fseek(fp_temp, 0L, 0); | 
 | 	while ( fgets(buf, sizeof(buf), fp_temp) != NULL ) | 
 | 	    printf("%s", buf); | 
 |     }	/* End if */ | 
 |  | 
 |     if ( fp_in != stdin ) | 
 | 	fseek(fp_in, 0L, 0); | 
 |  | 
 |     while ( fgets(buf, sizeof(buf), fp_in) != NULL ) | 
 | 	printf("%s", buf); | 
 |  | 
 | }   /* End of copyinput */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | int | 
 | lookup(font) | 
 |  | 
 |     char	*font; | 
 |  | 
 | { | 
 |  | 
 |     int		i; | 
 |  | 
 | /* | 
 |  * | 
 |  * Looks for *font in the map table. Return the map table index if found and | 
 |  * not yet downloaded - otherwise return next. | 
 |  * | 
 |  */ | 
 |  | 
 |     for ( i = 0; i < next; i++ ) | 
 | 	if ( strcmp(font, map[i].font) == 0 ) { | 
 | 	    if ( map[i].downloaded == TRUE ) | 
 | 		i = next; | 
 | 	    break; | 
 | 	}   /* End if */ | 
 |  | 
 |     return(i); | 
 |  | 
 | }   /* End of lookup */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  | 
 | Map *allocate(ptr, num) | 
 |  | 
 |     Map		*ptr; | 
 |     int		num; | 
 |  | 
 | { | 
 |  | 
 | /* | 
 |  * | 
 |  * Allocates space for num Map elements. Calls malloc() if ptr is NULL and | 
 |  * realloc() otherwise. | 
 |  * | 
 |  */ | 
 |  | 
 |     if ( ptr == NULL ) | 
 | 	ptr = (Map *)malloc(num * sizeof(Map)); | 
 |     else ptr = (Map *)realloc(ptr, num * sizeof(Map)); | 
 |  | 
 |     if ( ptr == NULL ) | 
 | 	error(FATAL, "no map memory"); | 
 |  | 
 |     return(ptr); | 
 |  | 
 | }   /* End of allocate */ | 
 |  | 
 | /*****************************************************************************/ | 
 |  |