/* $Id: c2man.c,v 1.1 2004-05-03 05:17:48 behdad Exp $ * * C Manual page generator. * Reads C source code and outputs manual pages. */ #include #include #include "c2man.h" #include "enum.h" #include "strconcat.h" #include "strappend.h" #include "manpage.h" #include "output.h" #include "patchlevel.h" #ifdef I_FCNTL #include #endif #ifdef I_SYS_FILE #include #endif #include #include /* getopt declarations */ extern int getopt(); extern char *optarg; extern int optind; /* lex declarations */ extern FILE *yyin; /* lex input stream */ /* Name of the program */ const char *progname = "c2man"; /* Program options */ /* TRUE if static declarations are also output. */ boolean static_out = FALSE; /* TRUE if variable declarations are output. */ boolean variables_out = FALSE; /* TRUE if formal parameter promotion is enabled. */ boolean promote_param = TRUE; /* String output before prototype declaration specifiers */ const char *decl_spec_prefix = ""; /* String output before prototype declarator */ const char *declarator_prefix = " "; /* String output after prototype declarator */ const char *declarator_suffix = "\n"; /* String output before the first parameter in a function prototype */ const char *first_param_prefix = "\n\t"; /* String output before each subsequent parameter in a function prototype */ const char *middle_param_prefix = "\n\t"; /* String output after the last parameter in a function prototype */ const char *last_param_suffix = "\n"; /* Directory to write output files in */ char *output_dir = NULL; /* Name of the manual */ char *manual_name = NULL; /* Section for manual page */ const char *manual_section = NULL; /* prefix for generated #include lines */ char *header_prefix = NULL; /* list of include file specified by user */ IncludeFile *first_include; static IncludeFile **last_next_include = &first_include; /* list of excluded sections specified by user */ ExcludeSection *first_excluded_section; static ExcludeSection **last_next_excluded_section = &first_excluded_section; /* TRUE if c2man should attempt to fixup comment sections */ boolean fixup_comments = TRUE; /* do we group related stuff into one file? */ boolean group_together; /* was terse description read from file or command line option? */ boolean terse_specified; /* terse description when grouped together */ char *group_terse = NULL; /* should we always document parameters, even if it's only "Not Documented" */ boolean always_document_params = TRUE; /* look for a function def comment at the start of the function body */ boolean look_at_body_start = FALSE; /* only look for a function def comment at the start of the function body */ boolean body_start_only = FALSE; /* default output info for each object type */ struct Output_Object_Info output_object[_OBJECT_NUM] = { #if 0 {'c', "class"}, {'s', "struct"}, {'e', "enum"}, {'t', "typedef"}, #endif {'f', "function"}, {'v', "variable"}, {'F', "static function"}, {'V', "static variable"} }; /* Include file directories */ #ifdef MSDOS int num_inc_dir = 1; const char *inc_dir[MAX_INC_DIR] = { ".\\" }; #else #ifdef AMIGA int num_inc_dir = 1; const char *inc_dir[MAX_INC_DIR] = { "include:" }; #else int num_inc_dir = 2; const char *inc_dir[MAX_INC_DIR] = { "./", "/usr/include/" }; #endif #endif /* total number of errors encountered */ int errors; /* name of the base file being processed; NULL = stdin */ const char *basefile; Time_t basetime; /* modification time of base file */ boolean inbasefile; /* are we parsing in that base file? */ /* is the base file a header file? */ boolean header_file; #ifdef AMIGA struct Output *output = &autodoc_output; const char *default_section = "doc"; #else /* use nroff output by default */ struct Output *output = &nroff_output; const char *default_section = "3"; #endif /* should we generate the output file named after the input file? */ boolean use_input_name = FALSE; /* should we generate embeddable files? */ boolean make_embeddable = FALSE; #define USE_CPP #ifdef USE_CPP const char *cpp_cmd = CPP_FILE_COM; #if defined(MSDOS) #include "popen.h" #define popen(c,m) os_popen(c,m) #define pclose(f) os_pclose(f) #else #if defined (_MSC_VER) #define popen(c,m) _popen(c,m) #define pclose(f) _pclose(f) #endif #endif #endif boolean verbose = FALSE; /* can cpp read standard input? */ static boolean cppcanstdin #ifdef CPP_CAN_STDIN = 1 #endif ; /* does cpp ignore header files */ static boolean cppignhdrs #ifdef CPP_IGN_HDRS = 1 #endif ; /* nifty little function for handling I/O errors */ void my_perror(action, filename) const char *action, *filename; { int err = errno; fprintf(stderr,"%s: %s ", progname, action); errno = err; perror(filename); } /* write the #include lines as specified by the user */ void print_includes(f) FILE *f; { IncludeFile *incfile; for (incfile = first_include; incfile; incfile=incfile->next) { char *name = incfile->name; boolean surrounded = *name == '"' || *name == '<'; fputs("#include ", f); if (!surrounded) fputc('<',f); fputs(name, f); if (!surrounded) fputc('>',f); fputc('\n',f); } } void outmem() { fprintf(stderr,"%s: Out of memory!\n", progname); exit(1); } #ifndef DBMALLOC void *safe_malloc(size) size_t size; { void *mem; if ((mem = (void *)malloc(size)) == NULL) outmem(); return mem; } #endif /* Replace any character escape sequences in a string with the actual * characters. Return a pointer to malloc'ed memory containing the result. * This function knows only a few escape sequences. */ static char * escape_string (src) char *src; { char *result, *get, *put; result = strduplicate(src); put = result; get = src; while (*get != '\0') { if (*get == '\\') { switch (*(++get)) { case 'n': *put++ = '\n'; ++get; break; case 't': *put++ = '\t'; ++get; break; default: if (*get != '\0') *put++ = *get++; } } else { *put++ = *get++; } } *put = *get; return result; } /* Output usage message and exit. */ static void usage () { int i; fprintf(stderr, "usage: %s [ option ... ] [ file ... ]\n", progname); fputs(" -o directory\twrite output files in directory\n",stderr); fputs(" -p\t\tdisable prototype promotion\n", stderr); fputs(" -s\t\toutput static declarations\n", stderr); fputs(" -v\t\toutput variable declarations\n", stderr); fputs(" -k\t\tdon't attempt to fixup comments\n", stderr); fputs(" -b\t\tlook for descriptions at top of function bodies\n", stderr); fputs(" -B\t\tonly look for descriptions by applying -b\n", stderr); fputc('\n', stderr); fputs(" -i incfile\n", stderr); fputs(" -i \"incfile\"\n", stderr); fputs(" -i \tadd #include for incfile to SYNOPSIS\n", stderr); fputc('\n', stderr); fputs(" -H prefix\tspecify prefix for #include in SYNOPSIS\n", stderr); fputc('\n', stderr); fputs(" -g\n", stderr); fputs(" -G terse\tgroup info from each file into a single page\n", stderr); fputs(" -e\t\tmake embeddable files\n", stderr); fputc('\n', stderr); fputs(" -l ", stderr); #ifdef HAS_LINK fputs("h|", stderr); #endif #ifdef HAS_SYMLINK fputs("s|", stderr); #endif fputs("f|n|r\t", stderr); fputs("linking for grouped pages: ", stderr); #ifdef HAS_LINK fputs("hard, ", stderr); #endif #ifdef HAS_SYMLINK fputs("soft, ", stderr); #endif fputs("file, none or remove\n", stderr); fputs(" -n\t\tName output file after input source file\n", stderr); fputs(" -L\t\tLazy: Be silent about undocumented parameters\n", stderr); fputs(" -T n|l|h|t|a[,options]\tselect typesetting output format: nroff, LaTeX, HTML ,TeXinfo or AutoDoc\n", stderr); nroff_output.print_options(); latex_output.print_options(); html_output.print_options(); texinfo_output.print_options(); autodoc_output.print_options(); fputs(" -M name\tset name of the manual in which the page goes\n", stderr); fputs(" -x section\texclude section from ouput\n", stderr); fputc('\n', stderr); fputs(" -D name[=value]\n", stderr); fputs(" -U name\n", stderr); fputs(" -I directory\tC preprocessor options\n", stderr); fputc('\n', stderr); fputs(" -F template\tset prototype template in the form ", stderr); fputs("\"int f (a, b)\"\n",stderr); fputs(" -P preprocessor\tAlternate C preprocessor ", stderr); fputs("(e.g., \"gcc -E -C\")\n", stderr); fputs(" -V\t\tbe verbose and print version information\n", stderr); fputs(" -S section\tset the section for the manual page (default = 3)\n", stderr); fputs(" -O ", stderr); for (i = 0; i < _OBJECT_NUM; i++) fputc(output_object[i].flag, stderr); fputs("[subdir][.ext]", stderr); fputs("\tOutput control over different object types:\n\t\t", stderr); for (i = 0; i < _OBJECT_NUM; i++) { fputs(output_object[i].name, stderr); if (i <= _OBJECT_NUM - 2) fprintf(stderr,i == _OBJECT_NUM-2 ? " or " : ", "); } fputs(".\n", stderr); exit(1); } /* name of the temporary file; kept here so we can blast it if hit with ctrl-C */ static char temp_name[20]; Signal_t (*old_interrupt_handler)(); /* ctrl-C signal handler for use when we have a temporary file */ static Signal_t interrupt_handler(sig) int sig; { unlink(temp_name); exit(128 + sig); } /* open a unique temporary file. * To be universally accepted by cpp's, the file's name must end in .c; so we * can't use mktemp, tmpnam or tmpfile. * returns an open stream & sets ret_name to the name. */ FILE *open_temp_file() { int fd; long n = getpid(); FILE *tempf; boolean remove_temp_file(); /* keep generating new names until we hit one that does not exist */ do { /* ideally we'd like to put the temporary file in /tmp, but it must go * in the current directory because when cpp processes a #include, it * looks in the same directory as the file doing the include; so if we * use /tmp/blah.c to fake reading fred.h via `#include "fred.h"', cpp * will look for /tmp/fred.h, and fail. */ sprintf(temp_name,"c2man%ld.c",n++ % 1000000); } while((fd = #ifdef HAS_OPEN3 open(temp_name,O_WRONLY|O_CREAT|O_EXCL,0666) #else creat(temp_name,O_EXCL|0666) /* do it the old way */ #endif ) == -1 && errno == EEXIST); /* install interrupt handler to remove the temporary file */ old_interrupt_handler = signal(SIGINT, interrupt_handler); /* convert it to a stream */ if ((fd == -1 && errno != EEXIST) || (tempf = fdopen(fd, "w")) == NULL) { my_perror("error fdopening temp file",temp_name); remove_temp_file(); return NULL; } return tempf; } /* remove the temporary file & restore ctrl-C handler. * returns FALSE in the event of failure. */ boolean remove_temp_file() { int ok = unlink(temp_name) == 0; /* this should always succeed */ signal(SIGINT, old_interrupt_handler); return ok; } /* process the specified source file through the pre-processor. * This is a lower level routine called by both process_stdin and process_file * to actually get the work done once any required temporary files have been * generated. */ int process_file_directly(base_cpp_cmd, name) const char *base_cpp_cmd; const char *name; { char *full_cpp_cmd; #ifdef DEBUG fprintf(stderr,"process_file_directly: %s, %s\n", base_cpp_cmd, name); #endif #ifdef USE_CPP full_cpp_cmd = strconcat(base_cpp_cmd, " ", name, NULLCP); if (verbose) fprintf(stderr,"%s: running `%s'\n", progname, full_cpp_cmd); if ((yyin = popen(full_cpp_cmd, "r")) == NULL) { my_perror("error running", base_cpp_cmd); free(full_cpp_cmd); return 0; } #else if (verbose) fprintf(stderr,"%s: reading %s\n", progname, name); if (name && freopen(name, "r", yyin) == NULL) { my_perror("cannot open", name); return 0; } #endif parse_file(name); #ifdef USE_CPP free(full_cpp_cmd); if (pclose(yyin) & 0xFF00) return 0; #else if (fclose(yyin)) { my_perror("error closing", name); return 0; } #endif return !errors; } /* process a specified file */ int process_file(base_cpp_cmd, name) const char *base_cpp_cmd; const char *name; { char *period; struct stat statbuf; #ifdef DEBUG fprintf(stderr,"process_file: %s, %s\n", base_cpp_cmd, name); #endif basefile = name; header_file = (period = strrchr(name,'.')) && (period[1] == 'h' || period[1] == 'H'); /* use the file's date as the date in the manual page */ if (stat(name,&statbuf) != 0) { my_perror("can't stat", name); return 0; } basetime = statbuf.st_mtime; /* should we do this via a temporary file? * Only if it's a header file and either CPP ignores them, or the user * has specified files to include. * * For HP/Apollo (SR10.3, CC 6.8), we must always use a temporary file, * because its compiler recognizes the special macro "__attribute(p)", * which we cannot redefine in the command line because it has parameters. */ #ifndef apollo if (header_file && (cppignhdrs || first_include)) #endif { FILE *tempf; int ret; if (verbose) fprintf(stderr, "%s: preprocessing via temporary file\n", progname); if ((tempf = open_temp_file()) == NULL) return 0; print_includes(tempf); if (verbose) print_includes(stderr); #ifdef apollo fprintf(tempf,"#define __attribute(p)\n", basefile); #endif fprintf(tempf,"#include \"%s\"\n", basefile); if (verbose) fprintf(stderr,"#include \"%s\"\n", basefile); if (fclose(tempf) == EOF) { my_perror("error closing temp file", temp_name); remove_temp_file(); return 0; } /* since we're using a temporary file, it's not the base file */ inbasefile = 0; ret = process_file_directly(base_cpp_cmd, temp_name); remove_temp_file(); return ret; } /* otherwise, process it directly */ inbasefile = 1; return process_file_directly(base_cpp_cmd,name); } /* process the thing on the standard input */ int process_stdin(base_cpp_cmd) const char *base_cpp_cmd; { if (isatty(fileno(stdin))) fprintf(stderr,"%s: reading standard input\n", progname); header_file = 0; /* assume it's not since it's from stdin */ basefile = NULL; /* use the current date in the man page */ basetime = time((Time_t *)NULL); inbasefile = 1; /* reading stdin, we start in the base file */ /* always use a temp file if the preprocessor can't read stdin, otherwise * only use one if the user specified files for inclusion. */ if (!cppcanstdin || first_include) /* did user specify include files? */ { FILE *tempf; int c, ret; if (verbose) fprintf(stderr,"%s: reading stdin to a temporary file\n", progname); if ((tempf = open_temp_file()) == NULL) return 0; print_includes(tempf); if (verbose) print_includes(stderr); fprintf(tempf,"#line 1 \"stdin\"\n"); while ((c = getchar()) != EOF) putc(c,tempf); if (fclose(tempf) == EOF) { my_perror("error closing temp file", temp_name); remove_temp_file(); return 0; } ret = process_file_directly(base_cpp_cmd, temp_name); remove_temp_file(); return ret; } else { char *full_cpp_cmd = strconcat(base_cpp_cmd," ", CPP_STDIN_FLAGS, NULLCP); if (verbose) fprintf(stderr,"%s: running `%s'\n", progname, full_cpp_cmd); if ((yyin = popen(full_cpp_cmd, "r")) == NULL) { my_perror("error running", full_cpp_cmd); return 0; } parse_file(basefile); free(full_cpp_cmd); if (pclose(yyin) & 0xFF00) return 0; return !errors; } } int main (argc, argv) int argc; char **argv; { int i, c, ok = 0; char *s, cbuf[2]; const char *base_cpp_cmd; IncludeFile *includefile; ExcludeSection *excludesection; char *cpp_opts; #ifdef HAS_LINK enum LinkType link_type = LINK_HARD; /* for -g/G */ #else enum LinkType link_type = LINK_FILE; #endif #ifdef YYDEBUG extern int yydebug; #endif /* initialise CPP options with -D__C2MAN__ */ cbuf[0] = VERSION + '0'; cbuf[1] = '\0'; #ifdef VMS cpp_opts = strconcat("-\"D__C2MAN__=", cbuf, "\"",NULLCP); #else cpp_opts = strconcat("-D__C2MAN__=", cbuf, NULLCP); #ifdef NeXT cpp_opts = strappend(cpp_opts, " -D_NEXT_SOURCE", NULLCP); #endif /* !NeXT */ #endif /* !VMS */ /* Scan command line options. */ while ((c = getopt(argc, argv, "P:D:F:I:psU:Vvo:eM:H:G:gi:x:S:l:LT:nO:kbB")) != EOF) { switch (c) { case 'I': case 'D': case 'U': cbuf[0] = c; cbuf[1] = '\0'; if (cpp_opts) cpp_opts = strappend(cpp_opts," -",cbuf,optarg,NULLCP); else cpp_opts = strconcat("-",cbuf,optarg,NULLCP); break; case 'P': cpp_cmd = optarg; /* with no better info to go on, we have to assume that this * preprocessor is minimally capable. */ cppcanstdin = 0; cppignhdrs = 1; break; case 'G': group_terse = optarg; terse_specified = TRUE; /* FALLTHROUGH */ case 'g': group_together = TRUE; break; case 'F': s = escape_string(optarg); decl_spec_prefix = s; while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s; if (*s == '\0') usage(); *s++ = '\0'; while (*s != '\0' && isascii(*s) && isalnum(*s)) ++s; if (*s == '\0') usage(); declarator_prefix = s; while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s; if (*s == '\0') usage(); *s++ = '\0'; while (*s != '\0' && isascii(*s) && isalnum(*s)) ++s; if (*s == '\0') usage(); declarator_suffix = s; while (*s != '\0' && *s != '(') ++s; if (*s == '\0') usage(); *s++ = '\0'; first_param_prefix = s; while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s; if (*s == '\0') usage(); *s++ = '\0'; while (*s != '\0' && *s != ',') ++s; if (*s == '\0') usage(); middle_param_prefix = ++s; while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s; if (*s == '\0') usage(); *s++ = '\0'; while (*s != '\0' && isascii(*s) && isalnum(*s)) ++s; if (*s == '\0') usage(); last_param_suffix = s; while (*s != '\0' && *s != ')') ++s; *s = '\0'; break; case 'p': promote_param = FALSE; break; case 's': static_out = TRUE; break; case 'V': verbose = TRUE; fprintf(stderr, "%s: Version %d, Patchlevel %d\n", progname, VERSION, PATCHLEVEL); break; case 'v': variables_out = TRUE; break; case 'k': fixup_comments = FALSE; break; case 'o': output_dir = optarg; break; case 'M': manual_name = optarg; break; case 'H': header_prefix = optarg; break; case 'i': *last_next_include = includefile = (IncludeFile *)safe_malloc(sizeof *includefile); includefile->name = optarg; includefile->next = NULL; last_next_include = &includefile->next; break; case 'x': *last_next_excluded_section = excludesection = (ExcludeSection *)safe_malloc(sizeof *excludesection); excludesection->name = optarg; excludesection->next = NULL; last_next_excluded_section = &excludesection->next; break; case 'S': manual_section = optarg; break; case 'l': switch(optarg[0]) { #ifdef HAS_LINK case 'h': link_type = LINK_HARD; break; #endif #ifdef HAS_SYMLINK case 's': link_type = LINK_SOFT; break; #endif case 'f': link_type = LINK_FILE; break; case 'n': link_type = LINK_NONE; break; case 'r': link_type = LINK_REMOVE;break; default: usage(); } break; case 'e': make_embeddable = TRUE; break; case 'n': use_input_name = TRUE; break; case 'L': always_document_params = FALSE; break; case 'T': switch(optarg[0]) { case 'n': output = &nroff_output; default_section = "3"; break; case 'l': output = &latex_output; default_section = "tex"; break; case 't': output = &texinfo_output; default_section = "texi"; break; case 'h': output = &html_output; default_section = "html"; break; case 'a': output = &autodoc_output; default_section = "doc"; break; default: usage(); } s = strtok(&optarg[1], ","); if (s && *output->parse_option == NULL) usage(); while(s) { if (output->parse_option(s)) usage(); s = strtok(NULL, ","); } break; case 'O': for (i = 0; i < _OBJECT_NUM; i++) if (output_object[i].flag == optarg[0]) break; if (i == _OBJECT_NUM) { fprintf(stderr,"%s: -O option must specify one of:\n\t", progname); for (i = 0; i < _OBJECT_NUM; i++) { fprintf(stderr,"%c (%s)", output_object[i].flag, output_object[i].name); if (i <= _OBJECT_NUM - 2) fprintf(stderr,i == _OBJECT_NUM-2 ? " or " : ", "); } fprintf(stderr, ".\n"); exit(1); } if ((s = strchr(++optarg,'.'))) /* look for an extension */ { output_object[i].subdir = alloc_string(optarg, s); output_object[i].extension = strduplicate(s+1); } else output_object[i].subdir = strduplicate(optarg); break; case 'b': look_at_body_start = TRUE; break; case 'B': body_start_only = TRUE; look_at_body_start = TRUE; break; case '?': default: usage(); } } /* make sure we have a manual section */ if (manual_section == NULL) manual_section = default_section; #ifdef MALLOC_DEBUG getchar(); /* wait so we can start up NeXT MallocDebug tool */ #endif #ifdef YYDEBUG yydebug = 1; #endif if (cpp_opts) { base_cpp_cmd = strconcat(cpp_cmd, " ", cpp_opts, NULLCP); free(cpp_opts); } else base_cpp_cmd = cpp_cmd; if (optind == argc) { if (use_input_name) { fprintf(stderr,"%s: %s\n", progname, "cannot name output after input file if there isn't one!"); usage(); } ok = process_stdin(base_cpp_cmd); } else for (i = optind; i < argc; ++i) if (!(ok = process_file(base_cpp_cmd,argv[i]))) break; if (ok && firstpage) output_manual_pages(firstpage,argc - optind, link_type); free_manual_pages(firstpage); destroy_enum_lists(); if (cpp_opts) free((char *)base_cpp_cmd); for (includefile = first_include; includefile;) { IncludeFile *next = includefile->next; free(includefile); includefile = next; } for (excludesection = first_excluded_section; excludesection;) { ExcludeSection *next = excludesection->next; free(excludesection); excludesection = next; } for (i = 0; i < _OBJECT_NUM; i++) { safe_free(output_object[i].subdir); safe_free(output_object[i].extension); } #ifdef DBMALLOC malloc_dump(2); malloc_chain_check(1); #endif #ifdef MALLOC_DEBUG sleep(1000000); #endif return !ok; }