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