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