| /* Copyright 1986-1992 Emmet P. Gray. |
| * Copyright 1994,1996-2002,2007-2009,2021 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/>. |
| * |
| * mcopy.c |
| * Copy an MSDOS files to and from Unix |
| * |
| */ |
| |
| |
| #include "sysincludes.h" |
| #include "msdos.h" |
| #include "mtools.h" |
| #include "vfat.h" |
| #include "mainloop.h" |
| #include "plain_io.h" |
| #include "nameclash.h" |
| #include "file.h" |
| #include "fs.h" |
| |
| |
| /* |
| * Preserve the file modification times after the fclose() |
| */ |
| |
| static void set_mtime(const char *target, time_t mtime) |
| { |
| if (target && strcmp(target, "-") && mtime != 0L) { |
| #ifdef HAVE_UTIMES |
| struct timeval tv[2]; |
| tv[0].tv_sec = mtime; |
| tv[0].tv_usec = 0; |
| tv[1].tv_sec = mtime; |
| tv[1].tv_usec = 0; |
| utimes(target, tv); |
| #else |
| #ifdef HAVE_UTIME |
| struct utimbuf utbuf; |
| |
| utbuf.actime = mtime; |
| utbuf.modtime = mtime; |
| utime(target, &utbuf); |
| #endif |
| #endif |
| } |
| return; |
| } |
| |
| typedef struct Arg_t { |
| int recursive; |
| int preserveAttributes; |
| int preserveTime; |
| unsigned char attr; |
| char *path; |
| int textmode; |
| int needfilter; |
| int nowarn; |
| int verbose; |
| int type; |
| int convertCharset; |
| MainParam_t mp; |
| ClashHandling_t ch; |
| int noClobber; |
| } Arg_t; |
| |
| static int _unix_write(MainParam_t *mp, int needfilter, const char *unixFile); |
| |
| /* Write the Unix file */ |
| static int unix_write(MainParam_t *mp, int needfilter) |
| { |
| Arg_t *arg=(Arg_t *) mp->arg; |
| |
| if(arg->type) |
| return _unix_write(mp, needfilter, "-"); |
| else { |
| char *unixFile = mpBuildUnixFilename(mp); |
| int ret; |
| if(!unixFile) { |
| printOom(); |
| return ERROR_ONE; |
| } |
| ret = _unix_write(mp, needfilter, unixFile); |
| free(unixFile); |
| return ret; |
| } |
| } |
| |
| |
| /* Write the Unix file */ |
| static int _unix_write(MainParam_t *mp, int needfilter, const char *unixFile) |
| { |
| Arg_t *arg=(Arg_t *) mp->arg; |
| time_t mtime; |
| Stream_t *File=mp->File; |
| Stream_t *Target, *Source; |
| struct MT_STAT stbuf; |
| char errmsg[80]; |
| |
| File->Class->get_data(File, &mtime, 0, 0, 0); |
| |
| if (!arg->preserveTime) |
| mtime = 0L; |
| |
| /* if we are creating a file, check whether it already exists */ |
| if(!arg->type) { |
| if (!arg->nowarn && !access(unixFile, 0)){ |
| if(arg->noClobber) { |
| fprintf(stderr, "File \"%s\" exists. To overwrite, try again, and explicitly specify target directory\n",unixFile); |
| return ERROR_ONE; |
| } |
| |
| /* sanity checking */ |
| if (!MT_STAT(unixFile, &stbuf)) { |
| struct MT_STAT srcStbuf; |
| int sFd; /* Source file descriptor */ |
| if(!S_ISREG(stbuf.st_mode)) { |
| fprintf(stderr,"\"%s\" is not a regular file\n", |
| unixFile); |
| |
| return ERROR_ONE; |
| } |
| sFd = get_fd(File); |
| if(sFd == -1) { |
| } else if((!MT_FSTAT(sFd, &srcStbuf)) && |
| stbuf.st_dev == srcStbuf.st_dev && |
| stbuf.st_ino == srcStbuf.st_ino) { |
| fprintf(stderr, "Attempt to copy file on itself\n"); |
| return ERROR_ONE; |
| } |
| } |
| |
| if( ask_confirmation("File \"%s\" exists, overwrite (y/n) ? ", |
| unixFile)) { |
| return ERROR_ONE; |
| } |
| |
| } |
| } |
| |
| if(!arg->type && arg->verbose) { |
| fprintf(stderr,"Copying "); |
| mpPrintFilename(stderr,mp); |
| fprintf(stderr,"\n"); |
| } |
| |
| if(got_signal) { |
| return ERROR_ONE; |
| } |
| |
| if ((Target = SimpleFileOpen(0, 0, unixFile, |
| O_WRONLY | O_CREAT | O_TRUNC, |
| errmsg, 0, 0, 0))) { |
| mt_off_t ret; |
| Source = COPY(File); |
| if(needfilter && arg->textmode) |
| Source = open_dos2unix(Source,arg->convertCharset); |
| |
| if (Source) |
| ret = copyfile(Source, Target); |
| else |
| ret = -1; |
| FREE(&Source); |
| FREE(&Target); |
| if(ret < 0){ |
| if(!arg->type) |
| unlink(unixFile); |
| return ERROR_ONE; |
| } |
| if(!arg->type) |
| set_mtime(unixFile, mtime); |
| return GOT_ONE; |
| } else { |
| fprintf(stderr,"%s\n", errmsg); |
| return ERROR_ONE; |
| } |
| } |
| |
| static int makeUnixDir(char *filename) |
| { |
| if(!mkdir(filename |
| #ifndef OS_mingw32msvc |
| , 0777 |
| #endif |
| )) |
| return 0; |
| if(errno == EEXIST) { |
| struct MT_STAT buf; |
| if(MT_STAT(filename, &buf) < 0) |
| return -1; |
| if(S_ISDIR(buf.st_mode)) |
| return 0; |
| errno = ENOTDIR; |
| } |
| return -1; |
| } |
| |
| /* Copy a directory to Unix */ |
| static int unix_copydir(direntry_t *entry, MainParam_t *mp) |
| { |
| Arg_t *arg=(Arg_t *) mp->arg; |
| time_t mtime; |
| Stream_t *File=mp->File; |
| int ret; |
| char *unixFile; |
| |
| if (!arg->recursive && mp->basenameHasWildcard) |
| return 0; |
| |
| File->Class->get_data(File, &mtime, 0, 0, 0); |
| if (!arg->preserveTime) |
| mtime = 0L; |
| if(!arg->type && arg->verbose) { |
| fprintf(stderr,"Copying "); |
| fprintPwd(stderr, entry,0); |
| fprintf(stderr, "\n"); |
| } |
| if(got_signal) |
| return ERROR_ONE; |
| unixFile = mpBuildUnixFilename(mp); |
| if(!unixFile) { |
| printOom(); |
| return ERROR_ONE; |
| } |
| if(arg->type || !*mpPickTargetName(mp) || !makeUnixDir(unixFile)) { |
| Arg_t newArg; |
| |
| newArg = *arg; |
| newArg.mp.arg = (void *) &newArg; |
| newArg.mp.unixTarget = unixFile; |
| newArg.mp.targetName = 0; |
| newArg.mp.basenameHasWildcard = 1; |
| |
| ret = mp->loop(File, &newArg.mp, "*"); |
| set_mtime(unixFile, mtime); |
| free(unixFile); |
| return ret | GOT_ONE; |
| } else { |
| perror("mkdir"); |
| fprintf(stderr, |
| "Failure to make directory %s\n", |
| unixFile); |
| free(unixFile); |
| return ERROR_ONE; |
| } |
| } |
| |
| static int dos_to_unix(direntry_t *entry UNUSEDP, MainParam_t *mp) |
| { |
| return unix_write(mp, 1); |
| } |
| |
| |
| static int unix_to_unix(MainParam_t *mp) |
| { |
| return unix_write(mp, 0); |
| } |
| |
| |
| static int directory_dos_to_unix(direntry_t *entry, MainParam_t *mp) |
| { |
| return unix_copydir(entry, mp); |
| } |
| |
| /* |
| * Open the named file for read, create the cluster chain, return the |
| * directory structure or NULL on error. |
| */ |
| static int writeit(struct dos_name_t *dosname, |
| char *longname, |
| void *arg0, |
| direntry_t *entry) |
| { |
| Stream_t *Target; |
| time_t now; |
| int type; |
| mt_off_t ret; |
| uint32_t fat; |
| time_t date; |
| mt_off_t filesize; |
| Arg_t *arg = (Arg_t *) arg0; |
| Stream_t *Source = COPY(arg->mp.File); |
| |
| if (Source->Class->get_data(Source, &date, &filesize, |
| &type, 0) < 0 ){ |
| fprintf(stderr, "Can't stat source file\n"); |
| return -1; |
| } |
| |
| if(fileTooBig(filesize)) { |
| fprintf(stderr, "File \"%s\" too big\n", longname); |
| return 1; |
| } |
| |
| if (type){ |
| if (arg->verbose) |
| fprintf(stderr, "\"%s\" is a directory\n", longname); |
| return -1; |
| } |
| |
| /*if (!arg->single || arg->recursive)*/ |
| if(arg->verbose) |
| fprintf(stderr,"Copying %s\n", longname); |
| if(got_signal) |
| return -1; |
| |
| /* will it fit? */ |
| if (!getfreeMinBytes(arg->mp.targetDir, filesize)) |
| return -1; |
| |
| /* preserve mod time? */ |
| if (arg->preserveTime) |
| now = date; |
| else |
| getTimeNow(&now); |
| |
| mk_entry(dosname, arg->attr, 1, 0, now, &entry->dir); |
| |
| Target = OpenFileByDirentry(entry); |
| if(!Target){ |
| fprintf(stderr,"Could not open Target\n"); |
| exit(1); |
| } |
| if (arg->needfilter & arg->textmode) { |
| Source = open_unix2dos(Source,arg->convertCharset); |
| } |
| |
| ret = copyfile(Source, Target); |
| GET_DATA(Target, 0, 0, 0, &fat); |
| FREE(&Source); |
| FREE(&Target); |
| if(ret < 0 ){ |
| fat_free(arg->mp.targetDir, fat); |
| return -1; |
| } else { |
| mk_entry(dosname, arg->attr, fat, (uint32_t)ret, |
| now, &entry->dir); |
| return 0; |
| } |
| } |
| |
| |
| |
| static int dos_write(direntry_t *entry, MainParam_t *mp, int needfilter) |
| /* write a messy dos file to another messy dos file */ |
| { |
| int result; |
| Arg_t * arg = (Arg_t *) (mp->arg); |
| const char *targetName = mpPickTargetName(mp); |
| |
| if(entry && arg->preserveAttributes) |
| arg->attr = entry->dir.attr; |
| else |
| arg->attr = ATTR_ARCHIVE; |
| |
| arg->needfilter = needfilter; |
| if (entry && mp->targetDir == entry->Dir){ |
| arg->ch.ignore_entry = -1; |
| arg->ch.source = entry->entry; |
| } else { |
| arg->ch.ignore_entry = -1; |
| arg->ch.source = -2; |
| } |
| result = mwrite_one(mp->targetDir, targetName, 0, |
| writeit, (void *)arg, &arg->ch); |
| if(result == 1) |
| return GOT_ONE; |
| else |
| return ERROR_ONE; |
| } |
| |
| static Stream_t *subDir(Stream_t *parent, const char *filename) |
| { |
| direntry_t entry; |
| initializeDirentry(&entry, parent); |
| |
| switch(vfat_lookup_zt(&entry, filename, ACCEPT_DIR, 0, 0, 0, 0)) { |
| case 0: |
| return OpenFileByDirentry(&entry); |
| case -1: |
| return NULL; |
| default: /* IO Error */ |
| return NULL; |
| } |
| } |
| |
| static int dos_copydir(direntry_t *entry, MainParam_t *mp) |
| /* copyes a directory to Dos */ |
| { |
| Arg_t * arg = (Arg_t *) (mp->arg); |
| Arg_t newArg; |
| time_t now; |
| time_t date; |
| int ret; |
| const char *targetName = mpPickTargetName(mp); |
| |
| if (!arg->recursive && mp->basenameHasWildcard) |
| return 0; |
| |
| if(entry && isSubdirOf(mp->targetDir, mp->File)) { |
| fprintf(stderr, "Cannot recursively copy directory "); |
| fprintPwd(stderr, entry,0); |
| fprintf(stderr, " into one of its own subdirectories "); |
| fprintPwd(stderr, getDirentry(mp->targetDir),0); |
| fprintf(stderr, "\n"); |
| return ERROR_ONE; |
| } |
| |
| if (arg->mp.File->Class->get_data(arg->mp.File, |
| & date, 0, 0, 0) < 0 ){ |
| fprintf(stderr, "Can't stat source file\n"); |
| return ERROR_ONE; |
| } |
| |
| if(!arg->type && arg->verbose) |
| fprintf(stderr,"Copying %s\n", mpGetBasename(mp)); |
| |
| if(entry && arg->preserveAttributes) |
| arg->attr = entry->dir.attr; |
| else |
| arg->attr = 0; |
| |
| if (entry && (mp->targetDir == entry->Dir)){ |
| arg->ch.ignore_entry = -1; |
| arg->ch.source = entry->entry; |
| } else { |
| arg->ch.ignore_entry = -1; |
| arg->ch.source = -2; |
| } |
| |
| /* preserve mod time? */ |
| if (arg->preserveTime) |
| now = date; |
| else |
| getTimeNow(&now); |
| |
| newArg = *arg; |
| newArg.mp.arg = &newArg; |
| newArg.mp.targetName = 0; |
| newArg.mp.basenameHasWildcard = 1; |
| if(*targetName) { |
| /* maybe the directory already exist. Use it */ |
| newArg.mp.targetDir = subDir(mp->targetDir, targetName); |
| if(!newArg.mp.targetDir) |
| newArg.mp.targetDir = createDir(mp->targetDir, |
| targetName, |
| &arg->ch, arg->attr, |
| now); |
| } else |
| newArg.mp.targetDir = mp->targetDir; |
| |
| if(!newArg.mp.targetDir) |
| return ERROR_ONE; |
| |
| ret = mp->loop(mp->File, &newArg.mp, "*"); |
| if(*targetName) |
| FREE(&newArg.mp.targetDir); |
| return ret | GOT_ONE; |
| } |
| |
| |
| static int dos_to_dos(direntry_t *entry, MainParam_t *mp) |
| { |
| return dos_write(entry, mp, 0); |
| } |
| |
| static int unix_to_dos(MainParam_t *mp) |
| { |
| return dos_write(0, mp, 1); |
| } |
| |
| static void usage(int ret) NORETURN; |
| static void usage(int ret) |
| { |
| fprintf(stderr, |
| "Mtools version %s, dated %s\n", mversion, mdate); |
| fprintf(stderr, |
| "Usage: %s [-spatnmQVBT] [-D clash_option] sourcefile targetfile\n", progname); |
| fprintf(stderr, |
| " %s [-spatnmQVBT] [-D clash_option] sourcefile [sourcefiles...] targetdirectory\n", |
| progname); |
| exit(ret); |
| } |
| |
| void mcopy(int argc, char **argv, int mtype) NORETURN; |
| void mcopy(int argc, char **argv, int mtype) |
| { |
| Arg_t arg; |
| int c, fastquit; |
| |
| |
| /* get command line options */ |
| |
| init_clash_handling(& arg.ch); |
| |
| /* get command line options */ |
| arg.recursive = 0; |
| arg.preserveTime = 0; |
| arg.preserveAttributes = 0; |
| arg.nowarn = 0; |
| arg.textmode = 0; |
| arg.verbose = 0; |
| arg.convertCharset = 0; |
| arg.type = mtype; |
| fastquit = 0; |
| if(helpFlag(argc, argv)) |
| usage(0); |
| while ((c = getopt(argc, argv, "i:abB/sptTnmvQD:oh")) != EOF) { |
| switch (c) { |
| case 'i': |
| set_cmd_line_image(optarg); |
| break; |
| case 's': |
| case '/': |
| arg.recursive = 1; |
| break; |
| case 'p': |
| arg.preserveAttributes = 1; |
| break; |
| case 'T': |
| arg.convertCharset = 1; |
| /*-fallthrough*/ |
| case 'a': |
| case 't': |
| arg.textmode = 1; |
| break; |
| case 'n': |
| arg.nowarn = 1; |
| break; |
| case 'm': |
| arg.preserveTime = 1; |
| break; |
| case 'v': |
| arg.verbose = 1; |
| break; |
| case 'Q': |
| fastquit = 1; |
| break; |
| case 'B': |
| case 'b': |
| batchmode = 1; |
| break; |
| case 'o': |
| handle_clash_options(&arg.ch, (char) c); |
| break; |
| case 'D': |
| if(handle_clash_options(&arg.ch, *optarg)) |
| usage(1); |
| break; |
| case 'h': |
| usage(0); |
| case '?': |
| usage(1); |
| default: |
| break; |
| |
| } |
| } |
| |
| if (argc - optind < 1) |
| usage(1); |
| |
| init_mp(&arg.mp); |
| arg.mp.lookupflags = ACCEPT_PLAIN | ACCEPT_DIR | DO_OPEN | NO_DOTS; |
| arg.mp.fast_quit = fastquit; |
| arg.mp.arg = (void *) &arg; |
| arg.mp.openflags = O_RDONLY; |
| arg.noClobber = 0; |
| |
| /* last parameter is "-", use mtype mode */ |
| if(!mtype && !strcmp(argv[argc-1], "-")) { |
| arg.type = mtype = 1; |
| argc--; |
| } |
| |
| if(mtype){ |
| /* Mtype = copying to stdout */ |
| arg.mp.targetName = strdup("-"); |
| arg.mp.unixTarget = strdup(""); |
| arg.mp.callback = dos_to_unix; |
| arg.mp.dirCallback = unix_copydir; |
| arg.mp.unixcallback = unix_to_unix; |
| } else { |
| const char *target; |
| if (argc - optind == 1) { |
| /* copying to the current directory */ |
| target = "."; |
| arg.noClobber = 1; |
| } else { |
| /* target is the last item mentioned */ |
| argc--; |
| target = argv[argc]; |
| } |
| |
| target_lookup(&arg.mp, target); |
| if(!arg.mp.targetDir && !arg.mp.unixTarget) { |
| fprintf(stderr,"Bad target %s\n", target); |
| exit(1); |
| } |
| |
| /* callback functions */ |
| if(arg.mp.unixTarget) { |
| arg.mp.callback = dos_to_unix; |
| arg.mp.dirCallback = directory_dos_to_unix; |
| arg.mp.unixcallback = unix_to_unix; |
| } else { |
| arg.mp.dirCallback = dos_copydir; |
| arg.mp.callback = dos_to_dos; |
| arg.mp.unixcallback = unix_to_dos; |
| } |
| } |
| |
| exit(main_loop(&arg.mp, argv + optind, argc - optind)); |
| } |