/* * publish -- make a new software package known to the world * * Usage: publish [flags] pkg-ver * * where the available flags are: * * -q quiet mode: print nothing but error messages * -n no-install: just print what would be done, don't do it * -a auto mode: never create .DO_NOT_PUBLISH * -u unpublish * -r republish * -p publish * -L data-library mode: pick up all from lib directory * -D turn on verbose debugging output * -h, -?, anything unrecognized: * print help message and exit (-h and -? don't complain first) * * If UNPUBLISH_ARGV0 is defined at compile time and argv[0]'s last * component matches it, -u is silently supplied, effectively before * all actual arguments; if REPUBLISH_ARGV0 is defined at compile * time and argv[0]'s last component matches it, -r is silently * supplied, effectively before all actual arguments. * * Note that -n silently suppresses -q. By default, publish prints out * a list of all links created. * * If the pkg-ver argument is the one-character string ".", publish * will look for a file named WHERE_I_CAME_FROM (the name can be * changed by redefining WHERE_I_CAME_FROM below). If it finds it, it * will read it, looking for lines beginning "package:" and * "version:". If it finds one of each, and the version: string does * not contain a "-", it will paste the rest of the lines (after * stripping whitespace) together with a "-" between them and use * that. Otherwise (if the file can't be found, or doesn't contain * the appropriate lines), it will use the last component of the * current directory name (`basename \`pwd\``, loosely speaking). * * In -p mode (publishing): * * Please read # as * in the following lists; / and * together are * special in C. :-( See below for what the LOCAL* names stand for. * * LOCALMAN/pkg-ver must exist * * The patterns * LOCALMAN/pkg-ver/#.{0,[1-9lL]*,man,n} * LOCALMAN/pkg-ver/{cat,man}?/#.{0,[1-9lL]*,man,n} * must collectively match at least one file * * Let SD be LOCALBIN/pkg-ver, if it exists. * * SD/.DO_NOT_PUBLISH must not exist * * What will be done: * * Let SD be as above; if more than one of those directories * exists, exit with a complaint. * * Let MD be LOCALMAN/pkg-ver; if this doesn't exist, exit with a * complaint. * * Let ID be LOCALINC/pkg-ver. * * Let LD be LOCALLIB/pkg-ver. * * If SD/.DO_NOT_PUBLISH exists, complain and exit. * * If ID exists, let INCS be a list of all plain files under ID, * including any in subdirectories. (Note: not just .h files; in * particular, X11 has where ... does not have a * .h on it, and has at least one .c file in its include area.) * * If LD exists, let LIBS be a list of all lib*.* files in LD, or * if -L was given, all files in LD regardless of name. * * Let MANS be a list of all files in MD matching the patterns * mentioned above. Note that below, when links are made or being * checked, if the manpage file has a .man extension, the link * made or checked for will have an extension based on the section * it appears in, rather than being also called .man. * * Check for conflicts between MANS and existing files in * LOCALPATHMAN; between BINS and files in LOCALPATHBIN; between * INCS and files in LOCALPATHINC; and between LIBS and files in * LOCALPATHLIB. If any are found, complain and exit; if -a was * not given and SD exists, copy these complaints into * SD/.DO_NOT_PUBLISH (if SD doesn't exist, LOCALBIN/pkg-ver is * created and used as SD for the purpose). * * Create symlinks to MANS from LOCALPATHMAN; to BINS from * LOCALPATHBIN; to INCS from LOCALPATHINC; to LIBS from * LOCALPATHLIB. Create subdirectories as needed, splitting * existing subdirectory links as needed. Echo a line about each * link created into SD/.PUBLISH (if SD doesn't exist, * LOCALBIN/pkg-ver is created and used as SD for the purpose). * * In -u mode (unpublishing): * * Determine SD as above. If SD/.PUBLISH doesn't exist, complain * and exit; otherwise, remove the links listed therein. If this * leaves any empty subdirectories in LOCALPATHINC, remove them. * Remove the .PUBLISH file, and if that leaves SD empty, remove * it too. * * In -r mode (republishing): * * Operate as for publishing, but also look for other possible * values of SD for the same pkg (with different ver values). * Unpublish all such. Without -k, also remove their * corresponding SD, MD, ID, and LD directories entirely. * * About LOCAL*: These directories are located by checking environment * variables as follows, for LOCALfoo (eg, foo=BIN for LOCALBIN * above): * * - PUBLISH_LOCALfoo - if this exists, it is used unchanged. * * - LOCALfoo - if this exists, it is used unchanged. * * - LOCALROOT - if this exists, let PREFIX below be $LOCALROOT, * otherwise, let PREFIX be as configured below. Compute * LOCALfoo by checking for PUBLISH_foo, and if it exists, using * PREFIX$PUBLISH_foo; otherwise, concatentate PREFIX with a * compiled-in default as configured below. Note no slash is * provided; be careful. * * In any case, the string determined for any of the LOCAL* strings * must be no longer than STRING_LIMIT (configured below). */ /* Configuration. */ /* If argv[0]'s basename matches this, silently supply -u. Undefine to disable this feature. */ #define UNPUBLISH_ARGV0 "unpublish" /* If argv[0]'s basename matches this, silently supply -r. Undefine to disable this feature. */ #define REPUBLISH_ARGV0 "republish" /* The name for WHERE_I_CAME_FROM files. Undefine to disable this feature. */ #define WHERE_I_CAME_FROM "WHERE_I_CAME_FROM" #define STRING_LIMIT 512 /* max length of a LOCAL* string */ /* Names for the special files: */ static const char dot_publish[256] = ".PUBLISH"; static const char dot_binaries[256] = ".BINARIES"; static const char dot_do_not_publish[256] = ".DO_NOT_PUBLISH"; /* * This structure is not configurable, but it has to be defined before * the array declaration/initialization below, which *is* * configurable. */ typedef struct pathdef PATHDEF; struct pathdef { char *string; const char *foo; const char *defsuf; } ; /* * These are not configurable either, but they too need to be declared * before the array below. */ static char localbin_[STRING_LIMIT]; static char localman_[STRING_LIMIT]; static char localinc_[STRING_LIMIT]; static char locallib_[STRING_LIMIT]; static char localpathbin_[STRING_LIMIT]; static char localpathman_[STRING_LIMIT]; static char localpathinc_[STRING_LIMIT]; static char localpathlib_[STRING_LIMIT]; /* * String setup. The only things here that are really configurable are * the third fields, the defaults-to-be-appended strings. Adding or * deleting lines, or changing the first or second fields, is liable * to break things in weird and horrible ways. */ static PATHDEF pathdef[] = { { &localbin_[0], "BIN", "/.bin" }, { &localman_[0], "MAN", "/.man" }, { &localinc_[0], "INC", "/.include" }, { &locallib_[0], "LIB", "/.lib" }, { &localpathbin_[0], "PATHBIN", "/bin" }, { &localpathman_[0], "PATHMAN", "/man" }, { &localpathinc_[0], "PATHINC", "/include" }, { &localpathlib_[0], "PATHLIB", "/lib" } }; /* * Default for PREFIX */ static const char *def_prefix = "/local"; /* End of configuration. */ #define localbin ((const char *)&localbin_[0]) #define localman ((const char *)&localman_[0]) #define localinc ((const char *)&localinc_[0]) #define locallib ((const char *)&locallib_[0]) #define localpathbin ((const char *)&localpathbin_[0]) #define localpathman ((const char *)&localpathman_[0]) #define localpathinc ((const char *)&localpathinc_[0]) #define localpathlib ((const char *)&localpathlib_[0]) #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #ifdef NO_PROGNAME const char *__progname; int main_(int, char **); int main(int, char **); int main(int ac, char **av) { __progname = av[0]; return(main_(ac,av)); } #define main main_ #endif /* * Passing plain char to calls, when char is or might be * signed, is a violation of the interface contract for those calls * (which do not permit negative arguments, except that if EOF is * negative then it's allowed). So we cast arguments when we're using * them on objects from char vectors - but, for readability, we define * macros for it here rather than sprinkling casts around the code. */ #define Cisspace(x) isspace((unsigned char)(x)) #define Cisdigit(x) isdigit((unsigned char)(x)) #ifdef NO_SNPRINTF /* * You lose the checking, but what else is there to do? funopen() can * pinch-hit for snprintf, but how many stdios have funopen but not * snprintf? */ static void snprintf(char *, int, const char *, ...); static void snprintf(char *buf, int len, const char *fmt, ...) { va_list ap; va_start(ap,fmt); vsprintf(buf,fmt,ap); va_end(ap); } #endif /* * A STRLIST is a list of strings, each potentially tagged with * something more. A MANFILE_TAG is used to hold extra info about a * manpage, by keeping a pointer to the MANFILE_TAG in the tag of the * STRLIST. */ typedef struct strlist STRLIST; typedef struct manfile_tag MANFILE_TAG; struct strlist { STRLIST *link; /* singly-linked list */ char *s; /* the string */ void *tag; /* tag, if any */ } ; struct manfile_tag { int section; /* section number */ int catfile_p; /* boolean, is this file a catfile (if not, manfile) */ char *linkext; /* extension on the link (nil -> same as link-to) */ } ; /* Boolean flag markers, set true if the flag is given (or implied). */ static int qflag = 0; static int nflag = 0; static int aflag = 0; static int uflag = 0; static int rflag = 0; static int pflag = 0; static int kflag = 0; static int Lflag = 0; static int Dflag = 0; /* The pkg-ver argument, broken apart. */ static const char *pkg; static const char *ver; static const char *pkg_ver = 0; /* The SD, MD, ID, LD directories. */ static const char *SD; static const char *MD; static const char *ID; static const char *LD; /* * List of files we want to link to when publishing. The binfiles list * holds absolute pathnames (for historical reasons); the others are * all relative to MD/ID/LD (as appropriate). */ static STRLIST *binfiles; static STRLIST *manfiles; static STRLIST *incfiles; static STRLIST *libfiles; /* * List of conflicts found. Each STRLIST's string is the * versioned-directory file, with the tag being the existing link-farm * file it conflicts with. */ static STRLIST *conflicts; /* * The report; in this case, the strings are strings to be printed, not * pathnames at all. */ static STRLIST *report; /* * Things which would have been removed, but weren't because of -n. * Used during unpublishing to get proper conflict checking. */ static STRLIST *virtualrm = 0; /* Produce a brief usage summary. */ static void usage(void) { fprintf(stderr,"Usage: %s [flags] pkg-ver\n",__progname); fprintf(stderr,"flags: -q (quiet), -n (no-install), -a (auto-mode), -u (unpublish)\n"); } /* Strip the const qualifier from a char *. */ static char *deconst(const char *sc) { return((sc-(const char *)0)+(char *)0); } /* * catstrings - a relatively central utility routine. Basic usage is * catstrings(permanent, str1, str2, ..., CS_END) * where str1, str2, etc, are the strings to be concatenated. If * permanent is true, it is the caller's responsability to free the * returned string (or save it in permanent data structure); * otherwise, the caller must not free the string, and catstrings * itself will free it "eventually" (but at least a few calls later, * currently NSTRS calls later). * * There are a couple of frills: * * If an argument is CS_FREE(str), then catstrings will free() the * argument string after copying it into the returned string; this is * designed for uses where you want the effect of * somevar = catstrings(1,...,somevar,...,CS_END); * but without leaking memory because somevar isn't freed afterwards. * Without CS_FREE this would mean an extra variable. * * If an argument is CS_LEN(str,len), then catstrings will use exactly * len bytes of the str; otherwise, it will call strlen() on the * argument to determine how many bytes to copy. Normally len will be * less than strlen(str), but this does not have to be so; catstrings * will quite happily embed a NUL in the returned buffer if CS_LEN() * is used with a length that calls for including a NUL. (Note that * catstrings will _always_ append a NUL after the last argument.) * * CS_FREE and CS_LEN may be combined; the order in which they are * nested matters not at all. Either CS_FREE(CS_LEN(str,len)) or * CS_LEN(CS_FREE(str),len) will work, and have identical effects. * * Every call must end with CS_END, or catstrings will run wild walking * off the end of the arglist looking for the endmarker (which is * _not_ a nil pointer!). */ /* CS_FREE and CS_LEN are implemented by dropping distinctive arguments into the argument list before the arguments to which they apply. CS_END is another distinctive magic pointer. That is, catstrings(...,CS_FREE(x),...,CS_LEN(y,z),...,CS_END) turns into catstrings(...,&magic1,x,...,&magic2,y,z,...,&magic3) where magic* are char variables allocated specifically for the purpose. The lack of parens around s in the expansions of CS_FREE and CS_LEN is necessary to make this work when CS_LEN and CS_FREE are nested. */ static char catstrings_end; /* Marker for CS_END */ static char catstrings_free; /* Marker for CS_FREE */ static char catstrings_len; /* Marker for CS_LEN */ #define CS_FREE(s) (&catstrings_free),s #define CS_LEN(s,l) (&catstrings_len),s,(int)(l) #define CS_END (&catstrings_end) static char *catstrings(int perm, ...) { /* * We save the last NSTRS returned strings from perm==0 calls, in * strs[]. lens[] holds their lengths, so we can optimize by not * bothering to free and realloc, if a string would fit in a buffer * we already have. hand is the pointer to the just-returned string * (not the next to be returned). */ #define NSTRS 16 static char *strs[NSTRS]; static int lens[NSTRS]; static int hand = 0; int len; /* total length */ va_list ap; char *rv; /* return value */ const char *s; /* argument string */ char *sp; /* pointer into rv, as we load it */ int flags; /* do we have CS_FREE and/or CS_LEN? */ #define F_FREE 1 #define F_LEN 2 /* First, scan through and find out how long the result is. */ len = 1; /* for the \0 */ flags = 0; va_start(ap,perm); while (1) { s = va_arg(ap,const char *); if (s == &catstrings_end) break; if (s == &catstrings_free) { flags |= F_FREE; continue; } if (s == &catstrings_len) { flags |= F_LEN; continue; } if (flags & F_LEN) { len += va_arg(ap,int); } else { len += strlen(s); } flags = 0; } va_end(ap); /* * Okay, now we know how long it is. Either malloc a buffer or check * and possibly re-allocate one of our private buffers, depending on * whether the caller wants a permanent string or not. */ if (perm) { rv = malloc(len); } else { if (--hand < 0) hand = NSTRS - 1; if (len > lens[hand]) { free(strs[hand]); strs[hand] = malloc(lens[hand]=len); } rv = strs[hand]; } /* Now run through and actually build the string. */ sp = rv; *sp = '\0'; va_start(ap,perm); flags = 0; while (1) { s = va_arg(ap,const char *); if (s == &catstrings_end) break; if (s == &catstrings_free) { flags |= F_FREE; continue; } if (s == &catstrings_len) { flags |= F_LEN; continue; } if (flags & F_LEN) { int l; l = va_arg(ap,int); bcopy(s,sp,l); sp += l; } else { strcpy(sp,s); sp += strlen(sp); } if (flags & F_FREE) free(deconst(s)); flags = 0; } va_end(ap); *sp = '\0'; return(rv); #undef NSTRS #undef F_FREE #undef F_LEN } /* * Return `basename \`pwd\``, effectively, but doesn't actually do a * full getcwd(), instead doing effectively just the last component of * it "by hand". If this fails, we print a complaint and die, rather * than returning anything, because we're called only in a * circumstance where that's the Right Thing. */ static char *basename_pwd(void) { DIR *dir; struct dirent *d; struct stat dotstb; struct stat entstb; char *t; if (stat(".",&dotstb) < 0) { fprintf(stderr,"%s: can't stat .: %s\n",__progname,strerror(errno)); exit(1); } dir = opendir(".."); if (dir == 0) { fprintf(stderr,"%s: can't opendir ..: %s\n",__progname,strerror(errno)); exit(1); } while ((d=readdir(dir))) { t = catstrings(0,"../",d->d_name,CS_END); if ( (lstat(t,&entstb) >= 0) && (dotstb.st_ino == entstb.st_ino) && (dotstb.st_dev == entstb.st_dev) ) { t = catstrings(1,d->d_name,CS_END); closedir(dir); return(t); } } closedir(dir); fprintf(stderr,"%s: can't find . in ..\n",__progname); exit(1); } /* * Process a pkg-ver argument, cracking it at the last dash and saving * the pieces. Assumes the caller handles checking for multiple * pkg-ver arguments; this just cracks and saves. The argument is * saved directly; it must not be an automatic variable or a * transiently allocated string. */ static int set_package(const char *p_v) { const char *cp; char *t; const char *tag; if (!strcmp(p_v,".")) { p_v = 0; #ifdef WHERE_I_CAME_FROM { FILE *f; char line[256]; char *pkg; char *ver; f = fopen(WHERE_I_CAME_FROM,"r"); pkg = 0; ver = 0; if (f) { while (fgets(&line[0],sizeof(line),f) == &line[0]) { if (!strncasecmp(&line[0],"package:",8)) { pkg = pkg ? &line[0] : catstrings(0,&line[8],CS_END); } else if (!strncasecmp(&line[0],"version:",8)) { ver = ver ? &line[0] : catstrings(0,&line[8],CS_END); } } fclose(f); if (pkg && ver && (pkg != &line[0]) && (ver != &line[0]) && !index(ver,'-')) { char *t; char *f; int tx; t = malloc(strlen(pkg)+1+strlen(ver)+1); tx = 0; for (f=pkg;*f;f++) if (!Cisspace(*f)) t[tx++] = *f; t[tx++] = '-'; for (f=ver;*f;f++) if (!Cisspace(*f)) t[tx++] = *f; t[tx] = '\0'; p_v = t; tag = catstrings(1," (expanded from ",WHERE_I_CAME_FROM,")",CS_END); } } } #endif if (! p_v) { p_v = basename_pwd(); tag = " (expanded from current directory)"; } } pkg_ver = p_v; cp = rindex(p_v,'-'); if (cp == 0) { fprintf(stderr,"%s: no dash in package-version argument `%s'%s\n",__progname,p_v,tag); return(1); } t = malloc((cp-p_v)+1); bcopy(p_v,t,cp-p_v); t[cp-p_v] = '\0'; pkg = t; ver = cp + 1; return(0); } /* * Parse the argument list. Handles flags and pkg-ver arguments, * complaining about anything it doesn't understand. Exits upon * errors (but finishes scanning the arglist first, and calls * usage()). */ static void handleargs(int ac, char **av) { int errs; errs = 0; for (ac--,av++;ac;ac--,av++) { if (**av != '-') { if (! pkg_ver) { errs += set_package(*av); } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; } continue; } for (++*av;**av;++*av) { switch (**av) { case 'q': qflag = 1; break; case 'n': nflag = 1; break; case 'a': aflag = 1; break; case 'u': uflag = 1; pflag = 0; rflag = 0; break; case 'p': pflag = 1; rflag = 0; uflag = 0; break; case 'r': rflag = 1; uflag = 0; pflag = 0; break; case 'k': kflag = 1; break; case 'U': fprintf(stderr,"%s: -U is no longer used; see -p and -r.\n",__progname); exit(1); break; case 'L': Lflag = 1; break; case 'D': Dflag = 1; break; case 'h': case '?': errs ++; break; default: fprintf(stderr,"%s: unrecognized flag -%c\n",__progname,**av); errs ++; break; } } } if (!errs && !pkg_ver) { fprintf(stderr,"%s: no pkg-ver specified\n",__progname); errs ++; } if (errs) { usage(); exit(1); } } /* * Return true if the path s names a filesystem entity of type * wanttype. wanttype must be one of the S_IF* names, eg, S_IFREG or * S_IFDIR. (This actually also requires that the path leading to s * be walkable by the caller; for the purposes of this program, that's * what we want. We also use stat rather than lstat; this is * arguable.) */ static int exists_type(const char *s, unsigned int wanttype) { struct stat stb; if ( (stat(s,&stb) < 0) || ((stb.st_mode & S_IFMT) != wanttype) ) return(0); return(1); } /* Make code marginally easier to read. */ #define exists_dir(s) exists_type((s),S_IFDIR) #define exists_plain(s) exists_type((s),S_IFREG) /* * Return if s names any filesystem entity at all. * (Or almost - see the parenthetical note above exists_type(), above.) */ static int exists_any(const char *s) { struct stat stb; return(lstat(s,&stb) >= 0); } /* * Return a STRLIST holding the contents of the argument directory. * The tags attached to the STRLISTs will all be nil pointers. If the * return value is DIRCONTENTS_ERR, opendir() returned an error * indication. The returned list is in no particular order (reversed * directory-entry order, actually). */ static STRLIST dircontents_err_; #define DIRCONTENTS_ERR (&dircontents_err_) static STRLIST *dircontents(const char *path) { DIR *dir; struct dirent *d; STRLIST *list; STRLIST *sl; list = 0; dir = opendir(path); if (dir == 0) return(DIRCONTENTS_ERR); while ((d=readdir(dir))) { if ( (d->d_fileno == 0) || ( (d->d_name[0] == '.') && ( !d->d_name[1] || ( (d->d_name[1] == '.') && !d->d_name[2] ) ) ) ) continue; sl = malloc(sizeof(STRLIST)); sl->link = list; list = sl; sl->s = catstrings(1,d->d_name,CS_END); sl->tag = 0; } closedir(dir); return(list); } /* * Read a line from f. Returns the line in malloc()ed storage, with * the newline removed. A newline is silently supplied if the file * contains a partial line at the end. A nil pointer return means EOF * was reached (with no partial line accumulated). */ static char *get_line(FILE *f) { char *buf; int len; int have; int c; buf = malloc(1); len = 0; have = 0; while (1) { c = getc(f); if (c == EOF) { if (len > 0) { c = '\n'; } else { free(buf); return(0); } } if (c == '\n') { buf[len] = '\0'; return(buf); } if (len >= have) buf = realloc(buf,(have=len+16)+1); buf[len++] = c; } } /* * Work out the list of binfiles. Scans SD, silently filtering out * .PUBLISH, if found. */ static void find_bin_files(void) { STRLIST *t; STRLIST *contents; binfiles = 0; if (! exists_dir(SD)) return; contents = dircontents(SD); if (contents == DIRCONTENTS_ERR) { fprintf(stderr,"%s: can't read %s?\n",__progname,SD); exit(1); } while (contents) { t = contents; contents = t->link; if (!strcmp(t->s,dot_publish)) { free(t->s); free(t); } else { t->link = binfiles; binfiles = t; t->s = catstrings(1,SD,"/",CS_FREE(t->s),CS_END); } } } /* * Work out the list of manfiles. Does this by scanning MD, looking * for cat[0-9] and man[0-9] directories, saving everything else it * finds; it then scans any such directories it found, saving * everything in them. It then walks the list of names it saved, * checking each name to see whether it has one of the extensions we * want; if so, we check to see if we can intuit the section number * (if not, assume section 1 and print a warning saying so); also, if * we can tell it's a catfile instead of a manfile, note that. This * routine is responsible for complaining if no manfiles were found. */ static void find_man_files(void) { STRLIST *contents; STRLIST *t; STRLIST *subt; STRLIST *subdirs; STRLIST *maybefiles; char *s; manfiles = 0; contents = dircontents(MD); if (contents == DIRCONTENTS_ERR) { fprintf(stderr,"%s: %s: %s\n",__progname,MD,strerror(errno)); exit(1); } subdirs = 0; maybefiles = 0; while (contents) { t = contents; contents = t->link; if ( ( !strncmp(t->s,"cat",3) || !strncmp(t->s,"man",3) ) && Cisdigit(t->s[3]) && !t->s[4] ) { t->link = subdirs; subdirs = t; } else { t->link = maybefiles; maybefiles = t; } } while (subdirs) { subt = subdirs; subdirs = subt->link; contents = dircontents(catstrings(0,MD,"/",subt->s,CS_END)); while (contents) { t = contents; contents = t->link; s = t->s; t->s = catstrings(1,subt->s,"/",s,CS_END); free(s); t->link = maybefiles; maybefiles = t; } free(subt->s); free(subt); } while (maybefiles) { int wantit; int section; int catfile; t = maybefiles; maybefiles = t->link; section = -1; catfile = 0; wantit = 0; s = rindex(t->s,'.'); if (s) { s ++; switch (*s) { case '0': wantit = ! s[1]; catfile = 1; break; case 'n': wantit = ! s[1]; break; case 'm': wantit = (s[1] == 'a') && (s[2] == 'n') && !s[3]; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': wantit = 1; section = *s - '0'; break; case 'l': case 'L': wantit = 1; break; } } if (wantit) { MANFILE_TAG *tag; if ( !strncmp(t->s,"cat",3) && t->s[3] && (t->s[4] == '/') ) { catfile = 1; section = t->s[3] - '0'; } else if ( !strncmp(t->s,"man",3) && t->s[3] && (t->s[4] == '/') ) { catfile = 0; section = t->s[3] - '0'; } else if (section < 0) { fprintf(stderr,"%s: %s/%s: assuming section 1\n",__progname,MD,t->s); section = 1; } t->link = manfiles; manfiles = t; tag = malloc(sizeof(MANFILE_TAG)); tag->section = section; tag->catfile_p = catfile; tag->linkext = 0; if (!strcmp(s,"man")) { char x; x = '0' + section; tag->linkext = catstrings(1,CS_LEN(&x,1),CS_END); } t->tag = tag; } else { free(t->s); free(t); } } if (manfiles == 0) { fprintf(stderr,"%s: can't find any man pages in %s\n",__progname,MD); exit(1); } } /* * Compute the list of include files. This is just a recursive walk of * ID, saving the name of each plain file found. Each thing found * that's not a plain file is tried as a directory to be recursed into * (this means that if under ID there is a symlink pointing upward in * the directory structure, this code will loop "forever", until it * runs out of memory and crashes). */ static void find_inc_files(void) { STRLIST *pending; STRLIST *contents; STRLIST *t; char *curpref; pending = 0; incfiles = 0; contents = dircontents(ID); if (contents == DIRCONTENTS_ERR) return; curpref = catstrings(1,CS_END); while (1) { if (! contents) { if (! pending) break; t = pending; pending = t->link; contents = dircontents(catstrings(0,ID,"/",t->s,CS_END)); if (contents == DIRCONTENTS_ERR) { contents = 0; } else { free(curpref); curpref = catstrings(1,t->s,"/",CS_END); } free(t->s); free(t); continue; } t = contents; contents = t->link; if (exists_plain(catstrings(0,ID,"/",curpref,t->s,CS_END))) { t->link = incfiles; incfiles = t; t->s = catstrings(1,curpref,CS_FREE(t->s),CS_END); } else { t->link = pending; pending = t; t->s = catstrings(1,curpref,CS_FREE(t->s),CS_END); } } free(curpref); } /* * Compute the list of lib files. This is just lib*.* in LD, or, if * Lflag is set, all files from LD. */ static void find_lib_files(void) { STRLIST *contents; STRLIST *t; libfiles = 0; contents = dircontents(LD); if (contents == DIRCONTENTS_ERR) return; if (Lflag) { libfiles = contents; } else { while (contents) { t = contents; contents = t->link; if (!strncmp(t->s,"lib",3) && index(t->s,'.')) { t->link = libfiles; libfiles = t; } else { free(t->s); free(t); } } } } /* * Little utility routine to return a pointer to just after the last * slash in a pathname, returning a slashless argument unchanged. */ static char *basenameptr(char *s) { char *rv; rv = rindex(s,'/'); if (rv) rv ++; else rv = s; return(rv); } /* * Add a conflict to the list of conflicts. toinstall is the path in * the versioned directory we'd like to link to; existing is the path * in the link farm we found that keeps us from doing so. */ static void add_conflict(const char *toinstall, const char *existing) { STRLIST *t; t = malloc(sizeof(STRLIST)); t->link = conflicts; conflicts = t; t->s = catstrings(1,toinstall,CS_END); t->tag = catstrings(1,existing,CS_END); } /* * Return true if the argument string is on the virtualrm list. */ static int virtuallyrmed(const char *s) { STRLIST *t; for (t=virtualrm;t;t=t->link) { if (!strcmp(t->s,s)) return(1); } return(0); } /* Check for executable-program file conflicts. */ static void check_bin_conflicts(void) { STRLIST *t; char *p; for (t=binfiles;t;t=t->link) { p = catstrings(0,localpathbin,"/",basenameptr(t->s),CS_END); if (exists_any(p) && !virtuallyrmed(p)) add_conflict(t->s,p); } } /* * Check for manpage conflicts. This routine is relatively complicated * because it can find either manfiles or catfiles in the "manfiles" * list, and has to check against three files in each case: the * manfile, the catfile with the appropriate extension, and the * catfile with the extension ".0". Note that the way we built the * list of manfiles guarantees that each one contains a dot, thus the * check to make sure there really is a dot is a can't-happen. */ static void check_man_conflicts(void) { STRLIST *t; char *bnp; char *dot; int bnl; char *p; for (t=manfiles;t;t=t->link) { MANFILE_TAG *tag; char secdigit; bnp = basenameptr(t->s); dot = rindex(bnp,'.'); if (! dot) { fprintf(stderr,"%s: bugcheck: missing dot in check_man_conflicts (%s)\n",__progname,t->s); abort(); } bnl = dot - bnp; tag = t->tag; secdigit = '0' + tag->section; p = catstrings(0,localpathman,"/man",CS_LEN(&secdigit,1),"/",bnp,CS_END); if (exists_any(p) && !virtuallyrmed(p)) { add_conflict(t->s,p); continue; } if (tag->linkext) { p = catstrings(0,localpathman,"/cat",CS_LEN(&secdigit,1),"/",CS_LEN(bnp,bnl),".",tag->linkext,CS_END); } else { p = catstrings(0,localpathman,"/cat",CS_LEN(&secdigit,1),"/",bnp,CS_END); } if (exists_any(p) && !virtuallyrmed(p)) { add_conflict(t->s,p); continue; } p = catstrings(0,localpathman,"/cat",CS_LEN(&secdigit,1),"/",CS_LEN(bnp,bnl),".0",CS_END); if (exists_any(p) && !virtuallyrmed(p)) { add_conflict(t->s,p); continue; } } } /* * Check for conflicts, code shared by include files and lib files. * This would be shared with bin files too, except that the binfiles * list contains full pathnames, whereas incfiles and libfiles contain * pathnames relative to ID and LD. list is the list, path is the * link farm root, and src is the versioned directory (eg, ID). */ static void check_conflicts_simple(STRLIST *list, const char *path, const char *src) { STRLIST *t; char *p; for (t=list;t;t=t->link) { p = catstrings(0,path,"/",t->s,CS_END); if (exists_any(p) && !virtuallyrmed(p)) { add_conflict(catstrings(0,src,"/",t->s,CS_END),p); continue; } } } /* Check for include file conflicts. */ static void check_inc_conflicts(void) { check_conflicts_simple(incfiles,localpathinc,ID); } /* Check for library file conflicts. */ static void check_lib_conflicts(void) { check_conflicts_simple(libfiles,localpathlib,LD); } /* * This routine is essentially mkdir -p. We need to make directories * as necessary for include files, and when SD doesn't exist and we * need to create it to hold the .PUBLISH file. */ static void make_dirs(const char *path) { char *pathtmp; char *slashp; char *t; if (Dflag) printf("[make_dirs: %s]\n",path); pathtmp = catstrings(1,path,CS_END); slashp = rindex(pathtmp,'/'); if (slashp) { while (1) { *slashp = '\0'; if (mkdir(pathtmp,0777) >= 0) { if (Dflag) printf("[make_dirs: mkdir %s succeeded]\n",pathtmp); break; } if (Dflag) { int e; e = errno; printf("[make_dirs: mkdir %s failed (%s)]\n",pathtmp,strerror(e)); errno = e; } if (errno == EEXIST) break; if (errno != ENOENT) { free(pathtmp); return; } t = rindex(pathtmp,'/'); if (t == 0) { free(pathtmp); return; } *slashp = '/'; slashp = t; } if (Dflag) printf("[make_dirs: between loops, %s %s]\n",pathtmp,slashp+1); while (1) { t = index(slashp+1,'/'); *slashp = '/'; if (! t) break; *t = '\0'; if (mkdir(pathtmp,0777) < 0) { if (Dflag) printf("[make_dirs: mkdir %s failed (%s)]\n",pathtmp,strerror(errno)); free(pathtmp); return; } if (Dflag) printf("[make_dirs: mkdir %s OK]\n",pathtmp); slashp = t; } } if (Dflag) printf("[make_dirs: final mkdir %s]\n",pathtmp); mkdir(pathtmp,0777); free(pathtmp); } /* Ensure SD exists. */ static void ensure_SD(void) { if (! exists_dir(SD)) make_dirs(SD); } /* * This routine is essentially strftime() into malloc()ed storage. But * we don't have any way of telling how much space strftime() wants, * so we have to just keep growing until we find we've allocated * enough. Ick. */ static char *alloc_strftime(const char *fmt, const struct tm *tm) { time_t now; int fmtlen; int slop; static char *buf = 0; static int buflen = 0; if (tm == 0) { time(&now); tm = localtime(&now); } fmtlen = strlen(fmt); slop = buflen - fmtlen; if (slop < 1) slop = 1; while (1) { if (buflen < fmtlen+slop) { free(buf); buf = malloc(buflen=fmtlen+slop); } if (strftime(buf,buflen,fmt,tm)) return(buf); if (slop < 32) slop += 16; else slop *= 2; } } /* * Push (a copy of) a string on a STRLIST. * Uses a nil pointer for the tag. */ static void push_str(STRLIST **l, const char *s) { STRLIST *t; t = malloc(sizeof(STRLIST)); t->link = *l; *l = t; t->s = catstrings(1,s,CS_END); t->tag = 0; } /* Reverse a STRLIST. */ static STRLIST *reverse_strlist(STRLIST *list) { STRLIST *newlist; STRLIST *t; newlist = 0; while (list) { t = list; list = t->link; t->link = newlist; newlist = t; } return(newlist); } /* * Dump out the strings in the STRLIST to the FILE *. It's called * dump_report because its only use is to dump out the strings in * "report". */ static void dump_report(STRLIST *report, FILE *f) { STRLIST *t; for (t=report;t;t=t->link) fprintf(f,"%s\n",t->s); } /* * Make a symlink, creating parent directories as necessary if we get * ENOENT from the first attempt to make the link. Return semantics * are as for symlink(2), except that conceivably we could end up * creating some directories and then still failing to create the * link. */ static int make_symlink(const char *to, const char *path) { char *pathtmp; char *slashp; if (symlink(to,path) >= 0) return(0); if (errno != ENOENT) return(-1); pathtmp = catstrings(1,path,CS_END); slashp = rindex(pathtmp,'/'); if (slashp == 0) { free(pathtmp); errno = ENOENT; return(-1); } *slashp = '\0'; make_dirs(pathtmp); free(pathtmp); return(symlink(to,path)); } /* * Try to make a link; this is where -n is implemented (for link * creation). This routine also pushes a line on the report if the * link was created. */ static void attempt_link(const char *path, const char *to) { if (nflag || (make_symlink(to,path) >= 0)) { push_str(&report,catstrings(0,path,"\t-> ",to,CS_END)); } else { fprintf(stderr,"%s: symlink %s->%s: %s\n",__progname,path,to,strerror(errno)); } } /* (Try to) make the binfile links. */ static void make_bin_links(void) { STRLIST *t; if (! binfiles) return; push_str(&report,""); for (t=binfiles;t;t=t->link) { attempt_link( catstrings(0,localpathbin,"/",basenameptr(t->s),CS_END), t->s ); } } /* * (Try to) make the manfile links. Complicated by the need to drop * the link into the correct subdirectory of localpathman, even if the * link-to path doesn't include a man? or cat? subdirectory. */ static void make_man_links(void) { STRLIST *t; MANFILE_TAG *tag; char secdigit; char *bnp; char *dot; int bnl; if (! manfiles) return; push_str(&report,""); for (t=manfiles;t;t=t->link) { bnp = basenameptr(t->s); dot = rindex(bnp,'.'); if (! dot) { fprintf(stderr,"%s: bugcheck: missing dot in make_manpage_links (%s)\n",__progname,t->s); abort(); } bnl = dot - bnp; tag = t->tag; secdigit = '0' + tag->section; if (tag->catfile_p) { attempt_link( catstrings(0,localpathman,"/cat",CS_LEN(&secdigit,1),"/",CS_LEN(bnp,bnl),".0",CS_END), catstrings(0,MD,"/",t->s,CS_END) ); } else if (tag->linkext) { attempt_link( catstrings(0,localpathman,"/man",CS_LEN(&secdigit,1),"/",CS_LEN(bnp,bnl),".",tag->linkext,CS_END), catstrings(0,MD,"/",t->s,CS_END) ); } else { attempt_link( catstrings(0,localpathman,"/man",CS_LEN(&secdigit,1),"/",bnp,CS_END), catstrings(0,MD,"/",t->s,CS_END) ); } } } /* * Make symlinks, easy case - used for incfiles and libfiles. This * routine assumes relative pathnames and parallel directory * structure. As with check_conflicts_simple, this could be used for * binfiles too, except that binfile link-to pathnames are absolute * instead of relative. */ static void make_links_simple(STRLIST *list, const char *path, const char *src) { STRLIST *t; if (! list) return; push_str(&report,""); for (t=list;t;t=t->link) { attempt_link( catstrings(0,path,"/",t->s,CS_END), catstrings(0,src,"/",t->s,CS_END) ); } } /* (Try to) make the incfile links. */ static void make_inc_links(void) { make_links_simple(incfiles,localpathinc,ID); } /* (Try to) make the libfile links. */ static void make_lib_links(void) { make_links_simple(libfiles,localpathlib,LD); } /* * Dump out a STRLIST, for debugging. If dumper is a nil pointer, just * writes the strings; otherwise, calls (*dumper) to dump each one. */ static void debug_dump_list(STRLIST *libfiles, const char *label, void (*dumper)(STRLIST *)) { STRLIST *t; printf("[%s:\n",label); for (t=libfiles;t;t=t->link) { if (dumper) (*dumper)(t); else printf("\t%s\n",t->s); } printf("]\n"); fflush(stdout); } /* * A debug_dump_list dumper routine to print out a manfile. Just like * the default except it also prints out the MANFILE_TAG fields. */ static void dump_man_file(STRLIST *l) { MANFILE_TAG *tag; tag = l->tag; printf("\t%s (sec=%d cat_p=%d)\n",l->s,tag->section,tag->catfile_p); } /* A debug_dump_list dumper routine to print out a conflict entry. */ static void dump_conflict(STRLIST *l) { printf("\t%s vs %s\n",(char *)l->tag,l->s); } /* Check for a .DO_NOT_PUBLISH file in SD. If it's there, croak. */ static void check_do_not_publish(void) { char *t; t = catstrings(0,SD,"/",dot_do_not_publish,CS_END); if (exists_any(t)) { fprintf(stderr,"%s: %s exists\n",__progname,t); exit(1); } } /* * Setup and initial checking for publishing. */ static void publish_setup(void) { /* Determine directory names. */ SD = catstrings(1,localbin,"/",pkg_ver,CS_END); MD = catstrings(1,localman,"/",pkg_ver,CS_END); ID = catstrings(1,localinc,"/",pkg_ver,CS_END); LD = catstrings(1,locallib,"/",pkg_ver,CS_END); if (Dflag) { printf("[SD is %s]\n",SD); printf("[MD is %s]\n",MD); printf("[ID is %s]\n",ID); printf("[LD is %s]\n",LD); } /* Check for .DO_NOT_PUBLISH */ check_do_not_publish(); } /* * Find the files we'd like to publish. */ static void publish_findfiles(void) { /* Find the files we would like to link to. */ find_bin_files(); if (Dflag) debug_dump_list(binfiles,"bin file list",0); find_man_files(); if (Dflag) debug_dump_list(manfiles,"man file list",dump_man_file); find_inc_files(); if (Dflag) debug_dump_list(incfiles,"inc file list",0); find_lib_files(); if (Dflag) debug_dump_list(libfiles,"lib file list",0); } /* * Check for conflicts. */ static void publish_conflicts(void) { /* Check for conflicts. */ conflicts = 0; check_bin_conflicts(); check_man_conflicts(); check_inc_conflicts(); check_lib_conflicts(); if (Dflag) debug_dump_list(conflicts,"conflicts found",dump_conflict); } /* * Generate the publication report. This routine is responsible for * printing the report and exiting if conflicts exist. */ static void publish_genreport(void) { /* Generate the report header. */ report = 0; push_str(&report,"================================================================================"); push_str(&report,alloc_strftime(" publish -- invoked %a %b %e %T %Z %Y",0)); push_str(&report,""); push_str(&report,catstrings(0," package name: ",pkg_ver,CS_END)); push_str(&report,""); if (nflag) { push_str(&report," * operating in \"no install\" mode"); push_str(&report,""); } push_str(&report,"================================================================================"); push_str(&report,""); /* If conflicts, just complain about them (depending on -n and/or -a) and go away. */ if (conflicts) { if (aflag) { fprintf(stderr,"%s: conflicts encountered for %s\n",__progname,pkg_ver); } else { STRLIST *t; push_str(&report,"the following conflicts were encountered"); push_str(&report,"----------------------------------------"); push_str(&report,""); for (t=conflicts;t;t=t->link) { push_str(&report,catstrings(0,"existing ",(char *)t->tag,"\tvs. ",t->s,CS_END)); } report = reverse_strlist(report); dump_report(report,stdout); if (! nflag) { char *dnpp; FILE *f; ensure_SD(); dnpp = catstrings(0,SD,"/",dot_do_not_publish,CS_END); f = fopen(dnpp,"w"); if (f == 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,dnpp,strerror(errno)); exit(1); } dump_report(report,f); fclose(f); } } exit(1); } } /* * Actually do the publiciation. */ static void publish_doit(void) { /* Okay, go do the links. */ if (nflag) { push_str(&report,"the following symbolic links would have been created"); push_str(&report,"----------------------------------------------------"); } else { push_str(&report,"the following symbolic links were created"); push_str(&report,"-----------------------------------------"); } make_bin_links(); make_man_links(); make_inc_links(); make_lib_links(); } /* * Last step of publishing. Take the report, potentially save it to * SD/.PUBLISH, and potentially dump it to stdout. */ static void publish_done(void) { report = reverse_strlist(report); if (! nflag) { char *dnpp; FILE *f; ensure_SD(); dnpp = catstrings(0,SD,"/",dot_publish,CS_END); f = fopen(dnpp,"w"); if (f == 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,dnpp,strerror(errno)); fprintf(stderr,"%s: no .PUBLISH file created\n",__progname); } else { dump_report(report,f); fclose(f); } } if (! qflag) dump_report(report,stdout); } /* Main routine for publishing. */ static void do_publish(void) { publish_setup(); publish_findfiles(); publish_conflicts(); publish_genreport(); publish_doit(); publish_done(); } /* * A STRLIST version of sort -ru. Sorts the list backwards, returning * it in lexicographically decreasing order, with identical elements * removed. We sort backwards so that a parent directory comes * _after_ all its children in the returned list. The sort is a * simple mergesort; we do a card-shuffle split of the argument into * two halves, recurse on the halves, and merge them. Dropping * duplicates and sorting are of course actually done in the merge * phase. (We don't actually care about sorting except to (a) make * sure a parent dir comes after its subdirs and (b) to remove dups.) */ static STRLIST *sort_ru(STRLIST *list) { STRLIST *l[2]; /* The two half-lists. */ STRLIST *t; /* Temporary. */ STRLIST **tail; /* Tail pointer of merged list. */ STRLIST *last; /* Last entry added to merged list, nil if none yet. */ int flip; /* Which of l[] is used, in shuffle-split and merge. */ /* Split. The halves end up getting reversed, but since they're not sorted yet, this doesn't matter. */ l[0] = 0; l[1] = 0; flip = 0; while (list) { t = list; list = t->link; t->link = l[flip]; l[flip] = t; flip = ! flip; } if (! l[1]) return(l[0]); /* Recurse on the halves. */ l[0] = sort_ru(l[0]); l[1] = sort_ru(l[1]); /* Merge. Since here we can't tolerate having the list get reversed, we do the tconc thing to build the list right way round. */ tail = &list; last = 0; while (l[0] || l[1]) { flip = (!l[0] || (l[1] && (strcmp(l[0]->s,l[1]->s) < 0))); t = l[flip]; l[flip] = t->link; if (last && !strcmp(t->s,last->s)) { free(t->s); free(t); } else { *tail = t; last = t; tail = &t->link; } } *tail = 0; return(list); } /* * The bulk of unpublishing. We read the .PUBLISH file, looking for * lines containing "->" with at least one whitespace character on * either side. When we find such a line, we treat it as indicating a * symlink we should remove. We parse the lines with a simple state * machine. * * Inability to read the .PUBLISH file is fatal to the run; for the * command line, this is TRT, and for republishing, our caller should * have checked that .PUBLISH exists first; thus, if can't read it, * something odd is wrong, so we want to cough and die. * * Since republish with -n wants things that would have been destroyed * without -n to appear nonexistent for conflict checking, we are * prepared to make a list of everything we would have removed. */ static void unpublish_it(const char *p_v, STRLIST **vrm) { FILE *f; /* .PUBLISH file being read. */ char *pubpath; /* Path to .PUBLISH. */ char *line; /* Line from .PUBLISH. */ char *cp; /* Temporary. */ char *firstend; /* End of first pathname on line. */ char *secondstart; /* Start of second pathname on line. */ int state; /* State machine state: */ #define S_NOTHING 1 /* no whitespace found */ #define S_FIRSTWHITE 2 /* first whitespace found */ #define S_DASH 3 /* whitespace - found */ #define S_ARROW 4 /* whitespace - > found */ #define S_SECONDWHITE 5 /* whitespace - > whitespace found */ #define S_SUCCESS 6 /* whitespace - > whitespace found, ptr saved */ STRLIST *incdirs; /* List of subdirs of localpathinc we may want to rmdir. */ STRLIST *remcats; /* List of catfiles we removed along with manfiles. */ STRLIST *t; /* Temporary. */ char *sd; /* First, find and open the .PUBLISH file. */ sd = catstrings(1,localbin,"/",p_v,CS_END); if (Dflag) printf("[unpublish SD at %s]\n",sd); pubpath = catstrings(1,sd,"/",dot_publish,CS_END); f = fopen(pubpath,"r"); if (f == 0) { fprintf(stderr,"%s: can't read %s\n",__progname,pubpath); exit(1); } /* No include directories or catfiles yet. */ incdirs = 0; remcats = 0; /* Read the .PUBLISH file. */ while ((line=get_line(f))) { if (Dflag) printf("[read .PUBLISH line %s]\n",line); /* Got a line; walk the state machine down it. */ state = S_NOTHING; for (cp=line;*cp;cp++) { switch (state) { case S_NOTHING: if (Cisspace(*cp)) { state = S_FIRSTWHITE; firstend = cp; } break; case S_FIRSTWHITE: if (*cp == '-') state = S_DASH; else if (!Cisspace(*cp)) state = S_NOTHING; break; case S_DASH: if (*cp == '>') state = S_ARROW; else if (Cisspace(*cp)) state = S_FIRSTWHITE; else state = S_NOTHING; break; case S_ARROW: if (Cisspace(*cp)) state = S_SECONDWHITE; else state = S_NOTHING; break; case S_SECONDWHITE: if (! Cisspace(*cp)) { state = S_SUCCESS; secondstart = cp; } break; case S_SUCCESS: break; } } /* Did we find what we were looking for? */ if (state == S_SUCCESS) { /* Yes. */ char *from; char *to; int tolen; char *linkbuf; int rlrv; /* Pick out the from and to paths, each in its own storage. */ from = catstrings(1,CS_LEN(line,firstend-line),CS_END); to = catstrings(1,secondstart,CS_END); if (Dflag) printf("[parse OK, from %s to %s]\n",from,to); /* Check to make sure the link exists and points to where it should. */ tolen = strlen(to); linkbuf = malloc(tolen+1); rlrv = readlink(from,linkbuf,tolen+1); if (rlrv < 0) { /* Couldn't readlink() it. */ if (Dflag) { int e; e = errno; printf("[readlink error: %s]\n",strerror(e)); errno = e; } do { if (errno == ENOENT) { /* Maybe it's a catfile we removed when we earlier removed its corresponding manfile. */ for (t=remcats;t;t=t->link) if (!strcmp(from,t->s)) break; if (t) { if (! qflag) printf("%s: (%s already removed)\n",__progname,from); break; } } fprintf(stderr,"%s: %s not removed: %s\n",__progname,from,strerror(errno)); } while (0); } else if ((rlrv != tolen) || bcmp(linkbuf,to,tolen)) { /* readlink() worked, but link-to length is wrong or the string doesn't match what was in the .PUBLISH file. */ if (Dflag) printf("[link to wrong place]\n"); fprintf(stderr,"%s: %s not removed: link doesn't point where expected\n",__progname,from); } else if (!nflag && (unlink(from) < 0)) { /* It's a link and it points where it should, but we couldn't remove it for some reason. */ if (Dflag) { int e; e = errno; printf("[unlink failed: %s]\n",strerror(e)); errno = e; } fprintf(stderr,"%s: %s not removed: %s\n",__progname,from,strerror(errno)); } else { /* * We removed the link (or didn't try because of -n). Check * to see if it's a manfile (if so, try removing catfiles) * or an include file (if so, remember the directory for * possible removal later). If we succeed in removing a * catfile, remember the name for later in case that file * was explicitly published, so we don't produce noise about * the catfile not existing when we ourselves removed it. */ /* These three variables are set the first time we enter this code; mandir==0 is the signal we haven't yet set them. The length variables are for strncmp() calls. */ static char *mandir = 0; /* MD/man */ static int mdlen; /* strlen(mandir) */ static int idlen; /* strlen(localpathinc) */ char *cp; if (nflag && vrm) push_str(vrm,from); if (! qflag) printf("%s: %s %sremoved\n",__progname,from,nflag?"would have been ":""); if (! mandir) { mandir = catstrings(1,localpathman,"/man",CS_END); mdlen = strlen(mandir); idlen = strlen(localpathinc); } if (! strncmp(from,mandir,mdlen)) { /* It's a manfile. Change "man" to "cat" to get the same-extension catfile. We bash on the from string, knowing we have no use for it after we're done with this. */ from[mdlen-3] = 'c'; from[mdlen-2] = 'a'; from[mdlen-1] = 't'; if (Dflag) printf("[mandir match, same-extension catfile %s]\n",from); if (nflag) { printf("%s: (%s, if present, would be removed too)\n",__progname,from); if (vrm) push_str(vrm,from); } else if (unlink(from) >= 0) { if (! qflag) printf("%s: (%s removed too)\n",__progname,from); push_str(&remcats,from); } /* Now try replacing the extension of the catfile with .0. */ cp = rindex(from,'.'); if (cp) { from = catstrings(1,CS_LEN(CS_FREE(from),cp-from),".0",CS_END); if (Dflag) printf("[.0 catfile %s]\n",from); if (nflag) { printf("%s: (%s, if present, would be removed too)\n",__progname,from); if (vrm) push_str(vrm,from); } else if (unlink(from) >= 0) { if (! qflag) printf("%s: (%s removed too)\n",__progname,from); push_str(&remcats,from); } } } else if (!strncmp(from,localpathinc,idlen) && (from[idlen] == '/')) { /* It's an include file. Save the directories all the way up from the one containing the include file to ID (but don't save ID itself; we never want to remove that, even if we do leave it empty). */ if (Dflag) printf("[incdir match]\n"); cp = rindex(from,'/'); while (cp-from > idlen) { *cp = '\0'; if (Dflag) printf("[saving %s]\n",from); push_str(&incdirs,from); cp = rindex(from,'/'); } } } free(to); free(from); free(linkbuf); } else { /* No. */ if (Dflag) printf("[parse failure, skipping]\n"); } /* Free the line. */ free(line); } fclose(f); /* We no longer have any use for the list of removed catfiles. */ while (remcats) { t = remcats; remcats = t->link; free(t->s); free(t); } /* Done reading .PUBLISH; now go through and remove any of those include directories that we can (and be silent about "not empty" errors). */ /* First, sort the list. */ incdirs = sort_ru(incdirs); if (Dflag) debug_dump_list(incdirs,"saved incdirs",0); /* Now, try to torch each one. The sort placed each directory before its parent in the list, so we're doing them in the correct order. Be careful to obey -n. */ while (incdirs) { t = incdirs; incdirs = t->link; if (nflag) { printf("%s: (%s, if empty, would have been removed too)\n",__progname,t->s); } else if (rmdir(t->s) >= 0) { if (! qflag) printf("%s: (%s removed too)\n",__progname,t->s); } else if (errno != ENOTEMPTY) { fprintf(stderr,"%s: rmdir %s: %s\n",__progname,t->s,strerror(errno)); } free(t->s); free(t); } /* Now, torch .PUBLISH itself, and if SD becomes empty thereby, that too. (The idea is that if SD contains only .PUBLISH, it was probably created for the purpose by the publish run that published the package.) */ if (nflag || (unlink(pubpath) >= 0)) { if (nflag) printf("%s: %s would have been removed\n",__progname,pubpath); else if (! qflag) printf("%s: %s removed\n",__progname,pubpath); cp = rindex(pubpath,'/'); if (cp) { *cp = '\0'; if (nflag) { printf("%s: (%s, if empty, would have been removed too)\n",__progname,pubpath); } else if (rmdir(pubpath) >= 0) { if (! qflag) printf("%s: (%s removed too)\n",__progname,pubpath); } } } else { fprintf(stderr,"%s: %s not removed: %s\n",__progname,pubpath,strerror(errno)); } #undef S_NOTHING #undef S_FIRSTWHITE #undef S_DASH #undef S_ARROW #undef S_SECONDWHITE #undef S_SUCCESS } /* * Main routine for unpublishing. The body of unpublishing is broken * out into a separate routine for the benefit of republish mode * (which wants to unpublish stuff too). */ static void do_unpublish(void) { unpublish_it(pkg_ver,0); } /* * This routine is essentially rm -r. Used by -r without -k. This * ignores nflag, since it's not called when -n is in effect. * * Annoyingly, unlink() on a directory doesn't always return EISDIR; * when called by root, some OSes simply orphan the directory. This * means we have little choice but to waste a bunch of syscalls. */ static int rm_r(const char *dn) { DIR *dir; struct dirent *d; int errs; char *t; errs = 0; dir = opendir(dn); if (dir) { while ((d = readdir(dir))) { if ( (d->d_fileno == 0) || ( (d->d_name[0] == '.') && ( !d->d_name[1] || ( (d->d_name[1] == '.') && !d->d_name[2] ) ) ) ) continue; t = catstrings(1,dn,"/",&d->d_name[0],CS_END); if (rm_r(t) < 0) errs = 1; free(t); } closedir(dir); } if ( !errs && (rmdir(dn) < 0) && ((errno != ENOTDIR) || (unlink(dn) < 0)) ) { fprintf(stderr,"%s: can't remove %s: %s\n",__progname,dn,strerror(errno)); return(-1); } return(errs?-1:0); } /* * Destroy the installed-binaries directories for a pkg-ver. Used by * unpublish mode when -k is not in effect. Be careful to obey -n. */ static void destroy_it(const char *p_v) { if (nflag) { printf("%s: %s/%s would have been destroyed\n",__progname,localbin,p_v); printf("%s: %s/%s would have been destroyed\n",__progname,localman,p_v); printf("%s: %s/%s would have been destroyed\n",__progname,localinc,p_v); printf("%s: %s/%s would have been destroyed\n",__progname,locallib,p_v); } else { auto void foo(const char *s) { char *t; t = catstrings(1,s,"/",p_v,CS_END); if (exists_any(t) && (rm_r(t) == 0)) { printf("%s: %s destroyed\n",__progname,t); } free(t); } foo(localbin); foo(localman); foo(localinc); foo(locallib); } } /* * Do the unpublish-other-versions part of republishing. */ static void republish_unpublish(void) { STRLIST *contents; STRLIST *t; int pl; pl = strlen(pkg); contents = dircontents(localbin); while (contents) { t = contents; contents = t->link; if ( !bcmp(t->s,pkg,pl) && (t->s[pl] == '-') && !index(t->s+pl+1,'-') && strcmp(t->s+pl+1,ver) ) { unpublish_it(t->s,&virtualrm); if (! kflag) destroy_it(t->s); } free(t->s); free(t); } } /* * Main routine for republishing. This looks a lot like publishing, * except that just before we check for conflicts, we scan for other * versions of the current package, and unpublish them when found, * possibly also destroying them entirely. */ static void do_republish(void) { publish_setup(); publish_findfiles(); republish_unpublish(); publish_conflicts(); publish_genreport(); publish_doit(); publish_done(); } /* * Code to set up localbin, etc, per the main comment header. */ static void setup_paths(void) { int i; PATHDEF *p; const char *localroot = 0; char envbuf[64]; /* all envvar names are of known lengths */ const char *vv; for (i=0;i<(sizeof(pathdef)/sizeof(pathdef[0]));i++) { p = &pathdef[i]; snprintf(&envbuf[0],sizeof(envbuf),"PUBLISH_LOCAL%s",p->foo); vv = getenv(&envbuf[0]); if (vv) { snprintf(p->string,STRING_LIMIT,"%s",vv); } else { snprintf(&envbuf[0],sizeof(envbuf),"LOCAL%s",p->foo); vv = getenv(&envbuf[0]); if (vv) { snprintf(p->string,STRING_LIMIT,"%s",vv); } else { if (localroot == 0) { localroot = getenv("LOCALROOT"); if (localroot == 0) localroot = def_prefix; } snprintf(&envbuf[0],sizeof(envbuf),"PUBLISH_%s",p->foo); vv = getenv(&envbuf[0]); if (vv == 0) vv = p->defsuf; snprintf(p->string,STRING_LIMIT,"%s%s",localroot,vv); } } } } /* main() is pretty boring. */ int main(int, char **); int main(int ac, char **av) { #ifdef UNPUBLISH_ARGV0 if (!strcmp(basenameptr(av[0]),UNPUBLISH_ARGV0)) uflag = 1; #endif #ifdef REPUBLISH_ARGV0 if (!strcmp(basenameptr(av[0]),REPUBLISH_ARGV0)) rflag = 1; #endif handleargs(ac,av); if (! (uflag|rflag|pflag)) pflag = 1; setup_paths(); if (nflag) qflag = 0; if (pflag) do_publish(); if (uflag) do_unpublish(); if (rflag) do_republish(); exit(0); }