| /* 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 <http://www.gnu.org/licenses/>. |
| * |
| * mainloop.c |
| * Iterating over all the command line parameters, and matching patterns |
| * where needed |
| */ |
| |
| #include "sysincludes.h" |
| #include "msdos.h" |
| #include "mtools.h" |
| #include "vfat.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; |
| } |
| |
| int unix_dir_loop(Stream_t *Stream, MainParam_t *mp); |
| int unix_loop(Stream_t *Stream UNUSEDP, MainParam_t *mp, char *arg, |
| int follow_dir_link); |
| |
| 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; |
| int 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; |
| } |
| |
| |
| int isSpecial(const char *name) |
| { |
| if(name[0] == '\0') |
| return 1; |
| if(!strcmp(name,".")) |
| return 1; |
| if(!strcmp(name,"..")) |
| return 1; |
| return 0; |
| } |
| |
| #ifdef HAVE_WCHAR_H |
| int isSpecialW(const wchar_t *name) |
| { |
| if(name[0] == '\0') |
| return 1; |
| if(!wcscmp(name,L".")) |
| return 1; |
| if(!wcscmp(name,L"..")) |
| return 1; |
| return 0; |
| } |
| #endif |
| |
| 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 *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) |
| MyFile = mp->File = OpenFileByDirentry(direntry); |
| ret = mp->callback(direntry, mp); |
| } |
| FREE(&MyFile); |
| if(isUniqueTarget(mp->targetName)) |
| ret |= STOP_NOW; |
| 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(&entry, filename, -1, |
| 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) |
| { |
| /* Dir is de-allocated by the same entity which allocated it */ |
| const char *ptr; |
| direntry_t entry; |
| int 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); |
| } |
| |
| doing_mcwd = !!filename1; |
| |
| ptr = strchr(filename0, '/'); |
| if(!ptr) { |
| length = strlen(filename0); |
| ptr = filename1; |
| filename1 = 0; |
| } else { |
| length = ptr - filename0; |
| ptr++; |
| } |
| if(!ptr) { |
| if(mp->lookupflags & OPEN_PARENT) { |
| mp->targetName = filename0; |
| ret = handle_leaf(getDirentry(mp->File), mp, |
| lookupState); |
| mp->targetName = 0; |
| return ret; |
| } |
| |
| if(!strcmp(filename0, ".") || !filename0[0]) { |
| return handle_leaf(getDirentry(mp->File), |
| mp, lookupState); |
| } |
| |
| if(!strcmp(filename0, "..")) { |
| return handle_leaf(getParent(getDirentry(mp->File)), mp, |
| lookupState); |
| } |
| |
| 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); |
| FREE(&SubDir); |
| } else { |
| ret |= handle_leaf(&entry, mp, lookupState); |
| if(isUniqueTarget(mp->targetName)) |
| return ret | STOP_NOW; |
| } |
| 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; |
| |
| 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; |
| |
| ret = recurs_dos_loop(mp, cwd, pathname, lookupState); |
| if(ret & NO_CWD) { |
| /* no CWD */ |
| *mp->mcwd = '\0'; |
| unlink_mcwd(); |
| ret = recurs_dos_loop(mp, "", pathname, lookupState); |
| } |
| FREE(&RootDir); |
| return ret; |
| } |
| |
| static int dos_loop(MainParam_t *mp, const char *arg) |
| { |
| return common_dos_loop(mp, arg, 0, mp->openflags); |
| } |
| |
| |
| static 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; |
| } |
| } |
| |
| static int unix_target_lookup(MainParam_t *mp, const char *arg) |
| { |
| char *ptr; |
| mp->unixTarget = strdup(arg); |
| /* try complete filename */ |
| if(access(mp->unixTarget, F_OK) == 0) |
| return GOT_ONE; |
| ptr = strrchr(mp->unixTarget, '/'); |
| if(!ptr) { |
| mp->targetName = mp->unixTarget; |
| mp->unixTarget = strdup("."); |
| return GOT_ONE; |
| } else { |
| *ptr = '\0'; |
| mp->targetName = ptr+1; |
| return GOT_ONE; |
| } |
| } |
| |
| int target_lookup(MainParam_t *mp, const char *arg) |
| { |
| if((mp->lookupflags & NO_UNIX) || (arg[0] && arg[1] == ':' )) |
| return dos_target_lookup(mp, arg); |
| else |
| return unix_target_lookup(mp, arg); |
| } |
| |
| 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); |
| } |
| |
| 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->targetName = 0; |
| mp->targetDir = 0; |
| mp->unixTarget = 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; |
| } |
| |
| 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); |
| } |
| |
| char *mpBuildUnixFilename(MainParam_t *mp) |
| { |
| const char *target; |
| char *ret; |
| char *tmp; |
| |
| target = mpPickTargetName(mp); |
| ret = malloc(strlen(mp->unixTarget) + 2 + strlen(target)); |
| if(!ret) |
| return 0; |
| strcpy(ret, mp->unixTarget); |
| if(*target) { |
| #if 1 /* fix for 'mcopy -n x:file existingfile' -- H. Lermen 980816 */ |
| if(!mp->targetName && !mp->targetDir) { |
| struct MT_STAT buf; |
| if (!MT_STAT(ret, &buf) && !S_ISDIR(buf.st_mode)) |
| return ret; |
| } |
| #endif |
| strcat(ret, "/"); |
| if(!strcmp(target, ".")) { |
| target="DOT"; |
| } else if(!strcmp(target, "..")) { |
| target="DOTDOT"; |
| } |
| while( (tmp=strchr(target, '/')) ) { |
| strncat(ret, target, tmp-target); |
| strcat(ret, "\\"); |
| target=tmp+1; |
| } |
| strcat(ret, target); |
| } |
| return ret; |
| } |