blob: cfd91bfd69b7b5b05088421be736968364166d21 [file] [log] [blame]
/* 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;
}