1 /*
2  *	File: mkreadmes.c
3  *
4  *	Contains three routines that drive the body of the work, one for
5  *	each level of readmes -- the top level, the category level, and
6  *	the individual port level -- plus a few small auxiliary functions
7  *
8  *	main() will call only one of the make_readme_* routines, based on whether
9  *	the pathname given as a command line argument (or the top-level ports
10  *	directory, if none is provided) is determined to be at the top, category,
11  *	or individual port level
12  *
13  *	$Id: mkreadmes.c,v 1.128 2012/05/04 20:53:10 conrads Exp $
14  *
15  *****************************************************************************/
16 
17 #include <fcntl.h>
18 #include <limits.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <sys/stat.h>
23 
24 #include "mkreadmes.h"
25 
26 /* placeholder names used in templates.  on a successful match, the
27 	placeholder's index in this array is then used as an index into the
28 	index_field[] array to obtain a pointer to the corresponding index field's
29 	data, which will have been safely tucked away in the index_line[] array
30 	(an array of nul-terminated strings) by the split_index_line() routine
31  */
32 static const char *placeholder[] = {
33 	"%%PKG%%",
34 	"%%PORT%%",
35 	"%%PREFIX%%",
36 	"%%COMMENT%%",
37 	"%%DESCR%%",
38 	"%%EMAIL%%",
39 	"%%CATEGORY%%",
40 	"%%BUILD_DEPENDS%%",
41 	"%%RUN_DEPENDS%%",
42 	"%%WEBSITE%%",
43 	"%%TOP%%",
44 	"%%SUBDIR%%",
45 	NULL			/* end of list marker */
46 };
47 
48 static	char	category_comment[LINE_MAX];	/* comment from a category's
49 												Makefile, saved by
50 												make_readme_category() for use
51 												by make_readme_top()
52 											 */
53 char	categories[MAXCATLIST];	/* list of categories found in the top-level
54 									Makefile
55 								 */
56 size_t	num_categories = 0;		/* number of categories found */
57 
58 char *(*index_categories)[] = NULL;	/* pointer to an array of pointers to
59 										the first line of each category's
60 										section within the index buffer
61 									 */
62 
63 extern	char	*index_field[];	/* pointers to current port's index fields */
64 
65 char   *next_index;			/* pointer to the next index line to search */
66 char   *next_cat;			/* pointer to first index line of next category */
67 
68 char	templates_dir[PATH_MAX];	/* directory containing the README.*
69 										template files
70 									 */
71 /* pointers to dynamically allocated buffers for the template files */
72 char	*template_buf_top = NULL;
73 char	*template_buf_category = NULL;
74 char	*template_buf_port = NULL;
75 
76 extern	char	ports_dir[PATH_MAX];		/* top-level ports directory */
77 extern	char	ports_dir_plus[PATH_MAX];	/* ports_dir plus trailing slash */
78 
79 extern	int		quiet;			/* silence normal output */
80 extern	int		verbose;		/* increase the amount of output */
81 
82 /* prototypes for global functions */
83 
84 int make_readme(const char *path, int make_level);
85 
86 /* prototypes for external functions */
87 
88 extern	char   *load_file(const char *path);						/* util.c */
89 extern	char   *skip_leading(const char *str, const char *pattern);	/* util.c */
90 extern	int		search_index(const char *path);					   /* index.c */
91 
92 /*****************************************************************************/
93 
load_template(const char * ext)94 static char *load_template(const char *ext)
95 /*
96 	load the template file with extension 'ext' into a dynamically allocated
97 	buffer
98 
99 	returns a pointer to the buffer on success, or NULL on failure
100 
101 	Note: This function makes no attempt to determine whether the requested
102 	file has already been loaded, relying instead on the caller to have
103 	already checked for that before calling this function.  Both it and its
104 	auxiliary routine, load_file() in util.c, don't know and don't care whether
105 	the pointer variable the result will be assigned to on return has already
106 	been set, so use with caution!
107 
108 	There *is* an upside to this methodology: because of the lack of checking
109 	before proceeding to load the file, both of these routines are very simple,
110 	and very fast.  :-)
111  */
112 {
113 	char	template_path[PATH_MAX];
114 
115 	/* generate the fully qualified pathname for the template file */
116 	strcpy(template_path, templates_dir);
117 	strcat(template_path, TEMPLATENAME);	/* template name minus extension */
118 
119 	/* add extension to pathname */
120 	strcat(template_path, ext);
121 
122 	return load_file(template_path);
123 }
124 
125 /*****************************************************************************/
126 
load_templates(void)127 static int load_templates(void)
128 /*
129 	load all three of the template files -- README.port, README.category and
130 	README.top -- into memory
131 
132 	this is just a "convenience" function, called from make_readme
133 	for top-level builds
134 
135 	returns 0 on success, or -1
136 */
137 {
138 	/* Don't load any files that have already been loaded */
139 
140 	if (template_buf_port == NULL)		/* not loaded yet */
141 		if ((template_buf_port = load_template("port")) == NULL)
142 			return -1;
143 
144 	if (template_buf_category == NULL)	/* same here */
145 		if ((template_buf_category = load_template("category")) == NULL)
146 			return -1;
147 
148 	if (template_buf_top == NULL)		/* and here */
149 		if ((template_buf_top = load_template("top")) == NULL)
150 			return -1;
151 
152 	return 0;
153 }
154 
155 /*****************************************************************************/
156 
HTMLify(char * dest,char * src)157 static inline char *HTMLify(char *dest, char *src )
158 /*
159 	Copy 'src' to 'dest', replacing any occurences of '&', '<' or '>'
160 	with their HTML representations
161 
162 	Returns a pointer to the location in 'dest' one past the last
163 	character copied
164 
165 	Uses the addstr() macro defined in mkreadmes.h
166  */
167 {
168 	while (*src)
169 	{
170 		switch(*src)
171 		{
172 			case '&':
173 			{
174 				addstr(dest, "&amp;");
175 				++src;
176 				continue;
177 			}
178 			case '<':
179 			{
180 				addstr(dest, "&lt;");
181 				++src;
182 				continue;
183 			}
184 			case '>':
185 			{
186 				addstr(dest, "&gt;");
187 				++src;
188 				continue;
189 			}
190 			default:
191 				*dest++ = *src++;
192 		}
193 	}
194 
195 	return dest;
196 }
197 
198 /*****************************************************************************/
199 
match_placeholder(const char * ptr)200 static inline int match_placeholder(const char *ptr)
201 /*
202 	Try to find a matching "%%VARIABLE%%" within the placeholder array
203 	for the string pointed to by 'ptr'
204 
205 	On entry to this routine, 'ptr' will be pointing to the first "%" in the
206 	possible symbol name we're looking for
207 
208 	Returns a positive integer representing the variable's index in the
209 	placeholder array, or -1 if no match is found
210  */
211 {
212 	int j;
213 
214 	int i = 0;
215 
216 	while (placeholder[i])
217 	{
218 		j = 0;
219 
220 		while(ptr[j] == (placeholder[i])[j])
221 			++j;
222 
223 		if ((placeholder[i])[j] == '\0')
224 			return i;
225 		++i;
226 	}
227 
228 	/* No match found */
229 	return -1;
230 }
231 
232 /*****************************************************************************/
233 
category_index(const char * cat)234 static inline int category_index(const char *cat)
235 /*
236 	scan the list of category names to get an index to use with the
237 	index_categories[] array, from which we'll obtain a pointer to
238 	the first line of this category in the index buffer
239 
240 	returns the index at which the category was found in the list, or
241 	num_categories if not found
242 */
243 {
244 	char   *categories_ptr = categories;
245 	int		i = 0;
246 
247 	while (*categories_ptr)
248 	{
249 		if (strcmp(cat, categories_ptr) == 0)
250 			return i;									/* found a match */
251 		/* else */
252 		categories_ptr += strlen(categories_ptr) + 1;	/* bump pointer and */
253 		++i;											/* index */
254 	}
255 
256 	return i;	/* (i == num_categories) = not found */
257 }
258 
259 /*****************************************************************************/
260 
make_readme_port(const char * path,int make_level)261 static int make_readme_port(const char *path, int make_level)
262 /*
263 	Make the README.html file for a single port
264 
265 	On entry, category will already be pointing either to the "category/port"
266 	part of the port's pathname (if make_level == PORT) or just the category
267 	part (if make_level == CATEGORY or TOP)
268 
269 	Returns 0 on success, or 1 on failure
270 
271 	Uses the addstr() macro defined in mkreadmes.h
272 */
273 {
274 	char	readme_buf[MAXREADME_PORT];	/* output buffer */
275 	char	readme_path[PATH_MAX];		/* output pathname */
276 	int		readme;						/* file descriptor */
277 	char   *catport;					/* category/port portion of path */
278 	char	catonly[MAXCATNAME];		/* just the category name */
279 	char   *category;					/* pointer to category name */
280 	size_t	catlen;						/* length of category name */
281 	int		i;					/* index into the placeholder array where a
282 									template match was found
283 								 */
284 	int		cat_index;	/* index into the categories[] and index_categories[]
285 							arrays for this port's category (can't initialize
286 							here as we do in make_readme_category() below, not
287 							until we make certain that 'category' has been set
288 							properly
289 						 */
290 
291 	char   *readme_ptr = readme_buf;	/* pointer into output buffer */
292 	char   *template_ptr = template_buf_port;	/* pointer into template
293 													buffer
294 												 */
295 
296 	/* save a pointer to the category/port portion of port's path */
297 	catport = skip_leading(path, ports_dir_plus);
298 
299 	/* copy just the category to catonly */
300 	catlen = strcspn(catport, "/");
301 	strncpy(catonly, catport, catlen);
302 	catonly[catlen] = '\0';
303 	category = catonly;			/* set category to point to the string we
304 									just copied
305 								 */
306 	if (make_level == PORT)	/* need to set next_index and next_cat */
307 	{
308 	/* set next_index to point to the first line of this port's category's
309 		section in the index buffer, so we won't waste any time searching in
310 		any of the preceding categories
311 	 */
312 		next_index = (*index_categories)[cat_index = category_index(category)];
313 
314 		if (next_index == NULL)	/* category not found :-( */
315 		{
316 			fprintf(stderr, "No such category: %s\n", category);
317 			return 1;
318 		}
319 
320 		/* set next_cat to point to the first index line of the next category,
321 			to use as the search cutoff point
322 		 */
323 		next_cat = (*index_categories)[cat_index + 1];
324 	}
325 
326 	/* Find the port's entry in the index file, saving pointers to its index
327 		information in the index_field[] array
328 	 */
329 	if (!search_index(path))
330 	{
331 		fprintf(stderr, "Port %s not found in INDEX\n", catport);
332 		return 1;
333 	}
334 
335 	/* For efficiency's sake, don't output the following message
336 		unless this one port is all we're building, or the verbose
337 		flag is set to >= 3
338 	 */
339 	if ((verbose > 2) || ((make_level == PORT) && !quiet))
340 		printf("Making README.html for port %s\n", catport);
341 
342 	/* Copy the template buffer to the readme buffer,
343 		performing any substitutions needed
344 	 */
345 
346 	while (*template_ptr)
347 	{
348 		/* just copy from template buffer to readme buffer
349 			unless we hit a '%'
350 		 */
351 		if ((*template_ptr == '%') &&
352 			((i = match_placeholder(template_ptr)) >= 0))
353 		{
354 			/* a few fields require special handling:
355 
356 				for the PORT field, use the "category/port" string we saved
357 				earlier in the variable catport[]
358 
359 				for the COMMENT field, convert any '&', '<' or '>' characters
360 				into their HTML representations
361 
362 				for the DESCR field, adjust the path prefix to the realpath
363 				of the top of our ports tree, which may differ from the
364 				canonical "/usr/ports" used in the index field
365 
366 				for BUILD_DEPENDS and RUN_DEPENDS, don't output anything
367 				if these fields are empty in the INDEX file, otherwise,
368 				precede them with the introductory text #defined in
369 				BUILD_DEPS or RUN_DEPS
370 
371 				for WEBSITE, output the lead-in text #defined in WWW_BEFORE,
372 				then the field contents, and follow it up with the text
373 				#defined in WWW_AFTER
374 
375 			 */
376 			switch (i)
377 			{
378 				case PKG:
379 					addstr(readme_ptr, index_field[i]);
380 					break;
381 				case PORT:
382 					addstr(readme_ptr, catport);
383 					break;
384 				case COMMENT:
385 					/* the port's comment may have some bad HTML chars */
386 					readme_ptr = HTMLify(readme_ptr, index_field[COMMENT]);
387 					break;
388 				case DESCR:
389 					addstr(readme_ptr, ports_dir);
390 					addstr(readme_ptr, skip_leading(index_field[i], PORTSTOP));
391 					break;
392 				case EMAIL:
393 					addstr(readme_ptr, index_field[i]);
394 					break;
395 				case BUILD_DEPENDS:
396 					if (strlen(index_field[i]))
397 					{
398 						addstr(readme_ptr, BUILD_DEPS);
399 						addstr(readme_ptr, index_field[i]);
400 					}
401 					break;
402 				case RUN_DEPENDS:
403 					if (strlen(index_field[i]))
404 					{
405 						addstr(readme_ptr, RUN_DEPS);
406 						addstr(readme_ptr, index_field[i]);
407 					}
408 					break;
409 				case WEBSITE:
410 					if (strlen(index_field[i]))
411 					{
412 						addstr(readme_ptr, WWW_BEFORE);
413 						addstr(readme_ptr, index_field[i]);
414 						addstr(readme_ptr, WWW_AFTER);
415 					}
416 					break;
417 				case TOP:
418 					addstr(readme_ptr, ports_dir);
419 					break;
420 			}
421 			/* bump template pointer past placeholder */
422 			template_ptr += strlen(placeholder[i]);
423 		}
424 		else *readme_ptr++ = *template_ptr++;
425 	}
426 
427 	/* Open the port's README.html file for writing */
428 
429 	strcpy(readme_path, path);			/* port's path in the ports tree */
430 	strcat(readme_path, READMENAME);	/* append "/README.html" */
431 
432 	if ((readme = open(readme_path, O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, MODE))
433 				== -1)
434 	{
435 		perror(readme_path);
436 		return 1;
437 	}
438 
439 	/* write readme_buf to output file and close */
440 
441 	if (write(readme, readme_buf, (size_t)(readme_ptr - readme_buf)) == -1)
442 	{
443 		perror(readme_path);
444 		close(readme);
445 		return 1;
446 	}
447 
448 	close(readme);
449 
450 	return 0;
451 }
452 
453 /*****************************************************************************/
454 
make_readme_category(const char * path,int make_level)455 static int make_readme_category(const char *path, int make_level)
456 /*
457 	Make the README.html file for a single category
458 
459 	Calls make_readme_port() to build the README.html file for each
460 	port within the category
461 
462 	Returns 0 on success, or 1 on failure
463 
464 	Uses the addstr() macro defined in mkreadmes.h
465 */
466 {
467 	char	readme_buf[MAXREADME_CAT];	/* output buffer */
468 	char	readme_path[PATH_MAX];		/* output pathname */
469 	int		readme;						/* file descriptor */
470 
471 	char	Makefile_buf[LINE_MAX];		/* input buffer */
472 	char	Makefile_path[PATH_MAX];	/* input pathname */
473 	FILE   *Makefile;					/* input stream for category's
474 											Makefile
475 										 */
476 	char	ports[MAXPORTSLIST];		/* list of ports found in a category's
477 											Makefile
478 										 */
479 	char   *port;				/* pointer to port in current input buffer */
480 	char	port_path[PATH_MAX];	/* full path to port in ports tree */
481 
482 	int		i;					/* index into the placeholder array where a
483 									template match was found
484 								 */
485 
486 	char   *comment_ptr = Makefile_buf;	/* for locating the COMMENT line */
487 	char   *readme_ptr = readme_buf;	/* pointer into output buffer */
488 	char   *ports_ptr = ports;			/* pointer into ports list */
489 	char   *template_ptr = template_buf_category;	/* pointer into template
490 														buffer
491 													 */
492 	int		num_ports = 0;				/* number of ports in category */
493 
494 /* get the index into the categories[] and index_categories[] arrays
495 	for this category
496  */
497 	char   *category = skip_leading(path, ports_dir_plus);
498 	int		cat_index = category_index(category);
499 
500 	/* set next_index to point to the first line of this category's section
501 		in the index buffer, so we won't waste any time searching in any of
502 		the preceding categories
503 	 */
504 	if ((next_index = (*index_categories)[cat_index]) == NULL)	/* not found */
505 	{
506 		fprintf(stderr, "No such category: %s\n", category);
507 		return 1;
508 	}
509 
510 	/* set next_cat to point to the first index line of the next category,
511 		to use as the search cutoff point
512 	 */
513 	next_cat = (*index_categories)[cat_index + 1];
514 
515 	/* Open the category's Makefile for reading */
516 
517 	strcpy(Makefile_path, path);
518 	strcat(Makefile_path, MAKEFILENAME);
519 
520 	if ((Makefile = fopen(Makefile_path, "r")) == NULL)
521 	{
522 		perror(Makefile_path);
523 		return 1;
524 	}
525 
526 	/* First, find the COMMENT line, saving it externally in category_comment[]
527 		in case we were called from make_readme_top(), which will need the
528 		comment for its own README.html file
529 	 */
530 
531 	while ((fgets(Makefile_buf, LINE_MAX, Makefile) != NULL))
532 	{
533 		if ((comment_ptr = skip_leading(Makefile_buf, "    COMMENT = "))
534 			!= Makefile_buf)	/* found it */
535 		{
536 			/* don't want the newline */
537 			comment_ptr[strlen(comment_ptr) - 1] = '\0';
538 			strcpy(category_comment, comment_ptr);	/* save the COMMENT */
539 			break;
540 		}
541 	}
542 
543 	/* Got the comment, continue reading to get all the SUBDIRs */
544 
545 	while ((fgets(Makefile_buf, LINE_MAX, Makefile) != NULL))
546 	{
547 		if ((port = skip_leading(Makefile_buf, "    SUBDIR += ")) !=
548 				Makefile_buf)	/* found a SUBDIR line */
549 		{
550 			/* don't want the newline */
551 			port[strlen(port) - 1] = '\0';
552 			ports_ptr = stpcpy(ports_ptr, port) + 1;	/* add to ports list */
553 			++num_ports;
554 		}
555 	}
556 
557 	/* add an extra nul character to mark the end of the ports list */
558 	*ports_ptr = '\0';
559 
560 	fclose(Makefile);
561 
562 	/* Done collecting the data, now we can start generating
563 		the README.html file
564 	 */
565 	if (!quiet)
566 		printf("Making README.html for category %s (%d ports)\n",
567 			category, num_ports);
568 
569 	/* Copy the template buffer to the readme buffer,
570 		performing any substitutions needed
571 	 */
572 
573 	while (*template_ptr)
574 	{
575 		/* just copy from template buffer to readme buffer
576 			unless we hit a '%'
577 		 */
578 		if ((*template_ptr == '%') &&
579 			((i = match_placeholder(template_ptr)) >= 0))
580 		{
581 			/* SUBDIR requires special handling, calling make_readme_port()
582 				for each port in the category to build the port's README.html
583 				file and to obtain its package name and comment for the
584 				category README.html file
585 
586 				just as in make_readme_port(), for the port's COMMENT field,
587 				convert any '&', '<' or '>' characters into their HTML
588 				representations
589 			 */
590 			switch (i)
591 			{
592 				case COMMENT:	/* the category's comment, not a port's */
593 					addstr(readme_ptr, category_comment);
594 					/* add the number of ports in category to end of comment */
595 					readme_ptr += sprintf(readme_ptr, " (%d ports)", num_ports);
596 					break;
597 				case CATEGORY:
598 					addstr(readme_ptr, category);
599 					break;
600 				case SUBDIR:
601 					ports_ptr = ports;
602 
603 					while (*ports_ptr)
604 					{
605 						port = ports_ptr;	/* port's base name */
606 
607 						/* generate the full path to the port */
608 						strcpy(port_path, path);	/* category's path */
609 						strcat(port_path, "/");
610 						strcat(port_path, port);	/* append port's name */
611 
612 						/* make the port's README.html */
613 						make_readme_port(port_path, make_level);
614 
615 						/* now we can use the information left over in the
616 							index_field[] array by make_readme_port() */
617 						addstr(readme_ptr, "<a href=\"");
618 						addstr(readme_ptr, port);
619 						addstr(readme_ptr, "/README.html\">");
620 						addstr(readme_ptr, index_field[PKG]);
621 						addstr(readme_ptr, "</a>: ");
622 
623 						/* the port's comment may have some bad HTML chars */
624 						readme_ptr = HTMLify(readme_ptr, index_field[COMMENT]);
625 						strcpy(readme_ptr++, "\n");
626 
627 						/* bump ports_ptr to next item in ports array */
628 						ports_ptr += strlen(port) + 1;
629 					}
630 			}
631 			/* bump template pointer past placeholder */
632 			template_ptr += strlen(placeholder[i]);
633 		}
634 		else *readme_ptr++ = *template_ptr++;
635 	}
636 
637 	/* Open the category's README.html file for writing */
638 
639 	strcpy(readme_path, path);
640 	strcat(readme_path, READMENAME);
641 
642 	if ((readme = open(readme_path, O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, MODE))
643 				== -1)
644 	{
645 		perror(readme_path);
646 		return 1;
647 	}
648 
649 	/* write readme_buf to output file and close */
650 
651 	if (write(readme, readme_buf, (size_t)(readme_ptr - readme_buf)) == -1)
652 	{
653 		perror(readme_path);
654 		close(readme);
655 		return 1;
656 	}
657 
658 	close(readme);
659 
660 	return 0;
661 }
662 
663 /*****************************************************************************/
664 
make_readme_top(const char * path,int make_level)665 static int make_readme_top(const char *path, int make_level)
666 /*
667 	Make the top-level README.html file
668 
669 	Calls make_readme_category() to build the README.html file for each
670 	category in the categories list created earlier from the top-level Makefile
671 
672 	Returns 0 on success, or 1 on failure
673 
674 	Uses the addstr() macro defined in mkreadmes.h
675 */
676 {
677 	char	readme_buf[MAXREADME_TOP];	/* output buffer */
678 	char	readme_path[PATH_MAX];		/* output pathname */
679 	int		readme;						/* file descriptor */
680 
681 	char   *category;				/* name of current category */
682 	char	category_path[PATH_MAX];	/* fully qualified path to current
683 											category
684 										 */
685 	int		i;						/* index into the placeholder array
686 										where a template match was found
687 									 */
688 	char   *categories_ptr = categories;	/* pointer into categories list */
689 	char   *readme_ptr = readme_buf;	/* pointer into output buffer */
690 	char   *template_ptr = template_buf_top;	/* pointer into template buffer
691 												 */
692 	if (!quiet)
693 		printf("Making top-level README.html in %s (%lu categories)\n",
694 			ports_dir, num_categories);
695 
696 	/* Copy the template buffer to the readme buffer,
697 		performing any substitutions needed
698 	 */
699 
700 	while (*template_ptr)
701 	{
702 		/* just copy from template buffer to readme buffer
703 			unless we hit a '%'
704 		 */
705 		if ((*template_ptr == '%') &&
706 			((i = match_placeholder(template_ptr)) >= 0))
707 		{
708 			/* %%SUBDIR%% is the only placeholder used in the top-level
709 				template, and will cause this routine to call
710 				make_readme_category() for each category found in the top-level
711 				Makefile (already obtained and saved earlier in the categories[]
712 				list), which in turn will call make_readme_port() for each port
713 				in the category's own Makefile.
714 
715 				On return from make_readme_category() the category's COMMENT
716 				will be stored in category_comment[] for use in the top-level
717 				README.html file.
718 			 */
719 			if (i == SUBDIR)
720 			{
721 				categories_ptr = categories;
722 
723 				while (*categories_ptr)
724 				{
725 					category = categories_ptr;	/* base name of category */
726 
727 					/* generate the full path to the category */
728 					strcpy(category_path, ports_dir_plus);
729 					strcat(category_path, category);
730 
731 					/* make the category's README.html, saving its
732 						COMMENT in category_comment[] */
733 					make_readme_category(category_path, make_level);
734 
735 					/* now we have all the information we need
736 						for this category
737 					 */
738 					addstr(readme_ptr, "<a href=\"");
739 					addstr(readme_ptr, category);
740 					addstr(readme_ptr, "/README.html\">");
741 					addstr(readme_ptr, category);
742 					addstr(readme_ptr, "</a>: ");
743 					addstr(readme_ptr, category_comment);
744 					strcpy(readme_ptr++, "\n");
745 
746 					/* bump pointer to next item in categories array */
747 					categories_ptr += strlen(category) + 1;
748 				}
749 			}
750 			/* bump template pointer past placeholder */
751 			template_ptr += strlen(placeholder[i]);
752 		}
753 		else *readme_ptr++ = *template_ptr++;
754 	}
755 
756 	/* Open the top-level README.html file for writing */
757 
758 	strcpy(readme_path, path);			/* fully qualified top-level path */
759 	strcat(readme_path, READMENAME);	/* append "/README.html" */
760 
761 	if ((readme = open(readme_path, O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, MODE))
762 				== -1)
763 	{
764 		perror(readme_path);
765 		return 1;
766 	}
767 
768 	/* write readme_buf to output file and close */
769 
770 	if (write(readme, readme_buf, (size_t)(readme_ptr - readme_buf)) == -1)
771 	{
772 		perror(readme_path);
773 		close(readme);
774 		return 1;
775 	}
776 
777 	close(readme);
778 
779 	return 0;
780 }
781 
782 /*****************************************************************************/
783 
make_readme(const char * path,int make_level)784 int make_readme(const char *path, int make_level)
785 /*
786 	wrapper for the actual make_readme_* routines, called from main()
787 
788 	make sure the template files are loaded for whichever level of build
789 	we're doing, then do the actual build
790 
791 	returns the return code of whichever make_readme_* routine it calls
792 
793 	(the default case in the switch statement below should never happen,
794 	and is provided merely to avoid a compiler warning)
795  */
796 {
797 	switch (make_level)
798 	{
799 		case TOP:
800 		{
801 			/* load_templates() will check first before trying to load a
802 				a template file, to see if it has already been loaded, and
803 				if so, won't try to load it again
804 			 */
805 			if (load_templates() != 0)
806 				return -1;
807 			/* else */
808 			return make_readme_top(path, make_level);
809 		}
810 
811 		/* for CATEGORY and PORT, we do our checking right here first, before
812 			calling load_template()
813 		 */
814 
815 		case CATEGORY:
816 		{
817 			if (template_buf_category == NULL)	/* not loaded yet */
818 				if ((template_buf_category = load_template("category")) == NULL)
819 					return -1;
820 			if (template_buf_port == NULL)		/* ditto */
821 				if ((template_buf_port = load_template("port")) == NULL)
822 					return -1;
823 			return make_readme_category(path, make_level);
824 		}
825 
826 		case PORT:
827 		{
828 			if (template_buf_port == NULL)	/* and likewise for PORT */
829 				if ((template_buf_port = load_template("port")) == NULL)
830 					return -1;
831 			return make_readme_port(path, make_level);
832 		}
833 		default:	return -1;	/* this should never happen, but let's
834 									keep the compiler happy
835 								 */
836 	}
837 }
838 
839