/* * Copyright 1995-1998 by Devin Reade . For distribution * information see the README file that is part of the manpack archive, * or contact the author, above. * * $Id: man2.c,v 1.2 1998/03/29 07:16:12 gdr-ftp Exp $ */ #ifdef __ORCAC__ segment "man2______"; #pragma noroot #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "man.h" #define MAX(a,b) ((a) > (b)) ? (a) : (b) static LC_StringArray_t buildManList(char *suffix, char *name); static LC_StringArray_t makePathArray(const char *manpath); static void display(char *list); static char * getBaseName (char *out, char *in); /* * Pre: argc is the number of strings in argv. It should either be 1 or 2. * If DEBUG is defined, then any other value of argc will result in * a failed assert. * * argv is an array of strings. If argc==1, then argv[0] is presumed * to be the name of the man page. If argc==2, then argv[0] is * presumed to be a section number and argv[1] is the name of the * man page. * * globals: * manpath must be a malloc'd string. Either ':' or ' ' may * may be used as a directory delimiter. * macroPackage must be set to a static string. * t_flag must be in a defined state (either set or unset) * hyphen_flag must be in a defined state (either set or unset) * * Post: print the man page in whatever form it takes, acting as necessary * on the -, -M, and -T flags and section number. */ int man(int argc, char *argv[]) { LC_StringArray_t manpath_array, manpagelist; char *sec, *name, *current_path; Section *section; int i, j, k, abort; short section_found, page_found; char dirbrk; /* path component separator. either ':' or '/' */ char c, *p; struct sgttyb termMode; short oldMode; /* initialization */ abort = 0; section_found = 0; page_found = 0; if (t_flag) { if ((tcat = getenv("TCAT")) == NULL) { tcat = TCAT; } else tcat = LC_xstrdup(tcat); if ((troff = getenv("TROFF")) == NULL) { troff = TROFF; } else troff = LC_xstrdup(troff); } if (hyphen_flag) { pager = CAT; } else if ((pager = getenv("PAGER")) == NULL) { pager = PAGER; } /* determine name and, if appropriate, sec */ switch (argc) { case 1: sec = NULL; name = argv[0]; break; case 2: sec = argv[0]; /* special case some section abbreviations */ if (!strcasecmp(sec,"l")) { sec = "local"; } else if (!strcasecmp(sec,"n")) { sec = "new"; } else if (!strcasecmp(sec,"o")) { sec = "old"; } else if (!strcasecmp(sec,"p")) { sec = "public"; } name = argv[1]; break; default: fprintf(stderr,"internal error at line %d in file %s\n",__LINE__, __FILE__); exit(1); } /* create array of paths to search */ if ((manpath_array = makePathArray(manpath)) == NULL) { return 1; } /* * loop over all the paths in MANPATH */ i=0; current_path = (manpath_array->lc_vec)[i]; while (!abort && current_path) { dirbrk = (strchr(current_path,':')!=NULL) ? ':' : '/'; /* go to the current path in MANPATH */ if (chdir(current_path) == -1) { i++; current_path = (manpath_array->lc_vec)[i]; continue; } /* loop over sections */ for (j=0; !abort && sections[j].name != NULL; j++) { /* * if section number was specified and this isn't it, do * the next loop */ if (sec && (strcasecmp(sec,sections[j].name) || (!isdigit(*sec) && strncasecmp(sec,sections[j].name,strlen(sec))))) continue; section_found++; /* * we're going to check this section. Get the pathnames * (relative to this directory) of the two files. */ manpagelist = buildManList(sections[j].suffix,name); if (manpagelist->lc_used == 0) continue; page_found++; for (k=0; !abort && k < manpagelist->lc_used; k++) { display((manpagelist->lc_vec)[k]); if (!hyphen_flag && !t_flag) { fprintf(stderr, "type q to quit, or any other key for next man page: "); c = getcharraw(); if (c == '\0') { fprintf(stderr,"getcharraw failed line %d of %s: %s\n", __LINE__,__FILE__,strerror(errno)); exit(1); } fputc('\n',stderr); /* evaluate result */ if ((c == 'q') || (c == 'Q')) abort++; } } LC_StringArrayDestroy(manpagelist); } /* done looping over sections */ i++; current_path = (manpath_array->lc_vec)[i]; } /* done looping over paths */ i = 0; /* used here for the return code */ if (sec) { if (!section_found) { fprintf(stderr,"there is no section %s in the manual\n",sec); i++; } else if (!page_found) { fprintf(stderr,"there is no %s in section %s\n",name,sec); i++; } } else if (!page_found) { fprintf(stderr,"there is no %s in the manual\n",name); i++; } return i; } #define DIRBUF_LEN 10 static const char *manstr="man"; static const char *catstr="cat"; static LC_StringArray_t buildManList(char *suffix, char *name) { static char buffer1[FILENAME_MAX]; static char buffer2[FILENAME_MAX]; DIR *directory; struct dirent *entry; LC_StringArray_t list1, list2; size_t len; int i, j, k; char *p, *fn, *L1, *L2; /* initialization */ list1 = LC_StringArrayNew(); list2 = LC_StringArrayNew(); #ifdef DEBUG /* sanity check on arguments */ if(MAX(strlen(manstr),strlen(catstr)) + strlen(suffix) >= FILENAME_MAX) { fprintf(stderr,"internal error: buffer overflow at line %d of %s\n", __LINE__,__FILE__); } #endif /* * look in man subdirectory */ strcpy(buffer1,manstr); strcat(buffer1,suffix); if ((directory = opendir(buffer1)) != NULL) { while ((entry = readdir(directory)) != (struct dirent *) NULL) { /* skip if no match */ len = strlen(name); if (strncasecmp(entry->d_name,name,len) || (entry->d_name[len] != '.')) continue; if (strlen(manstr) + strlen(suffix) + strlen(entry->d_name) + strlen(suffix) + 1 >= FILENAME_MAX) { errx(1, "internal error: buffer overflow at line %s:%d\n", __FILE__, __LINE__); } sprintf(buffer1,"%s%s:%s",manstr,suffix,entry->d_name); /* look for "links" to aroff files. (what a kludge) */ if ((buffer1[3] != 'l') && (strcasecmp(".l",&buffer1[strlen(buffer1)-2])==0)) { FILE *linkptr; char *tp; /* dereference the "link" */ if ((linkptr = fopen(buffer1,"r")) == NULL) { fprintf(stderr,"couldn't open %s\n",buffer1); } else if (fgets(buffer2,FILENAME_MAX,linkptr)==NULL) { fprintf(stderr,"couldn't read %s\n",buffer1); } else { /* drop trailing space and newline */ tp = buffer2 + strlen(buffer2) -1; while ((tp>=buffer2) && (isspace(*tp) || *tp == '\n')) { *tp = '\0'; tp--; } fclose(linkptr); if (access(buffer2,R_OK) == 0) { LC_StringArrayAdd(list1, buffer2); } } } else { /* not a .l "link"; a normal file */ LC_StringArrayAdd(list1, buffer1); } } closedir(directory); } if (!t_flag) { /* * look in cat subdirectory */ strcpy(buffer1,catstr); strcat(buffer1,suffix); if ((directory = opendir(buffer1)) != NULL) { while ((entry = readdir(directory)) != (struct dirent *) NULL) { /* skip if no match */ len = strlen(name); if (strncasecmp(entry->d_name,name,len) || (entry->d_name[len] != '.')) continue; if (strlen(catstr) + strlen(suffix) + strlen(entry->d_name) + strlen(suffix) + 1 >= FILENAME_MAX) { fprintf(stderr,"internal error: buffer overflow at line %d of %s\n", __LINE__,__FILE__); } sprintf(buffer1,"%s%s:%s",catstr,suffix,entry->d_name); LC_StringArrayAdd(list2, buffer1); } closedir(directory); } } /* * eliminate files common to both lists */ len = strlen(suffix); for(i=0; i< list1->lc_used; i++) { L1 = (list1->lc_vec)[i]; for (j=0; j< list2->lc_used; j++) { L2 = (list2->lc_vec)[j]; getBaseName(buffer1, L1); getBaseName(buffer2, L2); #ifdef DEBUG if ((strlen(buffer1) < len + 5) || (strlen(buffer2) < len + 5)) { err(1, "internal error at line %d of %s\n", __LINE__, __FILE__); } #endif /* match after the respective "manXX/" and "catXX/" */ if (strcasecmp(&buffer1[len+4],&buffer2[len+4]) == 0 ) { p = newerFile(L1,L2); if (p == L1) { LC_StringArrayDelete(list2, L2); --j; } else if (p == (list2->lc_vec)[j]) { LC_StringArrayDelete(list1, L1); --i; break; } else { err(1, "internal error at %s:%d (newerFile failed)", __FILE__, __LINE__); } } /* endif */ } /* endfor */ } /* endfor */ /* * combine the two lists */ j = list2->lc_used; for (i=0; ilc_vec)[i]); } LC_StringArrayDestroy(list2); return list1; } /* * display */ #define MANSUBDIR 0 #define CATSUBDIR 1 #define FORMAT "Formatting manual page, please wait ..." #define DECOMP "Decompressing manual page, please wait ..." #define DECFOR "Decompressing and formatting manual page, please wait ..." #define OVERFLOW "Internal buffer overflow ... aborted." static void display(char *file) { int icompress, isubdir; char *tmac; char *roffer; char *compressor; fileType *ft; #ifdef DEBUG if (file == NULL) { fprintf(stderr,"internal error line %d of %s\n",__LINE__,__FILE__); exit(1); } if (strlen(file) < 4) { fprintf(stderr,"internal error line %d of %s\n",__LINE__,__FILE__); exit(1); } #endif /* * determine which subdirectory this file is in */ if (strncasecmp(file,"cat",3) == 0) { isubdir = CATSUBDIR; } else { isubdir = MANSUBDIR; } /* * if we're troffing a new file to fmt?, make sure the directory * exists */ if (t_flag && hyphen_flag) { char *p; struct stat sbuf; /* ensure the fmt? directory exists */ sprintf(linebuf,"fmt%s",&file[3]); if ((p = strchr(linebuf,':')) != NULL) { *p = '\0'; #ifdef DEBUG } else if ((p = strchr(linebuf,'/')) == NULL) { fprintf(stderr,"internal error line %d of %s\n",__LINE__, __FILE__); exit(1); #endif } else *p = '\0'; if (stat(linebuf,&sbuf) != 0) { fprintf(stderr,"couldn't stat %s: %s. %s skipped\n", linebuf,strerror(errno),file); return; } else if (!(sbuf.st_mode & S_IFDIR)) { fprintf(stderr,"cannot access %s: %s. %s skipped\n", linebuf,strerror(ENOTDIR),file); return; } } /* * determine the type of compression used, if any */ icompress = getSuffixIndex(file); if (icompress >= 0) { compressor = compressArray[icompress].extractor; if (isubdir == MANSUBDIR) { /* * compressed nroff source */ if (t_flag && hyphen_flag) { if (strlen(compressor) + 2*strlen(file) + strlen(troff) + strlen(macroPackage) + strlen(tcat) + 12 > BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s %s | %s -m%s - > fmt%s", compressor, file, troff, macroPackage, &file[3]); } else if (t_flag) { if (strlen(compressor) + strlen(file) + strlen(troff) + strlen(macroPackage) + strlen(tcat) + 12 > BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s %s | %s -m%s - | %s", compressor, file, troff, macroPackage, tcat); } else { /* not troff, jes' plain old nroff */ if (strlen(compressor) + strlen(file) + strlen(NROFF) + strlen(macroPackage) + strlen(pager) + 12 > BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } if (!hyphen_flag) printf("%s\n",DECFOR); sprintf(linebuf,"%s %s | %s -m%s - | %s", compressor, file, NROFF, macroPackage, pager); } } else { /* * compressed straight text */ if (strlen(compressor)+strlen(file)+strlen(pager)+5 > BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } if (!hyphen_flag) printf("%s\n",DECOMP); sprintf(linebuf,"%s %s | %s", compressor, file, pager); } } else { if (isubdir == MANSUBDIR) { /* * Can be either aroff or nroff source. If it's nroff source, * it must either be a TXT, SRC, or BIN file */ if (!hyphen_flag && !t_flag) printf("%s\n",FORMAT); if ((ft = getFileType(file)) == NULL) { perror(file); exit(1); } if ((ft->type == 0x50) || (ft->auxtype == 0x8010)) { /* * AppleworksGS Word Processor format; use 'aroff' */ if (t_flag) { fprintf(stderr,"cannot use troff on aroff source files\n"); return; } if (strlen(AROFF)+strlen(file)+strlen(pager)+5 > BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s %s | %s", AROFF, file, pager); } else if ((ft->type == TXT) || (ft->type == BIN) || (ft->type == SRC) || (ft->type == NON)) { /* * TeXT, BINary, or SouRCe file; assume nroff source */ if (t_flag && hyphen_flag) { if (strlen(troff) + strlen(macroPackage) + 2 * strlen(file) + 7 >= BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s -m%s %s > fmt%s", troff, macroPackage, file, &file[3]); } else if (t_flag) { if (strlen(troff) + strlen(macroPackage) + strlen(file) + strlen(tcat) + 7 >= BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s -m%s %s | %s",troff,macroPackage,file,tcat); } else { /* not troff, jes' plain old nroff */ if (strlen(NROFF) + strlen(macroPackage) + strlen(file) + strlen(pager) + 8 >= BUFFERSIZE) { fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s -m%s %s | %s",NROFF,macroPackage,file,pager); } } else { fprintf(stderr, "bad file type for %s\n\ttype = %x\n\taux = %lx\n", file,ft->type,ft->auxtype); return; } } else { /* assume straight text */ if (strlen(CAT) + strlen(file) + strlen(pager) + 4 >= BUFFERSIZE){ fprintf(stderr,"%s\n",OVERFLOW); exit(1); } sprintf(linebuf,"%s %s | %s", CAT, file, pager); } } #if 0 fprintf(stderr,"DEBUG: BUFFER: %s\n",linebuf); #endif system(linebuf); return; } /* * getBaseName -- copy the filename pointed to by into the buffer * pointed to by , dropping any compression suffix * that may be on the base name. The set of compression * suffixes is defined by the NULL-terminated compressArray[]. * * It is the caller's responsibility to ensure that the * buffer *out has been allocated with sufficient space * for the result. * * Returns a pointer to . */ static char *getBaseName (char *out, char *in) { char *p; int i; strcpy(out,in); if ((p = strrchr(out,'.')) != NULL) { for (i=0; compressArray[i].suffix; i++) { if (strcasecmp(p,compressArray[i].suffix)==0) { *p = '\0'; break; } } } return out; } /* * makePathArray * * Pre: is a list of colon-delimited path names * Post: returns a StringArray pointer where each string is an * element of */ static LC_StringArray_t makePathArray(const char *manpath) { LC_StringArray_t result; char *pathcopy, *p; result = LC_StringArrayNew(); pathcopy = LC_xstrdup(manpath); p = strtok(pathcopy, ":"); while (p != NULL) { LC_StringArrayAdd(result, p); p = strtok(NULL, ":"); } free(pathcopy); return result; }