/* * Utility functions to add scm awareness * to applications, and build utilities. */ /* * Sections of this code are form patch which is:- * Copyright 1986 Larry Wall * Copyright 1992, 1993, 1997 Free Software Foundation, Inc. * * The rest is (C) 2002 Roger Gammans * * All of the code here is Licenced by the GPL */ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ extern char * program_name; #include #include #include #include #include #include #include #include #ifdef __STDC__ # include # define vararg_start va_start #else # define vararg_start(ap,p) va_start (ap) # if HAVE_VARARGS_H # include # else typedef char *va_list; # define va_dcl int va_alist; # define va_start(ap) ((ap) = (va_list) &va_alist) # define va_arg(ap, t) (((t *) ((ap) += sizeof (t))) [-1]) # define va_end(ap) # endif #endif #define NULL_DEVICE "/dev/null" #define GENERIC_OBJECT void #define VERBOSE 1 const int verbosity =0; /* * Patch's base_name function to keep this almost * as portable as patch ;-) */ /* basename.c -- return the last element in a path */ #ifndef FILESYSTEM_PREFIX_LEN #define FILESYSTEM_PREFIX_LEN(f) 0 #endif #ifndef ISSLASH #define ISSLASH(c) ((c) == '/') #endif /* In general, we can't use the builtin `basename' function if available, since it has different meanings in different environments. In some environments the builtin `basename' modifies its argument. */ char * base_name (char const *name) { char const *base = name += FILESYSTEM_PREFIX_LEN (name); for (; *name; name++) if (ISSLASH (*name)) base = name + 1; return (char *) base; } /* Utility functions which do the hard work * these are from patch-2.5. * * RCS-Id->'util.c,v 1.24 1997/07/10 08:16:12 eggert' */ static char const DEV_NULL[] = NULL_DEVICE; static char const SCCSPREFIX[] = "s."; static char const GET[] = "get "; static char const GET_LOCKED[] = "get -e "; static char const SCCSDIFF1[] = "get -p "; static char const SCCSDIFF2[] = "|diff - %s"; static char const RCSSUFFIX[] = ",v"; static char const CHECKOUT[] = "co %s"; static char const CHECKOUT_LOCKED[] = "co -l %s"; static char const RCSDIFF1[] = "rcsdiff %s"; #define pfatal fatal #ifdef __STDC__ void fatal (char const *format, ...) #else /*VARARGS1*/ void fatal (format, va_alist) char const *format; va_dcl #endif { int errnum = errno; va_list args; fprintf (stderr, "%s: **** ", program_name); vararg_start (args, format); vfprintf (stderr, format, args); va_end (args); fflush (stderr); /* perror bypasses stdio on some hosts. */ errno = errnum; perror (" "); fflush (stderr); exit (2); } int systemic ( char const *command) { #if 0 if (debug & 8) say ("+ %s\n", command); #endif fflush (stdout); return system (command); } #ifdef __STDC__ void say (char const *format, ...) #else /*VARARGS1*/ void say (format, va_alist) char const *format; va_dcl #endif { va_list args; vararg_start (args, format); vfprintf (stdout, format, args); va_end (args); fflush (stdout); } void memory_fatal(void) { fatal ("out of memory"); } GENERIC_OBJECT * xmalloc (size_t size) { register GENERIC_OBJECT *p = malloc (size); if (!p) memory_fatal (); return p; } /* Place into QUOTED a quoted version of ARG suitable for `system'. Return the length of the resulting string (which is not null-terminated). If QUOTED is null, return the length without any side effects. */ /* Written by Paul Eggert */ size_t quote_system_arg (char *quoted,char const * arg) { char const *a; size_t len = 0; /* Scan ARG, copying it to QUOTED if QUOTED is not null, looking for shell metacharacters. */ for (a = arg; ; a++) { char c = *a; switch (c) { case 0: /* ARG has no shell metacharacters. */ return len; case '=': if (*arg == '-') break; /* Fall through. */ case '\t': case '\n': case ' ': case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case ';': case '<': case '>': case '?': case '[': case '\\': case '^': case '`': case '|': case '~': { /* ARG has a shell metacharacter. Start over, quoting it this time. */ len = 0; c = *arg++; /* If ARG is an option, quote just its argument. This is not necessary, but it looks nicer. */ if (c == '-' && arg < a) { c = *arg++; if (quoted) { quoted[len] = '-'; quoted[len + 1] = c; } len += 2; if (c == '-') while (arg < a) { c = *arg++; if (quoted) quoted[len] = c; len++; if (c == '=') break; } c = *arg++; } if (quoted) quoted[len] = '\''; len++; for (; c; c = *arg++) { if (c == '\'') { if (quoted) { quoted[len] = '\''; quoted[len + 1] = '\\'; quoted[len + 2] = '\''; } len += 3; } if (quoted) quoted[len] = c; len++; } if (quoted) quoted[len] = '\''; return len + 1; } } if (quoted) quoted[len] = c; len++; } } /* Return "RCS" if FILENAME is controlled by RCS, "SCCS" if it is controlled by SCCS, and 0 otherwise. READONLY is nonzero if we desire only readonly access to FILENAME. FILESTAT describes FILENAME's status or is 0 if FILENAME does not exist. If successful and if GETBUF is nonzero, set *GETBUF to a command that gets the file; similarly for DIFFBUF and a command to diff the file. *GETBUF and *DIFFBUF must be freed by the caller. */ char const * version_controller (char const *filename, int readonly, struct stat const *filestat, char **getbuf, char **diffbuf) { struct stat cstat; char const *filebase = base_name (filename); char const *dotslash = *filename == '-' ? "./" : ""; size_t dir_len = filebase - filename; size_t filenamelen = strlen (filename); size_t maxfixlen = sizeof "SCCS/" - 1 + sizeof SCCSPREFIX - 1; size_t maxtrysize = filenamelen + maxfixlen + 1; size_t quotelen = quote_system_arg (0, filename); size_t maxgetsize = sizeof GET_LOCKED + quotelen + maxfixlen; size_t maxdiffsize = (sizeof SCCSDIFF1 + sizeof SCCSDIFF2 + sizeof DEV_NULL - 1 + 2 * quotelen + maxfixlen); char *trybuf = xmalloc (maxtrysize); char const *r = 0; strcpy (trybuf, filename); #define try1(f,a1) (sprintf (trybuf + dir_len, f, a1), stat (trybuf, &cstat) == 0) #define try2(f,a1,a2) (sprintf (trybuf + dir_len, f, a1,a2), stat (trybuf, &cstat) == 0) /* Check that RCS file is not working file. Some hosts don't report file name length errors. */ if ((try2 ("RCS/%s%s", filebase, RCSSUFFIX) || try1 ("RCS/%s", filebase) || try2 ("%s%s", filebase, RCSSUFFIX)) && ! (filestat && filestat->st_dev == cstat.st_dev && filestat->st_ino == cstat.st_ino)) { if (getbuf) { char *p = *getbuf = xmalloc (maxgetsize); sprintf (p, readonly ? CHECKOUT : CHECKOUT_LOCKED, dotslash); p += strlen (p); p += quote_system_arg (p, filename); *p = '\0'; } if (diffbuf) { char *p = *diffbuf = xmalloc (maxdiffsize); sprintf (p, RCSDIFF1, dotslash); p += strlen (p); p += quote_system_arg (p, filename); *p++ = '>'; strcpy (p, DEV_NULL); } r = "RCS"; } else if (try2 ("SCCS/%s%s", SCCSPREFIX, filebase) || try2 ("%s%s", SCCSPREFIX, filebase)) { if (getbuf) { char *p = *getbuf = xmalloc (maxgetsize); sprintf (p, readonly ? GET : GET_LOCKED); p += strlen (p); p += quote_system_arg (p, trybuf); *p = '\0'; } if (diffbuf) { char *p = *diffbuf = xmalloc (maxdiffsize); strcpy (p, SCCSDIFF1); p += sizeof SCCSDIFF1 - 1; p += quote_system_arg (p, trybuf); sprintf (p, SCCSDIFF2, dotslash); p += strlen (p); p += quote_system_arg (p, filename); *p++ = '>'; strcpy (p, DEV_NULL); } r = "SCCS"; } free (trybuf); return r; } /* Get FILENAME from version control system CS. The file already exists if EXISTS is nonzero. Only readonly access is needed if READONLY is nonzero. Use the command GETBUF to actually get the named file. Store the resulting file status into *FILESTAT. Return nonzero if successful. */ int version_get (char const *filename, char const *cs, int readonly, char const *getbuf, struct stat *filestat) { if (verbosity == VERBOSE) say ("Getting file `%s' from %s%s...\n", filename, cs, readonly ? "" : " with lock"); if (systemic (getbuf) != 0) fatal ("can't get file `%s' from %s", filename, cs); if ((!filestat) || (stat (filename, filestat) != 0)) pfatal ("%s", filename); return 1; } /******************************************************************** * End of copied code... (the majority you'll note ;-) * * These are the functions we provide... */ /* * We don't support the mode argument as * callers presumable aren't going to call * us with O_CREAT. In fact we don't support * O_CREAT anyway. */ int scm_open(const char *pathname, int flags) { int readonly =1; const char * cs=0; char * getbuf; struct stat statbuf; if (flags & (O_TRUNC | O_APPEND | O_WRONLY | O_RDWR )) { //The is being open in with write access. readonly=0; } if (stat(pathname,&statbuf) !=0){ cs= version_controller(pathname,readonly,0 ,&getbuf, 0); if (cs) { version_get(pathname,cs,readonly,getbuf,0); } } return open(pathname,flags); } FILE *scm_fopen (const char *path, const char *mode) { int readonly =1; const char * cs=0; char * getbuf; struct stat statbuf; if (strpbrk(mode,"aw+")) { readonly=0; } if (stat(path,&statbuf) !=0){ cs= version_controller(path,readonly,0 ,&getbuf, 0); if (cs) { version_get(path,cs,readonly,getbuf,0); } return fopen(path,mode); } } FILE *scm_freopen (const char *path, const char *mode, FILE *stream) { int readonly =1; const char * cs=0; char * getbuf; struct stat statbuf; if (strpbrk(mode,"aw+")) { readonly=0; } if (stat(path,&statbuf) !=0){ cs= version_controller(path,readonly,0 ,&getbuf, 0); if (cs) { version_get(path,cs,readonly,getbuf,0); } } return freopen(path,mode,stream); }