/* Copyright 1997-2002,2005-2009 Alain Knaff. * This file is part of mtools. * * Mtools 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 3 of the License, or * (at your option) any later version. * * Mtools 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 Mtools. If not, see . * * mainloop.c * Iterating over all the command line parameters, and matching patterns * where needed */ #include "sysincludes.h" #include "mtools.h" #include "fs.h" #include "mainloop.h" #include "plain_io.h" #include "file.h" #include "file_name.h" /* Fix the info in the MCWD file to be a proper directory name. * Always has a leading separator. Never has a trailing separator * (unless it is the path itself). */ static const char *fix_mcwd(char *ans) { FILE *fp; char *s; char buf[MAX_PATH]; fp = open_mcwd("r"); if(!fp || !fgets(buf, MAX_PATH, fp)) { if(fp) fclose(fp); ans[0] = get_default_drive(); strcpy(ans+1, ":/"); return ans; } buf[strlen(buf) -1] = '\0'; fclose(fp); /* drive letter present? */ s = buf; if (buf[0] && buf[1] == ':') { memcpy(ans, buf, 2); ans[2] = '\0'; s = &buf[2]; } else { ans[0] = get_default_drive(); strcpy(ans+1, ":"); } /* add a leading separator */ if (*s != '/' && *s != '\\') { strcat(ans, "/"); strcat(ans, s); } else strcat(ans, s); #if 0 /* translate to upper case */ for (s = ans; *s; ++s) { *s = ch_toupper(*s); if (*s == '\\') *s = '/'; } #endif /* if only drive, colon, & separator */ if (strlen(ans) == 3) return(ans); /* zap the trailing separator */ if (*--s == '/') *s = '\0'; return ans; } static int _unix_loop(Stream_t *Dir, MainParam_t *mp, const char *filename UNUSEDP) { return unix_dir_loop(Dir, mp); } int unix_loop(Stream_t *Stream UNUSEDP, MainParam_t *mp, char *arg, int follow_dir_link) { int ret; int isdir=0; size_t unixNameLength; mp->File = NULL; mp->direntry = NULL; unixNameLength = strlen(arg); if(unixNameLength > 1 && arg[unixNameLength-1] == '/') { /* names ending in slash, and having at least two characters */ char *name = strdup(arg); name[unixNameLength-1]='\0'; mp->unixSourceName = name; } else { mp->unixSourceName = arg; } /* mp->dir.attr = ATTR_ARCHIVE;*/ mp->loop = _unix_loop; if((mp->lookupflags & DO_OPEN)){ mp->File = SimpleFileOpen(0, 0, arg, O_RDONLY, 0, 0, 0, 0); if(!mp->File){ perror(arg); #if 0 tmp = _basename(arg); strncpy(mp->filename, tmp, VBUFSIZE); mp->filename[VBUFSIZE-1] = '\0'; #endif return ERROR_ONE; } GET_DATA(mp->File, 0, 0, &isdir, 0); if(isdir) { #if !defined(__EMX__) && !defined(OS_mingw32msvc) struct MT_STAT buf; #endif FREE(&mp->File); #if !defined(__EMX__) && !defined(OS_mingw32msvc) if(!follow_dir_link && MT_LSTAT(arg, &buf) == 0 && S_ISLNK(buf.st_mode)) { /* skip links to directories in order to avoid * infinite loops */ fprintf(stderr, "skipping directory symlink %s\n", arg); return 0; } #endif if(! (mp->lookupflags & ACCEPT_DIR)) return 0; mp->File = OpenDir(arg); } } if(isdir) ret = mp->dirCallback(0, mp); else ret = mp->unixcallback(mp); FREE(&mp->File); return ret; } static int checkForDot(int lookupflags, const wchar_t *name) { return (lookupflags & NO_DOTS) && isSpecialW(name); } typedef struct lookupState_t { Stream_t *container; int nbContainers; Stream_t *Dir; int nbDirs; const char *filename; } lookupState_t; static int isUniqueTarget(const char *name) { return name && strcmp(name, "-"); } static int handle_leaf(direntry_t *direntry, MainParam_t *mp, lookupState_t *lookupState, Stream_t **DeferredFileP) { Stream_t *MyFile=0; int ret; if(got_signal) return ERROR_ONE; if(lookupState) { /* we are looking for a "target" file */ switch(lookupState->nbDirs) { case 0: /* no directory yet, open it */ lookupState->Dir = OpenFileByDirentry(direntry); lookupState->nbDirs++; /* dump the container, we have * better now */ FREE(&lookupState->container); return 0; case 1: /* we have already a directory */ FREE(&lookupState->Dir); fprintf(stderr,"Ambiguous\n"); return STOP_NOW | ERROR_ONE; default: return STOP_NOW | ERROR_ONE; } } mp->direntry = direntry; if(IS_DIR(direntry)) { if(mp->lookupflags & (DO_OPEN | DO_OPEN_DIRS)) MyFile = mp->File = OpenFileByDirentry(direntry); ret = mp->dirCallback(direntry, mp); } else { if(mp->lookupflags & DO_OPEN) { if(DeferredFileP && *DeferredFileP) { /* Already a deferred file => close it and error */ FREE(DeferredFileP); fprintf(stderr, "Attempt to copy multiple files to non-directory\n"); return STOP_NOW | ERROR_ONE; } MyFile = mp->File = OpenFileByDirentry(direntry); if(DeferredFileP) { *DeferredFileP = MyFile; return 0; } } ret = mp->callback(direntry, mp); } FREE(&MyFile); return ret; } static int _dos_loop(Stream_t *Dir, MainParam_t *mp, const char *filename) { Stream_t *MyFile=0; direntry_t entry; int ret; int r; ret = 0; r=0; initializeDirentry(&entry, Dir); while(!got_signal && (r=vfat_lookup_zt(&entry, filename, mp->lookupflags, mp->shortname.data, mp->shortname.len, mp->longname.data, mp->longname.len)) == 0 ){ mp->File = NULL; if(!checkForDot(mp->lookupflags,entry.name)) { MyFile = 0; if((mp->lookupflags & DO_OPEN) || (IS_DIR(&entry) && (mp->lookupflags & DO_OPEN_DIRS))) { MyFile = mp->File = OpenFileByDirentry(&entry); } if(got_signal) break; mp->direntry = &entry; if(IS_DIR(&entry)) ret |= mp->dirCallback(&entry,mp); else ret |= mp->callback(&entry, mp); FREE(&MyFile); } if (fat_error(Dir)) ret |= ERROR_ONE; if(mp->fast_quit && (ret & ERROR_ONE)) break; } if (r == -2) return ERROR_ONE; if(got_signal) ret |= ERROR_ONE; return ret; } static int recurs_dos_loop(MainParam_t *mp, const char *filename0, const char *filename1, lookupState_t *lookupState, Stream_t **DeferredFileP) { /* Dir is de-allocated by the same entity which allocated it */ const char *ptr; direntry_t entry; size_t length; int lookupflags; int ret; int have_one; int doing_mcwd; int r; while(1) { /* strip dots and / */ if(!strncmp(filename0,"./", 2)) { filename0 += 2; continue; } if(!strcmp(filename0,".") && filename1) { filename0 ++; continue; } if(filename0[0] == '/') { filename0++; continue; } if(!filename0[0]) { if(!filename1) break; filename0 = filename1; filename1 = 0; continue; } break; } if(!strncmp(filename0,"../", 3) || (!strcmp(filename0, "..") && filename1)) { /* up one level */ mp->File = getDirentry(mp->File)->Dir; return recurs_dos_loop(mp, filename0+2, filename1, lookupState, DeferredFileP); } doing_mcwd = !!filename1; ptr = strchr(filename0, '/'); if(!ptr) { length = strlen(filename0); ptr = filename1; filename1 = 0; } else { length = ptrdiff(ptr, filename0); ptr++; } if(!ptr) { if(mp->lookupflags & OPEN_PARENT) { mp->targetName = filename0; ret = handle_leaf(getDirentry(mp->File), mp, lookupState, NULL); mp->targetName = 0; return ret; } if(!strcmp(filename0, ".") || !filename0[0]) { return handle_leaf(getDirentry(mp->File), mp, lookupState, NULL); } if(!strcmp(filename0, "..")) { return handle_leaf(getParent(getDirentry(mp->File)), mp, lookupState, NULL); } lookupflags = mp->lookupflags; if(lookupState) { lookupState->filename = filename0; if(lookupState->nbContainers + lookupState->nbDirs > 0){ /* we have already one target, don't bother * with this one. */ FREE(&lookupState->container); } else { /* no match yet. Remember this container for * later use */ lookupState->container = COPY(mp->File); } lookupState->nbContainers++; } } else lookupflags = ACCEPT_DIR | DO_OPEN | NO_DOTS; ret = 0; r = 0; have_one = 0; initializeDirentry(&entry, mp->File); while(!(ret & STOP_NOW) && !got_signal && (r=vfat_lookup(&entry, filename0, length, lookupflags | NO_MSG, mp->shortname.data, mp->shortname.len, mp->longname.data, mp->longname.len)) == 0 ){ if(checkForDot(lookupflags, entry.name)) /* while following the path, ignore the * special entries if they were not * explicitly given */ continue; have_one = 1; if(ptr) { Stream_t *SubDir; SubDir = mp->File = OpenFileByDirentry(&entry); ret |= recurs_dos_loop(mp, ptr, filename1, lookupState, DeferredFileP); FREE(&SubDir); } else { ret |= handle_leaf(&entry, mp, lookupState, DeferredFileP); } if(doing_mcwd) break; } if (r == -2) return ERROR_ONE; if(got_signal) return ret | ERROR_ONE; if(doing_mcwd && !have_one) return NO_CWD; return ret; } static int common_dos_loop(MainParam_t *mp, const char *pathname, lookupState_t *lookupState, int open_mode) { Stream_t *RootDir; const char *cwd; char drive; Stream_t *DeferredFile=NULL; Stream_t **DeferredFileP=NULL; int ret; mp->loop = _dos_loop; drive='\0'; cwd = ""; if(*pathname && pathname[1] == ':') { drive = ch_toupper(*pathname); pathname += 2; if(mp->mcwd[0] == drive) cwd = mp->mcwd+2; } else if(mp->mcwd[0]) { drive = mp->mcwd[0]; cwd = mp->mcwd+2; } else { drive = get_default_drive(); } if(*pathname=='/') /* absolute path name */ cwd = ""; RootDir = mp->File = open_root_dir(drive, open_mode, NULL); if(!mp->File) return ERROR_ONE; if(mp->originalArg && strpbrk(mp->originalArg, "*[?") != 0 && (mp->lookupflags & DEFERABLE) && isUniqueTarget(mp->targetName)) DeferredFileP = &DeferredFile; ret = recurs_dos_loop(mp, cwd, pathname, lookupState, DeferredFileP); if(ret & NO_CWD) { /* no CWD */ *mp->mcwd = '\0'; unlink_mcwd(); ret = recurs_dos_loop(mp, "", pathname, lookupState, DeferredFileP); } if(DeferredFile) { mp->File = DeferredFile; ret = mp->callback(NULL, mp); FREE(&DeferredFile); } FREE(&RootDir); return ret; } static int dos_loop(MainParam_t *mp, const char *arg) { return common_dos_loop(mp, arg, 0, mp->openflags); } int dos_target_lookup(MainParam_t *mp, const char *arg) { lookupState_t lookupState; int ret; int lookupflags; lookupState.nbDirs = 0; lookupState.Dir = 0; lookupState.nbContainers = 0; lookupState.container = 0; lookupflags = mp->lookupflags; mp->lookupflags = DO_OPEN | ACCEPT_DIR; ret = common_dos_loop(mp, arg, &lookupState, O_RDWR); mp->lookupflags = lookupflags; if(ret & ERROR_ONE) return ret; if(lookupState.nbDirs) { mp->targetName = 0; mp->targetDir = lookupState.Dir; FREE(&lookupState.container); /* container no longer needed */ return ret; } switch(lookupState.nbContainers) { case 0: /* no match */ fprintf(stderr,"%s: no match for target\n", arg); return MISSED_ONE; case 1: mp->targetName = strdup(lookupState.filename); mp->targetDir = lookupState.container; return ret; default: /* too much */ fprintf(stderr, "Ambiguous %s\n", arg); return ERROR_ONE; } } int main_loop(MainParam_t *mp, char **argv, int argc) { int i; int ret, Bret; Bret = 0; if(argc != 1 && mp->targetName) { fprintf(stderr, "Several file names given, but last argument (%s) not a directory\n", mp->targetName); FREE(&mp->targetDir); return 1; } for (i = 0; i < argc; i++) { if ( got_signal ) break; mp->originalArg = argv[i]; mp->basenameHasWildcard = strpbrk(_basename(mp->originalArg), "*[?") != 0; if (mp->unixcallback && (!argv[i][0] #ifdef OS_mingw32msvc /* On Windows, support only the command-line image drive. */ || argv[i][0] != ':' #endif || argv[i][1] != ':' )) ret = unix_loop(0, mp, argv[i], 1); else ret = dos_loop(mp, argv[i]); if (! (ret & (GOT_ONE | ERROR_ONE)) ) { /* one argument was unmatched */ fprintf(stderr, "%s: File \"%s\" not found\n", progname, argv[i]); ret |= ERROR_ONE; } Bret |= ret; if(mp->fast_quit && (Bret & (MISSED_ONE | ERROR_ONE))) break; } FREE(&mp->targetDir); if(Bret & ERROR_ONE) return 1; if ((Bret & GOT_ONE) && ( Bret & MISSED_ONE)) return 2; if (Bret & MISSED_ONE) return 1; return 0; } static int dispatchToFile(direntry_t *entry, MainParam_t *mp) { if(entry) return mp->callback(entry, mp); else return mp->unixcallback(mp); } void init_mp(MainParam_t *mp) { fix_mcwd(mp->mcwd); mp->openflags = O_RDONLY; mp->lookupflags = 0; mp->targetName = 0; mp->targetDir = 0; mp->dirCallback = dispatchToFile; mp->unixcallback = NULL; mp->shortname.data = mp->longname.data = 0; mp->shortname.len = mp->longname.len = 0; mp->File = 0; mp->fast_quit = 0; mp->originalArg = 0; } const char *mpGetBasename(MainParam_t *mp) { if(mp->direntry) { wchar_to_native(mp->direntry->name, mp->targetBuffer, MAX_VNAMELEN+1, sizeof(mp->targetBuffer)); return mp->targetBuffer; } else return _basename(mp->unixSourceName); } void mpPrintFilename(FILE *fp, MainParam_t *mp) { if(mp->direntry) fprintPwd(fp, mp->direntry, 0); else fprintf(fp,"%s",mp->originalArg); } const char *mpPickTargetName(MainParam_t *mp) { /* picks the target name: either the one explicitly given by the * user, or the same as the source */ if(mp->targetName) return mp->targetName; else return mpGetBasename(mp); }