static char rcsid[] = "$Id: fxpand.c,v 1.1 1992/09/06 19:31:32 mike Exp $"; /* $Log: fxpand.c,v $ * Revision 1.1 1992/09/06 19:31:32 mike * Initial revision * */ #if 0 /* there are star-slashes in the comment below */ /* fxpand.c : fxpand(): expand file names * Input: * name: A file name which can contain: ?, \, [], [^] and * * If name ends with "." (eg *.) then only names with no extensions will * be matched. * If name beings with ~ then: "~/" expands to "$(HOME)/" and "~name" * expands to "". * MS-DOS: \ is same as /. Does not quote. * UNIX: \ quotes the next character. * ATARI (from jwahar r. bammi (bammi@cadence.com)): * Like MS-DOS, \ == /, except that we have the POSIX opendir() etc. * onlyone: If TRUE return the first match otherwise all matches are * returned. For example, this controls how "*" is expanded. * nopath: If TRUE, only return the matching file names. Otherwise * return the path and name. For example, "/foo/bar" => "bar". * slash_dir: If TRUE, append a slash to the end of a file name if the * file is a directory. For example, input of "dir" would generate * "dir/". * name_heap: * NULL: Call process_fname. * Pointer to char[]: Stuff the file names in here. The names are * separated by a blank and there are no trailing blanks. * process_fname: A function which is passed a char *. The string * contains the expanded file name. * Returns: 0 if all OK, >1 an error to be returned by fxpand(). * Output: * 0: All OK * 1: Something screwed up. Most likely the name is bad. * n: An error code returned by process_fname(). * * Notes: * When compiled on Apollo, this routine also works with the Domain/OS * "//" notation. This is mostly luck - I don't collapse "/"s and a * relaxed check lets this work. * Input error checking is pretty grim. * Unix Notes: * When wildcard matching, hidden files (those that start with ".") are * skipped unless you ask to see them. To match ".fred", you could use * ".f*". To match "fred/.sam/geo", you would need something like * "fred/.s*/g*". * When appending slashes (slash_dir), expanding something like "*" can be * very slow. This is because I have to stat() to find out if the file * is a directory and stat() can take a long time to stat files over nfs * mounts, follow links, etc. * * Craig Durland * * Do not delete this double quote! " (You are not expected to understand this) */ #endif /* Copyright 1989, 1990, 1991 Craig Durland * Distributed under the terms of the GNU General Public License. * Distributed "as is", without warranties of any kind, but comments, * suggestions and bug reports are welcome. */ #define _HPUX_SOURCE /* for ANSI C on HP-UX */ #define _BSD_SOURCE /* for ANSI C on Apollo BSD */ #define _POSIX_SOURCE /* for ANSI C on IBM AIX */ #include #include "os.h" #include "const.h" extern char *strchr(), *getenv(), *strcpy(), *strcat(); static char *name_list; static int prepend_blank; static int stuff_name(name) char *name; { if (prepend_blank) strcat(name_list," "); else prepend_blank = TRUE; strcat(name_list,name); return 0; } #if MSDOZ #define SLASH "/" /* "/" or "\\", your preference */ /* A note about MS-DOS: Its file find routines are brain dead: If you * ask for a directory, you will get all file types. Also, since * there is a file type==0, you can't just use AND to filter out * unwanted types. What a pain in the butt! */ #include #ifdef __TSC__ /* ibmdir.h has definitions for Lattice C structures */ #include "ibmdir.h" #include "char.h" #endif /* __TSC__ */ typedef struct FILEINFO FI; /* in dos.h */ #define FATTR (FA_ARCHIVE | FA_RD_ONLY | FA_DIRECTORY) /* file attributes */ fxpand(name, onlyone,nopath,slash_dir, name_heap,process_fname) char *name,*name_heap; pfi process_fname; { char path[128], word[100], *ptr, *qtr, tbuf[128]; FI de; int atend, eval, found, needadot, path_len, s; if (name_heap) /* store the found file names in name_heap */ { process_fname = stuff_name; *(name_list = name_heap) = '\0'; prepend_blank = FALSE; } *path = '\0'; if (*name == '~') { name++; if (ptr = getenv("HOME")) strcpy(path,ptr); } else if (name[1] == ':') { strncpy(path,name,2); path[2] = '\0'; name += 2; } atend = FALSE; needadot = FALSE; while (!atend) { atend = get_dpart(&name,word,&eval); path_len = strlen(path); uppercase(word); /* Since the directory entries are uppercase */ if (eval) /* wildcards in need of expansion */ { if (word[strlen(word)-1] == '.') needadot = TRUE; found = FALSE; ptr = path +path_len; strcpy(ptr,"*.*"); if (dfind(&de,path,FATTR)) return 1; /* No files found */ *ptr = '\0'; /* get rid of "*.*" */ do /* look at all entries in this directory */ { if (!atend) /* only care about directories */ { if (de.attr != FA_DIRECTORY) continue; } else if (de.attr && /* if de.attr==0, its a regular old file */ (de.attr & FATTR) == 0) continue; ptr = qtr = de.name; if (needadot && !strchr(ptr,'.')) ptr = strcat(strcpy(tbuf,ptr),"."); if (*qtr != '.' && wildmat(ptr,word)) /* ignore . & .. */ { found = TRUE; if (!atend) /* something like foo//bar */ { strcat(strcat(path,qtr), SLASH); break; } else /* something like foo/ */ { strcpy(path +path_len, qtr); s = procz(process_fname, slash_dir && (de.attr == FA_DIRECTORY), path, nopath ? path_len : 0); path[path_len] = '\0'; if (s) return s; if (onlyone) break; } } } while (!dnext(&de)); if (!found) return 1; } else { strcpy(path + path_len, word); if (atend) /* all done */ return procz(process_fname, slash_dir && is_dir(path), path, nopath ? path_len : 0); /* ??? if (!is_dir(path)) return 1; /* Make sure path is real */ } } /* end while */ return 0; } static int procz(process_fname, slash_it, path, path_len) pfi process_fname; char *path; { if (slash_it && (path[path_len] != '\0')) strcat(path, "/"); return (*process_fname)(path + path_len); } /* * Notes: * For some (unknown to me) reason, FA_DIRECTORY matches everything, * not just directories. * WARNING: * This routine changes the DTA. If you are in the middle of a * dfind/dnext loop, this will mess that up. */ static int is_dir(path) char *path; { FI de; return (!dfind(&de,path,FA_DIRECTORY) && (de.attr == FA_DIRECTORY)); } /* MS-DOS stuff for get_dpart() */ #define ASLASH case '/': case '\\' #define GOTTA_EVAL case '?': case '[': case '*' #endif /* MSDOZ */ #if ATARI /* Atari has Posix opendir(), etc */ #undef POSIX_OS #define POSIX_OS 1 /* turn on POSIX and UX_OS */ #endif /* ATARI */ #if UX_OS /* and Atari */ #define BADGETPW 1 /* 1 if system getpw... routines are screwed up */ #include #include #include /* Posix, SysV: HP-UX, Apollo SysV, DEC, Atari */ /* defined(POSIX) is a DECism */ #if POSIX_OS || SYSV_OS || defined(POSIX) #include #else /* Pure BSD: Apollo bsd4.3 */ #include #endif static int get_dpart(), getpwhome(), procz(), is_dir(); /* cases to check: * "~", "~fred", "/", "~/", "" */ fxpand(name, onlyone,nopath,slash_dir, name_heap,process_fname) char *name,*name_heap; pfi process_fname; { char path[512], word[256], *ptr, *qtr, tbuf[256]; DIR *dir; #if POSIX_OS || SYSV_OS || defined(POSIX) struct dirent *dtr; #else /* Apollo bsd4.3, (some)DEC */ struct direct *dtr; #endif int atend, needadot, eval, found, path_len, s, skip_dot_files; /* ignore names starting with "." */ struct passwd *pd; if (name_heap) /* store the found file names in name_heap */ { process_fname = stuff_name; *(name_list = name_heap) = '\0'; prepend_blank = FALSE; } *path = '\0'; if (*name == '~') /* csh/ksh home directory expansion */ { name++; if (ISSLASH(*name) || *name == '\0') /* ~/foo/bar or ~ */ { if (ptr = getenv("HOME")) strcpy(path,ptr); else /* no $HOME, see if the OS knows */ { #if BADGETPW return 1; /* !!! a sleeze */ #else if ((pd = getpwuid(getuid())) == NULL) return 1; strcpy(path,pd->pw_dir); #endif /* BADGETPW */ } } else /* ~fred => user freds' home directory */ { atend = get_dpart(&name,word,&eval); if (eval) return 1; /* no wildcards allowed in user name */ name--; if (!atend) word[strlen(word)-1] = '\0'; /* remove "/" from "~fred/" */ #if BADGETPW if (!getpwhome(word)) return 1; strcpy(path,word); #else if ((pd = getpwnam(word)) == NULL) return 1; strcpy(path,pd->pw_dir); #endif /* BADGETPW */ } } /* at this point, maybe: strlen(path)!=0 && strlen(name)==0 */ atend = FALSE; needadot = FALSE; while (!atend) { atend = get_dpart(&name,word,&eval); skip_dot_files = (*word != '.'); /* ".fred" means look at dot files */ path_len = strlen(path); if (eval) /* wildcards in need of expansion */ { if (word[strlen(word)-1] == '.') needadot = TRUE; found = FALSE; if ((dir = opendir(path_len == 0 ? "." : path)) == NULL) return 1; while (TRUE) /* look at all entries in this directory */ { if ((dtr = readdir(dir)) == NULL) break; ptr = qtr = dtr->d_name; if (skip_dot_files && *ptr == '.') continue; if (needadot && !strchr(ptr,'.')) ptr = strcat(strcpy(tbuf,ptr),"."); if (wildmat(ptr,word)) { if (!atend) /* something like foo//bar */ { strcpy(path + path_len, qtr); /* make sure its a real directory */ if (is_dir(path)) { strcat(path,"/"); found = TRUE; break; } } else /* something like foo/ */ { found = TRUE; strcpy(path +path_len, qtr); s = procz(process_fname, slash_dir, path, nopath ? path_len : 0); path[path_len] = '\0'; if (s) { closedir(dir); return s; } if (onlyone) break; } } } /* end while (TRUE) */ closedir(dir); if (!found) return 1; } else /* No wildcards: something like: .../bar/... or .../bar */ { /* word may == "" (for input like "~fred") */ strcpy(path + path_len, word); if (atend) /* all done */ return procz(process_fname, slash_dir, path, nopath ? path_len : 0); /* ??? if (!is_dir(path)) return 1; /* Make sure path is real */ } } /* end while */ return 0; } /* Input: * process_fname: pointer to function to call * slash_dir: TRUE: if path is a directory, append a slash * path: Full path name * path_len: offset in path. Used if what to pass just part of path * to process_fname. * Returns: * What ever process_fname returns. * Notes: * Need to check path[path_len] before appending a slash because: if * don't want a path (path_len != 0) and no name (eg expanding "~"), * would process_fname("/") which is not what is expected. */ static int procz(process_fname, slash_dir, path, path_len) pfi process_fname; char *path; { if (slash_dir && (path[path_len] != '\0') && is_dir(path)) strcat(path, "/"); return (*process_fname)(path + path_len); } static int is_dir(path) char *path; { struct stat sbuf; if (stat(path,&sbuf)) return FALSE; /* make sure its a directory */ #ifdef S_ISDIR return S_ISDIR(sbuf.st_mode); #else return ((sbuf.st_mode & 0170000) == 040000); #endif /* S_ISDIR */ } #if BADGETPW /* Get the home directory out of the password file. * Only use this if the system getpw... routines are * screwed up. */ static int getpwhome(name) char *name; { char buf[256], *ptr; FILE *fptr; int n, s = FALSE; if ((fptr = fopen("/etc/passwd","r")) == NULL) return FALSE; while (fgets(buf,255,fptr)) { for (ptr = buf; *ptr != ':'; ptr++) ; *ptr = '\0'; if (strcmp(name,buf)) continue; for (n = 0; n < 4; ptr++) if (*ptr == ':') n++; while (*ptr != ':') *name++ = *ptr++; *name = '\0'; s = TRUE; break; } fclose(fptr); return s; } #endif /* BADGETPW */ #if ATARI /* Atari stuff for get_dpart(), same as MS-DOS */ #define ASLASH case '/': case '\\' #define GOTTA_EVAL case '?': case '[': case '*' #else /* UNIX stuff for get_dpart() */ #define ASLASH case '/' #define GOTTA_EVAL case '?': case '\\': case '[': case '*' #endif /* ATARI */ #endif /* UX_OS || ATARI */ /* Get the next part of the filename (ie the stuff between "/"s). The * parts of "/foo/*.c" are: "/", "foo", "*.c". * Input: * Output: * eval: TRUE if part contains wildcards needing expansion. * word: The part. * start: Points after the part (after the "/" or '\0'). * Returns: * TRUE: Hit the end of the filename else FALSE. */ static int get_dpart(start,word,eval) char **start, *word; int *eval; { register char *ptr = *start; int atend = TRUE; *eval = FALSE; while (TRUE) { switch (*word++ = *ptr++) { ASLASH: atend = FALSE; if (*eval) word--; /* remove trailing "/" */ case '\0': *word = '\0'; *start = ptr; return atend; GOTTA_EVAL: *eval = TRUE; break; } } } #ifdef TEST /* **************** TEST ******************************************** */ #include "dtable.h" declare_and_init_dTable(ntable,char *); extern char *savestr(); add_to_table(name) char *name; { static int n = 0; xpand_dTable(&ntable, 1, 50, 25); ntable.table[n++] = savestr(name); return 0; } char name_heap[3000]; main() { char buf[80], *zim = NULL; int j, nopath; printf("Use name_heap? "); gets(buf); if (atoi(buf)) zim = name_heap; printf("No path? "); gets(buf); nopath = atoi(buf); printf(": "); gets(buf); if (fxpand(buf,FALSE,nopath,zim,add_to_table)) puts("blew up"); else { if (zim) printf(">%s<\n",zim); else { for (j = 0; j < sizeof_dTable(&ntable); j++) printf("table[%d]: %s\n",j,ntable.table[j]); } } } #endif