xref: /illumos-gate/usr/src/cmd/lp/filter/postscript/postreverse/postreverse.c (revision 4c87aefe8930bd07275b8dd2e96ea5f24d93a52e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <sys/file.h>
30 #include <sys/fcntl.h>
31 #include <sys/stat.h>
32 #include <sys/mman.h>
33 #include <string.h>
34 #include <errno.h>
35 #include "postreverse.h"
36 
37 /*
38  * This version of postreverse should parse any Adobe DSC conforming
39  * PostScript file and most that are not conforming, but minimally have the
40  * page (%%Page:) and trailer (%%Trailer) comments in them at the begining of
41  * the line.
42  *
43  * If a document cannot be parsed (no page and trailer comments), it is passed
44  * through untouched.  If you look through the code you will find that it
45  * doesn't ever look for the PostScript magic (%!).  This is because it
46  * assumes that PostScript is sent in.  If PostScript is in sent in, it will
47  * still attempt to parse it based on DSC page and trailer comments as if it
48  * were postscript.
49  *
50  * flow goes as follows:
51  *		1)  get command line options (including parsing a page
52  *			list if supplied)
53  *		2)  if no filename is supplied in command line, copy
54  *			stdin to temp file.
55  *		3)  parse the document:
56  *			start from begining looking for a DSC page comment
57  *			(that is the header) start from the end looking for
58  *			a DSC trailer comment (that is the trailer) start from
59  *			the header until the trailer looking for DSC page
60  *			comments. Each one signifies a new page.
61  *			start from the header until the trailer looking for BSD
62  *			global comments. Each one violates page independence and
63  *			will be stored so it can be printed after the header and
64  *			before any pages.
65  *		4)  print the document: if there is no header, trailer, or
66  *			pages, print it from start to end unaltered if they all
67  *			exist, print the header, pages, and trailer the pages
68  *			are compared against a page list before being printed,
69  *			and are reversed if the reverse flag has been set.
70  *			If global definitions were found in the pages of a
71  *			document, they are printed after the header and before
72  *			the pages.
73  */
74 
75 static void *
76 nmalloc(size_t size)
77 {
78 	void *ret = malloc(size);
79 
80 	if (!ret) {
81 		(void) fprintf(stderr,
82 			"postreverse : malloc() failed : Out of memory\n");
83 		exit(2);
84 	}
85 	return (ret);
86 }
87 
88 static void *
89 nrealloc(void *ptr, size_t size)
90 {
91 	void *ret = realloc(ptr, size);
92 
93 	if (!ret) {
94 		(void) fprintf(stderr,
95 			"postreverse : realloc() failed - Out of memory\n");
96 		exit(2);
97 	}
98 	return (ret);
99 }
100 
101 /*
102  * nstrlen() provides the same functionality as strlen() while also checking
103  * that the pointer does not cross the end of file.
104  *
105  * Returns the number of non-NULL bytes in string argument.
106  */
107 
108 static size_t
109 nstrlen(const char *s, char *bptr)
110 {
111 	const char *s0 = s;
112 
113 	while (s < bptr && *s != '\0')
114 		s++;
115 	return (s - s0);
116 }
117 
118 /*
119  * nstrstr() provides the same functionality as strstr() while also checking
120  * that the pointers do not cross the end of the file.
121  *
122  * nstrstr() locates the first occurrence in the string as1 of the sequence of
123  * characters (excluding the terminating null character) in the string as2.
124  * nstrstr() returns a pointer to the located string, or a null pointer if
125  * the string is not found. If as2 is "", the function returns as1.
126  */
127 
128 static char *
129 nstrstr(const char *as1, const char *as2, char *bptr)
130 {
131 	const char *s1, *s2;
132 	const char *tptr;
133 	char c;
134 
135 	s1 = as1;
136 	s2 = as2;
137 
138 	if (s2 == NULL || *s2 == '\0')
139 		return ((char *)s1);
140 	c = *s2;
141 
142 	while (s1 < bptr && *s1)
143 		if (*s1++ == c) {
144 			tptr = s1;
145 			while ((s1 < bptr) &&
146 				(c = *++s2) == *s1++ && c);
147 			if (c == 0)
148 				return ((char *)tptr - 1);
149 			s1 = tptr;
150 			s2 = as2;
151 			c = *s2;
152 		}
153 	return (NULL);
154 }
155 
156 
157 /*
158  * caddr_t strrstr(caddr_t as1, caddr_t as2 char *bptr1)
159  *      return the address of the beginning of the last occruence of as2
160  *      in as1 or NULL if not found
161  */
162 caddr_t
163 strrstr(caddr_t s1, caddr_t s2, char *bptr)
164 {
165 	char *t1, *t2;
166 	char c;
167 
168 
169 	t1 = s1 + nstrlen(s1, bptr) - 1;
170 	t2 = s2 + nstrlen(s2, bptr) - 1;
171 
172 	if (t2 == NULL || *t2 == '\0')
173 		return ((char *)t1);
174 	c = *t2;
175 
176 	while (s1 <= t1)
177 		if (*t1-- == c) {
178 			while ((c = *--t2) == *t1-- && t2 > s2);
179 			if (t2 <= s2)
180 				return ((char *)t1 + 1);
181 			t2 = s2 + nstrlen(s2, bptr) - 1;
182 			c = *t2;
183 		}
184 	return (NULL);
185 }
186 
187 /*
188  * Copy stdin to a temp file and return the name
189  */
190 char *
191 StdinToFile()
192 {
193 	char *fileName = tmpnam(NULL);
194 	int fd;
195 	int count;
196 	char buf[BUFSIZ];
197 
198 	if ((fd = open(fileName, O_RDWR | O_CREAT | O_EXCL, 0600)) < 0) {
199 		fprintf(stderr, "open(%s): %s\n", fileName,
200 			strerror(errno));
201 		return (NULL);
202 	}
203 	while ((count = read(0, buf, sizeof (buf))) > 0)
204 		if (write(fd, buf, count) != count) {
205 			fprintf(stderr, "write(%d, 0x%x, %d): %s\n", fd, buf,
206 				count, strerror(errno));
207 			close(fd);
208 			unlink(fileName);
209 			return (NULL);
210 		}
211 	return (fileName);
212 }
213 
214 /*
215  * Usage(char *name) - program usage
216  */
217 void
218 Usage(char *name)
219 {
220 	fprintf(stderr, "Usage: %s [ -o list ] [ -r ] [ filename ]\n", name);
221 	exit(1);
222 }
223 
224 
225 /*
226  * int **ParsePageList(char *list)
227  *    This will parse as string #,#,#-#,#... into an array of pointers
228  *  to integers.  This array will contain all numbers in the list including
229  *  those int the range #-#.  The list returned is NULL terminated.
230  *  It uses 2 passes to build the list.  pass 1 counts the # of ints and
231  *  allocates the space, and pass 2 fills in the list.
232  */
233 int **
234 ParsePageList(char *list)
235 {
236 	int **pageList = NULL;
237 	int pass = 0;
238 
239 	if (list == NULL)
240 		return (NULL);
241 
242 	while (pass++ < 2) {
243 		char *page;
244 		char *tmplist;
245 		int size = 0;
246 
247 		tmplist = strdup(list);
248 		page = strtok(tmplist, ",");
249 
250 		do {
251 			int start, end;
252 			char *s1 = page, *s2;
253 
254 			if (s2 = strchr(page, '-')) {
255 				*s2++ = '\0';
256 				start = atoi(s1);
257 				end = atoi(s2);
258 				if (end < start) {
259 					int tmp = end;
260 
261 					end = start;
262 					start = tmp;
263 				}
264 			} else
265 				start = end = atoi(s1);
266 
267 			while (start <= end)
268 				if (pass == 1)
269 				/* count the pages for allocation */
270 					size++, start++;
271 				else {	/* fill in the page list */
272 					int *tmp = (int *)nmalloc(sizeof (int));
273 					*tmp = start++;
274 					pageList[size++] = tmp;
275 				}
276 		} while (page = strtok(NULL, ","));
277 		free(tmplist);
278 		if (pass == 1)
279 			pageList = (int **)calloc(sizeof (int *), (size + 1));
280 	}
281 	return (pageList);
282 }
283 
284 
285 /*
286  * int PageIsListed(int page, int **pageList)
287  *    returns 1 if the pagelist is empty or if the page is in the
288  *  NULL terminated pageList.  returns 0 if the page is not listed
289  */
290 int
291 PageIsListed(int page, int **pageList)
292 {
293 	int count = 0;
294 
295 	if (!pageList)
296 		return (1);
297 
298 	for (count = 0; pageList[count] != NULL; count++)
299 		if (*pageList[count] == page)
300 			return (1);
301 	return (0);
302 }
303 
304 
305 /*
306  * Writes the document Header to the fd
307  */
308 int
309 WriteDocumentHeader(int fd, DOCUMENT * d)
310 {
311 	if (d) {
312 		HEADER *h = d->header;
313 
314 		if (h)
315 			return (write(fd, h->start, h->size));
316 	}
317 	errno = EINVAL;
318 	return (-1);
319 }
320 
321 /*
322  * Writes the document global block to the fd
323  */
324 int
325 WriteGlobal(int fd, GLOBAL * g)
326 {
327 	if (g)
328 		return (write(fd, g->start, g->size));
329 	errno = EINVAL;
330 	return (-1);
331 }
332 
333 /*
334  * Writes the document Trailer to the fd
335  */
336 int
337 WriteDocumentTrailer(int fd, DOCUMENT * d)
338 {
339 	if (d) {
340 		TRAILER *t = d->trailer;
341 
342 		if (t)
343 			return (write(fd, t->start, t->size));
344 	}
345 	errno = EINVAL;
346 	return (-1);
347 }
348 
349 /*
350  * Writes the document page to the fd
351  */
352 int
353 WritePage(int fd, PAGE * p, int global, char *bptr)
354 {
355 	if (p) {
356 		caddr_t ptr1;
357 
358 		if (((ptr1 = nstrstr(p->start, PS_BEGIN_GLOBAL, bptr))
359 			!= NULL) && (ptr1 < p->start + p->size) &&
360 			    (global != 0)) {
361 			/* BeginGlobal/EndGlobal in the page... */
362 			write(fd, p->start, ptr1 - p->start);
363 			ptr1 = nstrstr(ptr1, PS_END_GLOBAL, bptr);
364 			ptr1 += nstrlen(PS_END_GLOBAL, bptr);
365 			return (write(fd, ptr1, (p->size - (ptr1 - p->start))));
366 		} else
367 			return (write(fd, p->start, p->size));
368 	}
369 	errno = EINVAL;
370 	return (-1);
371 }
372 
373 /*
374  * Writes out the document pages in pageList (or all if NULL) and reverse
375  * the output if reverse == 1
376  */
377 void
378 WriteDocument(DOCUMENT * document, int reverse, int **pageList)
379 {
380 	int count = 0;
381 	int prnindex;
382 
383 	if (document->header && document->trailer && document->page) {
384 		WriteDocumentHeader(1, document);
385 
386 		if (document->global != NULL) {
387 			while (document->global[count] != NULL) {
388 				GLOBAL *global = document->global[count++];
389 
390 				if (global)
391 					WriteGlobal(1, global);
392 			}
393 		}
394 		count = reverse ? (document->pages-1) : 0;
395 
396 		for (prnindex = 0; prnindex < document->pages; prnindex++) {
397 			PAGE *page = document->page[count];
398 
399 			if (page && PageIsListed(page->number, pageList))
400 				WritePage(1, page, document->global != NULL,
401 					document->start + document->size);
402 
403 			count = reverse ? count - 1 : count + 1;
404 		}
405 
406 		WriteDocumentTrailer(1, document);
407 	} else {
408 		write(1, document->start, document->size);
409 	}
410 }
411 
412 /*
413  * get a document header from document and return a pointer to a HEADER
414  * structure.
415  */
416 HEADER *
417 DocumentHeader(DOCUMENT * document)
418 {
419 	HEADER *header;
420 	caddr_t start;
421 
422 	header = (HEADER *) nmalloc(sizeof (*header));
423 	memset(header, 0, sizeof (*header));
424 	if (start = nstrstr(document->start, PS_PAGE,
425 			    document->start + document->size)) {
426 		header->label = "Document Header";
427 		header->start = document->start;
428 		header->size = (start - document->start + 1);
429 	} else {
430 		free(header);
431 		header = NULL;
432 	}
433 	return (header);
434 }
435 
436 
437 /*
438  * get a document trailer from document and return a pointer to a trailer
439  * structure.
440  */
441 TRAILER *
442 DocumentTrailer(DOCUMENT * document)
443 {
444 	TRAILER *trailer;
445 
446 	trailer = (TRAILER *) nmalloc(sizeof (*trailer));
447 	memset(trailer, 0, sizeof (trailer));
448 	if (trailer->start = strrstr(document->start, PS_TRAILER,
449 		document->start + document->size)) {
450 		trailer->label = "Document Trailer";
451 		trailer->start += 1;
452 		trailer->size = nstrlen(trailer->start,
453 			document->start + document->size);
454 	} else {
455 		free(trailer);
456 		trailer = NULL;
457 	}
458 	return (trailer);
459 }
460 
461 GLOBAL **
462 DocumentGlobals(DOCUMENT * document)
463 {
464 	GLOBAL **globals = NULL, *global;
465 	caddr_t start, ptr1;
466 	int count = 0;
467 	char *bptr = document->start + document->size;
468 	long allocated_slots = 0;
469 	caddr_t global_end;
470 
471 	start = nstrstr(document->start, PS_PAGE, bptr);
472 	if (start != NULL) {
473 		for (ptr1 = nstrstr(start, PS_BEGIN_GLOBAL, bptr); ptr1 != NULL;
474 			ptr1 = nstrstr(++ptr1, PS_BEGIN_GLOBAL, bptr)) {
475 			count++;
476 
477 			global = (GLOBAL *) nmalloc(sizeof (GLOBAL));
478 			if ((global_end = nstrstr(++ptr1, PS_END_GLOBAL, bptr))
479 				== NULL) {
480 				fprintf(stderr,
481 					"DSC violation: %%%%BeginGlobal "
482 						"with no %%%%EndGlobal\n");
483 				exit(-1);
484 			}
485 			memset(global, 0, sizeof (GLOBAL));
486 			global->start = ptr1;
487 			global->size = strchr(++global_end, '\n') - ptr1 + 1;
488 
489 			if (count > allocated_slots) {
490 				globals = (GLOBAL **) nrealloc(globals,
491 					(allocated_slots + BLOCKSIZE) *
492 						sizeof (GLOBAL *));
493 				memset(globals +
494 					allocated_slots * sizeof (GLOBAL *), 0,
495 						BLOCKSIZE *
496 							sizeof (GLOBAL *));
497 				allocated_slots += BLOCKSIZE;
498 			}
499 
500 			globals[count - 1] = global;
501 			ptr1 = global->start + global->size;
502 		}
503 	}
504 	return (globals);
505 }
506 
507 
508 /*
509  * get the pages from a document and return a pointer a list of PAGE
510  * structures.
511  */
512 PAGE **
513 DocumentPages(DOCUMENT * document)
514 {
515 	PAGE **pages = NULL, *page;
516 	caddr_t ptr1, page_end;
517 	char *bptr = document->start + document->size;
518 	long allocated_slots = 0;
519 	long no_pages = 0;
520 	long number;
521 	char *label, *tmp, *tmp_end;
522 
523 	for (ptr1 = nstrstr(document->start, PS_PAGE, bptr); ptr1 != NULL;
524 	    ptr1 = nstrstr(++ptr1, PS_PAGE, bptr)) {
525 		no_pages++;
526 
527 		if (no_pages > allocated_slots) {
528 			pages = (PAGE **) nrealloc(pages,
529 			    (allocated_slots + BLOCKSIZE) * sizeof (PAGE *));
530 			memset(pages + allocated_slots, 0,
531 			    BLOCKSIZE * sizeof (PAGE *));
532 			allocated_slots += BLOCKSIZE;
533 		}
534 		page = (PAGE *) nmalloc(sizeof (PAGE));
535 		label = NULL;
536 		number = -1;
537 
538 		/* page start & end */
539 		if ((page_end = nstrstr(++ptr1, PS_PAGE, bptr)) == NULL)
540 			if (document->trailer)
541 				page_end = document->trailer->start - 1;
542 			else
543 				page_end = document->start + document->size;
544 
545 		/* page label & number */
546 		if (tmp = strchr(ptr1, ' ')) {
547 
548 			if (tmp_end = strchr(++tmp, ' ')) {
549 				label = (char *)nmalloc((tmp_end - tmp) + 1);
550 				memset(label, 0, (tmp_end - tmp) + 1);
551 				strncpy(label, tmp, (tmp_end - tmp));
552 				number = atol(++tmp_end);
553 			}
554 		}
555 		memset(page, 0, sizeof (PAGE));
556 		page->label = label;
557 		page->number = number;
558 		page->start = ptr1;
559 		page->size = page_end - ptr1 + 1;
560 
561 		pages[document->pages++] = page;
562 	}
563 	return (pages);
564 }
565 
566 /*
567  * parse a document and return a pointer to a DOCUMENT structure
568  */
569 DOCUMENT *
570 DocumentParse(char *name)
571 {
572 	DOCUMENT *document = NULL;
573 	int fd;
574 	struct stat st;
575 
576 	if (stat(name, &st) < 0) {
577 		fprintf(stderr, "stat(%s): %s\n", name, strerror(errno));
578 		return (NULL);
579 	}
580 	if (st.st_size == 0) {
581 		fprintf(stderr, "%s: empty file\n", name);
582 		return (NULL);
583 	}
584 	if ((fd = open(name, O_RDONLY)) < 0) {
585 		fprintf(stderr, "open(%s, O_RDONLY): %s\n", name,
586 			strerror(errno));
587 		return (NULL);
588 	}
589 	document = (DOCUMENT *) nmalloc(sizeof (DOCUMENT));
590 	memset(document, 0, sizeof (DOCUMENT));
591 	if ((document->start = mmap((void *)0, (size_t)st.st_size, PROT_READ,
592 		MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
593 		fprintf(stderr, "mmap(0, %ld, PROT_READ,"
594 			" MAP_SHARED, %d, 0): %s\n",
595 				st.st_size, fd, strerror(errno));
596 		free(document);
597 		document = NULL;
598 	} else {
599 		/* order in important */
600 		document->name = strdup(name);
601 		document->size = nstrlen(document->start,
602 			document->start + st.st_size);
603 		document->header = DocumentHeader(document);
604 		document->trailer = DocumentTrailer(document);
605 		document->page = DocumentPages(document);
606 		document->global = DocumentGlobals(document);
607 	}
608 	close(fd);
609 	return (document);
610 }
611 
612 
613 #if defined(DEBUG)
614 /*
615  * Print out the contents of the document structure
616  */
617 void
618 PrintDocumentInfo(DOCUMENT * d)
619 {
620 	if (d) {
621 		printf("Document:\n\tname:  %s\n\tstart: 0x%x\n\tsize:  %ld\n",
622 			d->name, d->start, d->size);
623 		if (d->header) {
624 			HEADER *h = d->header;
625 
626 			printf("\tHeader: %s (0x%x, %ld)\n",
627 				h->label, h->start, h->size);
628 		}
629 		if (d->global) {
630 			int count = 0;
631 
632 			while (d->global[count++] != NULL);
633 			printf("\tDSC violating BeginGlobals: %d\n", count);
634 		}
635 		if (d->page) {
636 			PAGE *p;
637 			int count = 0;
638 
639 			printf("\tPages: (%d)\n", d->pages);
640 			for (p = d->page[0]; p != NULL; p = d->page[++count])
641 				printf("\t\t %4d (%s) - (0x%x, %ld)\n",
642 					p->number,
643 						(p->label ? p->label : "Page"),
644 							p->start, p->size);
645 		}
646 		if (d->trailer) {
647 			TRAILER *t = d->trailer;
648 
649 			printf("\tTrailer: %s (0x%x, %ld)\n",
650 				t->label, t->start, t->size);
651 		}
652 	}
653 }
654 #endif				/* DEBUG */
655 
656 
657 int
658 main(int ac, char *av[])
659 {
660 	DOCUMENT *document;
661 	char *fileName = NULL;
662 	char *programName = NULL;
663 	char *unlinkFile = NULL;
664 	int reversePages = 1;
665 	int **pageList = NULL;
666 	int option;
667 
668 	if (programName = strrchr(av[0], '/'))
669 		programName++;
670 	else
671 		programName = av[0];
672 
673 	while ((option = getopt(ac, av, "o:r")) != EOF)
674 		switch (option) {
675 		case 'o':
676 			pageList = ParsePageList(optarg);
677 			break;
678 		case 'r':
679 			reversePages = 0;
680 			break;
681 		case '?':
682 			Usage(programName);
683 			break;
684 		default:
685 			fprintf(stderr, "missing case for option %c\n", option);
686 			Usage(programName);
687 			break;
688 		}
689 
690 	ac -= optind;
691 	av += optind;
692 
693 	switch (ac) {
694 	case 0:
695 		unlinkFile = fileName = StdinToFile();
696 		break;
697 	case 1:
698 		fileName = av[0];
699 		break;
700 	default:
701 		Usage(programName);
702 	}
703 
704 	if ((document = DocumentParse(fileName)) == NULL) {
705 		fprintf(stderr, "Unable to parse document (%s)\n", fileName);
706 		exit(0);
707 	}
708 #if defined(DEBUG) && defined(NOTDEF)
709 	PrintDocumentInfo(document);
710 #endif				/* DEBUG */
711 
712 	WriteDocument(document, reversePages, pageList);
713 
714 	if (unlinkFile)
715 		unlink(unlinkFile);
716 
717 	return (0);
718 }
719