makewhatis.c revision 1.18 1 /* $NetBSD: makewhatis.c,v 1.18 2002/01/11 18:33:03 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Matthias Scheler.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __COPYRIGHT("@(#) Copyright (c) 1999 The NetBSD Foundation, Inc.\n\
42 All rights reserved.\n");
43 #endif /* not lint */
44
45 #ifndef lint
46 __RCSID("$NetBSD: makewhatis.c,v 1.18 2002/01/11 18:33:03 christos Exp $");
47 #endif /* not lint */
48
49 #include <sys/types.h>
50 #include <sys/param.h>
51 #include <sys/stat.h>
52 #include <sys/wait.h>
53
54 #include <ctype.h>
55 #include <err.h>
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <fts.h>
59 #include <locale.h>
60 #include <paths.h>
61 #include <signal.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <unistd.h>
66 #include <zlib.h>
67
68 typedef struct manpagestruct manpage;
69 struct manpagestruct {
70 manpage *mp_left,*mp_right;
71 ino_t mp_inode;
72 char mp_name[1];
73 };
74
75 typedef struct whatisstruct whatis;
76 struct whatisstruct {
77 whatis *wi_left,*wi_right;
78 char *wi_data;
79 };
80
81 int main(int, char **);
82 char *findwhitespace(char *);
83 char *strmove(char *,char *);
84 char *GetS(gzFile, char *, size_t);
85 int manpagesection(char *);
86 char *createsectionstring(char *);
87 void addmanpage(manpage **, ino_t, char *);
88 void addwhatis(whatis **, char *);
89 char *replacestring(char *, char *, char *);
90 void catpreprocess(char *);
91 char *parsecatpage(gzFile *);
92 int manpreprocess(char *);
93 char *nroff(gzFile *);
94 char *parsemanpage(gzFile *, int);
95 char *getwhatisdata(char *);
96 void processmanpages(manpage **,whatis **);
97 void dumpwhatis(FILE *, whatis *);
98 void *emalloc(size_t);
99 char *estrdup(const char *);
100
101 char *default_manpath[] = {
102 "/usr/share/man",
103 NULL
104 };
105
106 char sectionext[] = "0123456789ln";
107 char whatisdb[] = "whatis.db";
108
109 int
110 main(int argc, char **argv)
111 {
112 char **manpath;
113 FTS *fts;
114 FTSENT *fe;
115 manpage *source;
116 whatis *dest;
117 FILE *out;
118
119 (void)setlocale(LC_ALL, "");
120
121 manpath = (argc < 2) ? default_manpath : &argv[1];
122
123 if ((fts = fts_open(manpath, FTS_LOGICAL, NULL)) == NULL)
124 err(EXIT_FAILURE, "Cannot open `%s'", *manpath);
125
126 source = NULL;
127 while ((fe = fts_read(fts)) != NULL) {
128 switch (fe->fts_info) {
129 case FTS_F:
130 if (manpagesection(fe->fts_path) >= 0)
131 addmanpage(&source, fe->fts_statp->st_ino,
132 fe->fts_path);
133 /*FALLTHROUGH*/
134 case FTS_D:
135 case FTS_DC:
136 case FTS_DEFAULT:
137 case FTS_DP:
138 case FTS_SLNONE:
139 break;
140 default:
141 errno = fe->fts_errno;
142 err(EXIT_FAILURE, "Error reading `%s'", fe->fts_path);
143 }
144 }
145
146 (void)fts_close(fts);
147
148 dest = NULL;
149 processmanpages(&source, &dest);
150
151 if (chdir(manpath[0]) == -1)
152 err(EXIT_FAILURE, "Cannot change dir to `%s'", manpath[0]);
153
154 (void)unlink(whatisdb);
155 if ((out = fopen(whatisdb, "w")) == NULL)
156 err(EXIT_FAILURE, "Cannot open `%s'", whatisdb);
157
158 dumpwhatis(out, dest);
159 if (fchmod(fileno(out), S_IRUSR|S_IRGRP|S_IROTH) == -1)
160 err(EXIT_FAILURE, "Cannot chmod `%s'", whatisdb);
161 if (fclose(out) != 0)
162 err(EXIT_FAILURE, "Cannot close `%s'", whatisdb);
163
164 return EXIT_SUCCESS;
165 }
166
167 char *
168 findwhitespace(char *str)
169 {
170 while (!isspace((unsigned char)*str))
171 if (*str++ == '\0') {
172 str = NULL;
173 break;
174 }
175
176 return str;
177 }
178
179 char
180 *strmove(char *dest,char *src)
181 {
182 return memmove(dest, src, strlen(src) + 1);
183 }
184
185 char *
186 GetS(gzFile in, char *buffer, size_t length)
187 {
188 char *ptr;
189
190 if (((ptr = gzgets(in, buffer, (int)length)) != NULL) && (*ptr == '\0'))
191 ptr = NULL;
192
193 return ptr;
194 }
195
196 int
197 manpagesection(char *name)
198 {
199 char *ptr;
200
201 if ((ptr = strrchr(name, '/')) != NULL)
202 ptr++;
203 else
204 ptr = name;
205
206 while ((ptr = strchr(ptr, '.')) != NULL) {
207 int section;
208
209 ptr++;
210 section=0;
211 while (sectionext[section] != '\0')
212 if (sectionext[section] == *ptr)
213 return section;
214 else
215 section++;
216 }
217
218 return -1;
219 }
220
221 char *
222 createsectionstring(char *section_id)
223 {
224 char *section = emalloc(strlen(section_id) + 7);
225 section[0] = ' ';
226 section[1] = '(';
227 (void)strcat(strcpy(§ion[2], section_id), ") - ");
228 return section;
229 }
230
231 void
232 addmanpage(manpage **tree,ino_t inode,char *name)
233 {
234 manpage *mp;
235
236 while ((mp = *tree) != NULL) {
237 if (mp->mp_inode == inode)
238 return;
239 tree = inode < mp->mp_inode ? &mp->mp_left : &mp->mp_right;
240 }
241
242 mp = emalloc(sizeof(manpage) + strlen(name));
243 mp->mp_left = NULL;
244 mp->mp_right = NULL;
245 mp->mp_inode = inode;
246 (void)strcpy(mp->mp_name, name);
247 *tree = mp;
248 }
249
250 void
251 addwhatis(whatis **tree, char *data)
252 {
253 whatis *wi;
254 int result;
255
256 while (isspace((unsigned char)*data))
257 data++;
258
259 if (*data == '/') {
260 char *ptr;
261
262 ptr = ++data;
263 while ((*ptr != '\0') && !isspace((unsigned char)*ptr))
264 if (*ptr++ == '/')
265 data = ptr;
266 }
267
268 while ((wi = *tree) != NULL) {
269 result = strcmp(data, wi->wi_data);
270 if (result == 0) return;
271 tree = result < 0 ? &wi->wi_left : &wi->wi_right;
272 }
273
274 wi = emalloc(sizeof(whatis) + strlen(data));
275
276 wi->wi_left = NULL;
277 wi->wi_right = NULL;
278 wi->wi_data = data;
279 *tree = wi;
280 }
281
282 void
283 catpreprocess(char *from)
284 {
285 char *to;
286
287 to = from;
288 while (isspace((unsigned char)*from)) from++;
289
290 while (*from != '\0')
291 if (isspace((unsigned char)*from)) {
292 while (isspace((unsigned char)*++from));
293 if (*from != '\0')
294 *to++ = ' ';
295 }
296 else if (*(from + 1) == '\10')
297 from += 2;
298 else
299 *to++ = *from++;
300
301 *to = '\0';
302 }
303
304 char *
305 replacestring(char *string, char *old, char *new)
306
307 {
308 char *ptr, *result;
309 size_t slength, olength, nlength, pos;
310
311 if (new == NULL)
312 return estrdup(string);
313
314 ptr = strstr(string, old);
315 if (ptr == NULL)
316 return estrdup(string);
317
318 slength = strlen(string);
319 olength = strlen(old);
320 nlength = strlen(new);
321 result = emalloc(slength - olength + nlength + 1);
322
323 pos = ptr - string;
324 (void)memcpy(result, string, pos);
325 (void)memcpy(&result[pos], new, nlength);
326 (void)strcpy(&result[pos + nlength], &string[pos + olength]);
327
328 return result;
329 }
330
331 char *
332 parsecatpage(gzFile *in)
333 {
334 char buffer[8192];
335 char *section, *ptr, *last;
336 size_t size;
337
338 do {
339 if (GetS(in, buffer, sizeof(buffer)) == NULL)
340 return NULL;
341 }
342 while (buffer[0] == '\n');
343
344 section = NULL;
345 if ((ptr = strchr(buffer, '(')) != NULL) {
346 if ((last = strchr(ptr + 1, ')')) !=NULL) {
347 size_t length;
348
349 length = last - ptr + 1;
350 section = emalloc(length + 5);
351 *section = ' ';
352 (void) memcpy(section + 1, ptr, length);
353 (void) strcpy(section + 1 + length, " - ");
354 }
355 }
356
357 for (;;) {
358 if (GetS(in, buffer, sizeof(buffer)) == NULL) {
359 free(section);
360 return NULL;
361 }
362 catpreprocess(buffer);
363 if (strncmp(buffer, "NAME", 4) == 0)
364 break;
365 }
366
367 ptr = last = buffer;
368 size = sizeof(buffer) - 1;
369 while ((size > 0) && (GetS(in, ptr, size) != NULL)) {
370 int length;
371
372 catpreprocess(ptr);
373
374 length = strlen(ptr);
375 if (length == 0) {
376 *last = '\0';
377
378 ptr = replacestring(buffer, " - ", section);
379 free(section);
380 return ptr;
381 }
382 if ((length > 1) && (ptr[length - 1] == '-') &&
383 isalpha(ptr[length - 2]))
384 last = &ptr[--length];
385 else {
386 last = &ptr[length++];
387 *last = ' ';
388 }
389
390 ptr += length;
391 size -= length;
392 }
393
394 free(section);
395
396 return NULL;
397 }
398
399 int
400 manpreprocess(char *line)
401 {
402 char *from, *to;
403
404 to = from = line;
405 while (isspace((unsigned char)*from)) from++;
406 if (strncmp(from, ".\\\"", 3) == 0)
407 return 1;
408
409 while (*from != '\0')
410 if (isspace((unsigned char)*from)) {
411 while (isspace((unsigned char)*++from));
412 if ((*from != '\0') && (*from != ','))
413 *to++ = ' ';
414 }
415 else if (*from == '\\')
416 switch (*++from) {
417 case '\0':
418 case '-':
419 break;
420 case 'f':
421 case 's':
422 from++;
423 if ((*from=='+') || (*from=='-'))
424 from++;
425 while (isdigit(*from))
426 from++;
427 break;
428 default:
429 from++;
430 }
431 else
432 if (*from == '"')
433 from++;
434 else
435 *to++ = *from++;
436
437 *to = '\0';
438
439 if (strncasecmp(line, ".Xr", 3) == 0) {
440 char *sect;
441
442 from = line + 3;
443 if (isspace((unsigned char)*from))
444 from++;
445
446 if ((sect = findwhitespace(from)) != NULL) {
447 size_t length;
448
449 *sect++ = '\0';
450 length = strlen(from);
451 (void) memmove(line, from, length);
452 line[length++] = '(';
453 to = &line[length];
454 length = strlen(sect);
455 (void) memmove(to, sect, length);
456 (void) strcpy(&to[length], ")");
457 }
458 }
459
460 return 0;
461 }
462
463 char *
464 nroff(gzFile *in)
465 {
466 char tempname[MAXPATHLEN], buffer[65536], *data;
467 int tempfd, bytes, pipefd[2], status;
468 static int devnull = -1;
469 pid_t child;
470
471 if (gzrewind(in) < 0)
472 err(EXIT_FAILURE, "Cannot rewind pipe");
473
474 if ((devnull < 0) &&
475 ((devnull = open(_PATH_DEVNULL, O_WRONLY, 0)) < 0))
476 err(EXIT_FAILURE, "Cannot open `/dev/null'");
477
478 (void)strcpy(tempname, _PATH_TMP "makewhatis.XXXXXX");
479 if ((tempfd = mkstemp(tempname)) == -1)
480 err(EXIT_FAILURE, "Cannot create temp file");
481
482 while ((bytes = gzread(in, buffer, sizeof(buffer))) > 0)
483 if (write(tempfd, buffer, (size_t)bytes) != bytes) {
484 bytes = -1;
485 break;
486 }
487
488 if (bytes < 0) {
489 (void)close(tempfd);
490 (void)unlink(tempname);
491 err(EXIT_FAILURE, "Read from pipe failed");
492 }
493 if (lseek(tempfd, (off_t)0, SEEK_SET) == (off_t)-1) {
494 (void)close(tempfd);
495 (void)unlink(tempname);
496 err(EXIT_FAILURE, "Cannot rewind temp file");
497 }
498 if (pipe(pipefd) == -1) {
499 (void)close(tempfd);
500 (void)unlink(tempname);
501 err(EXIT_FAILURE, "Cannot create pipe");
502 }
503
504 switch (child = vfork()) {
505 case -1:
506 (void)close(pipefd[1]);
507 (void)close(pipefd[0]);
508 (void)close(tempfd);
509 (void)unlink(tempname);
510 err(EXIT_FAILURE, "Fork failed");
511 /* NOTREACHED */
512 case 0:
513 (void)close(pipefd[0]);
514 if (tempfd != STDIN_FILENO) {
515 (void)dup2(tempfd, STDIN_FILENO);
516 (void)close(tempfd);
517 }
518 if (pipefd[1] != STDOUT_FILENO) {
519 (void)dup2(pipefd[1], STDOUT_FILENO);
520 (void)close(pipefd[1]);
521 }
522 if (devnull != STDERR_FILENO) {
523 (void)dup2(devnull, STDERR_FILENO);
524 (void)close(devnull);
525 }
526 (void)execlp("nroff", "nroff", "-S", "-man", NULL);
527 _exit(EXIT_FAILURE);
528 /*NOTREACHED*/
529 default:
530 (void)close(pipefd[1]);
531 (void)close(tempfd);
532 break;
533 }
534
535 if ((in = gzdopen(pipefd[0], "r")) == NULL) {
536 if (errno == 0)
537 errno = ENOMEM;
538 (void)close(pipefd[0]);
539 (void)kill(child, SIGTERM);
540 while (waitpid(child, NULL, 0) != child);
541 (void)unlink(tempname);
542 err(EXIT_FAILURE, "Cannot read from pipe");
543 }
544
545 data = parsecatpage(in);
546 while (gzread(in, buffer, sizeof(buffer)) > 0);
547 (void)gzclose(in);
548
549 while (waitpid(child, &status, 0) != child);
550 if ((data != NULL) &&
551 !(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) {
552 free(data);
553 errx(EXIT_FAILURE, "nroff exited with %d status",
554 WEXITSTATUS(status));
555 }
556
557 (void)unlink(tempname);
558 return data;
559 }
560
561 char *
562 parsemanpage(gzFile *in, int defaultsection)
563 {
564 char *section, buffer[8192], *ptr;
565
566 section = NULL;
567 do {
568 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) {
569 free(section);
570 return NULL;
571 }
572 if (manpreprocess(buffer))
573 continue;
574 if (strncasecmp(buffer, ".Dt", 3) == 0) {
575 char *end;
576
577 ptr = &buffer[3];
578 if (isspace((unsigned char)*ptr))
579 ptr++;
580 if ((ptr = findwhitespace(ptr)) == NULL)
581 continue;
582
583 if ((end = findwhitespace(++ptr)) != NULL)
584 *end = '\0';
585
586 free(section);
587 section = createsectionstring(ptr);
588 }
589 else if (strncasecmp(buffer, ".TH", 3) == 0) {
590 ptr = &buffer[3];
591 while (isspace((unsigned char)*ptr))
592 ptr++;
593 if ((ptr = findwhitespace(ptr)) != NULL) {
594 char *next;
595
596 while (isspace((unsigned char)*ptr))
597 ptr++;
598 if ((next = findwhitespace(ptr)) != NULL)
599 *next = '\0';
600 free(section);
601 section = createsectionstring(ptr);
602 }
603 }
604 else if (strncasecmp(buffer, ".Ds", 3) == 0) {
605 free(section);
606 return NULL;
607 }
608 } while (strncasecmp(buffer, ".Sh NAME", 8) != 0);
609
610 do {
611 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) {
612 free(section);
613 return NULL;
614 }
615 } while (manpreprocess(buffer));
616
617 if (strncasecmp(buffer, ".Nm", 3) == 0) {
618 size_t length, offset;
619
620 ptr = &buffer[3];
621 while (isspace((unsigned char)*ptr))
622 ptr++;
623
624 length = strlen(ptr);
625 if ((length > 1) && (ptr[length - 1] == ',') &&
626 isspace((unsigned char)ptr[length - 2])) {
627 ptr[--length] = '\0';
628 ptr[length - 1] = ',';
629 }
630 (void) memmove(buffer, ptr, length + 1);
631
632 offset = length + 3;
633 ptr = &buffer[offset];
634 for (;;) {
635 size_t more;
636
637 if ((sizeof(buffer) == offset) ||
638 (GetS(in, ptr, sizeof(buffer) - offset)
639 == NULL)) {
640 free(section);
641 return NULL;
642 }
643 if (manpreprocess(ptr))
644 continue;
645
646 if (strncasecmp(ptr, ".Nm", 3) != 0) break;
647
648 ptr += 3;
649 if (isspace((unsigned char)*ptr))
650 ptr++;
651
652 buffer[length++] = ' ';
653 more = strlen(ptr);
654 if ((more > 1) && (ptr[more - 1] == ',') &&
655 isspace((unsigned char)ptr[more - 2])) {
656 ptr[--more] = '\0';
657 ptr[more - 1] = ',';
658 }
659
660 (void) memmove(&buffer[length], ptr, more + 1);
661 length += more;
662 offset = length + 3;
663
664 ptr = &buffer[offset];
665 }
666
667 if (strncasecmp(ptr, ".Nd", 3) == 0) {
668 (void) strcpy(&buffer[length], " -");
669
670 while (strncasecmp(ptr, ".Sh", 3) != 0) {
671 int more;
672
673 if (*ptr == '.') {
674 char *space;
675
676 if (strncasecmp(ptr, ".Nd", 3) != 0) {
677 free(section);
678 return NULL;
679 }
680 space = findwhitespace(ptr);
681 if (space == NULL)
682 ptr = "";
683 else {
684 space++;
685 (void) strmove(ptr, space);
686 }
687 }
688
689 if (*ptr != '\0') {
690 buffer[offset - 1] = ' ';
691 more = strlen(ptr) + 1;
692 offset += more;
693 }
694 ptr = &buffer[offset];
695 if ((sizeof(buffer) == offset) ||
696 (GetS(in, ptr, sizeof(buffer) - offset)
697 == NULL)) {
698 free(section);
699 return NULL;
700 }
701 if (manpreprocess(ptr))
702 *ptr = '\0';
703 }
704 }
705 }
706 else {
707 int offset;
708
709 if (*buffer == '.') {
710 char *space;
711
712 if ((space = findwhitespace(&buffer[1])) == NULL) {
713 free(section);
714 return NULL;
715 }
716 space++;
717 (void) strmove(buffer, space);
718 }
719
720 offset = strlen(buffer) + 1;
721 for (;;) {
722 int more;
723
724 ptr = &buffer[offset];
725 if ((sizeof(buffer) == offset) ||
726 (GetS(in, ptr, sizeof(buffer) - offset)
727 == NULL)) {
728 free(section);
729 return NULL;
730 }
731 if (manpreprocess(ptr) || (*ptr == '\0'))
732 continue;
733
734 if ((strncasecmp(ptr, ".Sh", 3) == 0) ||
735 (strncasecmp(ptr, ".Ss", 3) == 0))
736 break;
737
738 if (*ptr == '.') {
739 char *space;
740
741 if ((space = findwhitespace(ptr)) == NULL) {
742 continue;
743 }
744
745 space++;
746 (void) memmove(ptr, space, strlen(space) + 1);
747 }
748
749 buffer[offset - 1] = ' ';
750 more = strlen(ptr);
751 if ((more > 1) && (ptr[more - 1] == ',') &&
752 isspace((unsigned char)ptr[more - 2])) {
753 ptr[more - 1] = '\0';
754 ptr[more - 2] = ',';
755 }
756 else more++;
757 offset += more;
758 }
759 }
760
761 if (section == NULL) {
762 char sectionbuffer[24];
763
764 (void) sprintf(sectionbuffer, " (%c) - ",
765 sectionext[defaultsection]);
766 ptr = replacestring(buffer, " - ", sectionbuffer);
767 }
768 else {
769 ptr = replacestring(buffer, " - ", section);
770 free(section);
771 }
772 return ptr;
773 }
774
775 char *
776 getwhatisdata(char *name)
777 {
778 gzFile *in;
779 char *data;
780 int section;
781
782 if ((in = gzopen(name, "r")) == NULL) {
783 if (errno == 0)
784 errno = ENOMEM;
785 err(EXIT_FAILURE, "Cannot open `%s'", name);
786 /* NOTREACHED */
787 }
788
789 section = manpagesection(name);
790 if (section == 0)
791 data = parsecatpage(in);
792 else {
793 data = parsemanpage(in, section);
794 if (data == NULL)
795 data = nroff(in);
796 }
797
798 (void) gzclose(in);
799 return data;
800 }
801
802 void
803 processmanpages(manpage **source, whatis **dest)
804 {
805 manpage *mp;
806
807 mp = *source;
808 *source = NULL;
809
810 while (mp != NULL) {
811 manpage *obsolete;
812 char *data;
813
814 if (mp->mp_left != NULL)
815 processmanpages(&mp->mp_left,dest);
816
817 if ((data = getwhatisdata(mp->mp_name)) != NULL)
818 addwhatis(dest,data);
819
820 obsolete = mp;
821 mp = mp->mp_right;
822 free(obsolete);
823 }
824 }
825
826 void
827 dumpwhatis(FILE *out, whatis *tree)
828 {
829 while (tree != NULL) {
830 if (tree->wi_left)
831 dumpwhatis(out, tree->wi_left);
832
833 if ((fputs(tree->wi_data, out) == EOF) ||
834 (fputc('\n', out) == EOF))
835 err(EXIT_FAILURE, "Write failed");
836
837 tree = tree->wi_right;
838 }
839 }
840
841 void *
842 emalloc(size_t len)
843 {
844 void *ptr;
845 if ((ptr = malloc(len)) == NULL)
846 err(EXIT_FAILURE, "malloc %lu failed", (unsigned long)len);
847 return ptr;
848 }
849
850 char *
851 estrdup(const char *str)
852 {
853 char *ptr;
854 if ((ptr = strdup(str)) == NULL)
855 err(EXIT_FAILURE, "strdup failed");
856 return ptr;
857 }
858