Imported Upstream version 4.0.40
[platform/upstream/mtools.git] / mainloop.c
1 /*  Copyright 1997-2002,2005-2009 Alain Knaff.
2  *  This file is part of mtools.
3  *
4  *  Mtools is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Mtools is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * mainloop.c
18  * Iterating over all the command line parameters, and matching patterns
19  * where needed
20  */
21
22 #include "sysincludes.h"
23 #include "mtools.h"
24 #include "fs.h"
25 #include "mainloop.h"
26 #include "plain_io.h"
27 #include "file.h"
28 #include "file_name.h"
29
30
31 /* Fix the info in the MCWD file to be a proper directory name.
32  * Always has a leading separator.  Never has a trailing separator
33  * (unless it is the path itself).  */
34
35 static const char *fix_mcwd(char *ans)
36 {
37         FILE *fp;
38         char *s;
39         char buf[MAX_PATH];
40
41         fp = open_mcwd("r");
42         if(!fp || !fgets(buf, MAX_PATH, fp)) {
43                 if(fp)
44                         fclose(fp);
45                 ans[0] = get_default_drive();
46                 strcpy(ans+1, ":/");
47                 return ans;
48         }
49
50         buf[strlen(buf) -1] = '\0';
51         fclose(fp);
52                                         /* drive letter present? */
53         s = buf;
54         if (buf[0] && buf[1] == ':') {
55                 memcpy(ans, buf, 2);
56                 ans[2] = '\0';
57                 s = &buf[2];
58         } else {
59                 ans[0] = get_default_drive();
60                 strcpy(ans+1, ":");
61         }
62                         /* add a leading separator */
63         if (*s != '/' && *s != '\\') {
64                 strcat(ans, "/");
65                 strcat(ans, s);
66         } else
67                 strcat(ans, s);
68
69 #if 0
70                                         /* translate to upper case */
71         for (s = ans; *s; ++s) {
72                 *s = ch_toupper(*s);
73                 if (*s == '\\')
74                         *s = '/';
75         }
76 #endif
77                                         /* if only drive, colon, & separator */
78         if (strlen(ans) == 3)
79                 return(ans);
80                                         /* zap the trailing separator */
81         if (*--s == '/')
82                 *s = '\0';
83         return ans;
84 }
85
86 int unix_dir_loop(Stream_t *Stream, MainParam_t *mp);
87 int unix_loop(Stream_t *Stream UNUSEDP, MainParam_t *mp, char *arg,
88               int follow_dir_link);
89
90 static int _unix_loop(Stream_t *Dir, MainParam_t *mp,
91                       const char *filename UNUSEDP)
92 {
93         return unix_dir_loop(Dir, mp);
94 }
95
96 int unix_loop(Stream_t *Stream UNUSEDP, MainParam_t *mp,
97               char *arg, int follow_dir_link)
98 {
99         int ret;
100         int isdir=0;
101         size_t unixNameLength;
102
103         mp->File = NULL;
104         mp->direntry = NULL;
105         unixNameLength = strlen(arg);
106         if(unixNameLength > 1 && arg[unixNameLength-1] == '/') {
107             /* names ending in slash, and having at least two characters */
108             char *name = strdup(arg);
109             name[unixNameLength-1]='\0';
110             mp->unixSourceName = name;
111         } else {
112             mp->unixSourceName = arg;
113         }
114         /*      mp->dir.attr = ATTR_ARCHIVE;*/
115         mp->loop = _unix_loop;
116         if((mp->lookupflags & DO_OPEN)){
117                 mp->File = SimpleFileOpen(0, 0, arg, O_RDONLY, 0, 0, 0, 0);
118                 if(!mp->File){
119                         perror(arg);
120 #if 0
121                         tmp = _basename(arg);
122                         strncpy(mp->filename, tmp, VBUFSIZE);
123                         mp->filename[VBUFSIZE-1] = '\0';
124 #endif
125                         return ERROR_ONE;
126                 }
127                 GET_DATA(mp->File, 0, 0, &isdir, 0);
128                 if(isdir) {
129 #if !defined(__EMX__) && !defined(OS_mingw32msvc)
130                         struct MT_STAT buf;
131 #endif
132
133                         FREE(&mp->File);
134 #if !defined(__EMX__) && !defined(OS_mingw32msvc)
135                         if(!follow_dir_link &&
136                            MT_LSTAT(arg, &buf) == 0 &&
137                            S_ISLNK(buf.st_mode)) {
138                                 /* skip links to directories in order to avoid
139                                  * infinite loops */
140                                 fprintf(stderr,
141                                         "skipping directory symlink %s\n",
142                                         arg);
143                                 return 0;
144                         }
145 #endif
146                         if(! (mp->lookupflags & ACCEPT_DIR))
147                                 return 0;
148                         mp->File = OpenDir(arg);
149                 }
150         }
151
152         if(isdir)
153                 ret = mp->dirCallback(0, mp);
154         else
155                 ret = mp->unixcallback(mp);
156         FREE(&mp->File);
157         return ret;
158 }
159
160
161 int isSpecial(const char *name)
162 {
163         if(name[0] == '\0')
164                 return 1;
165         if(!strcmp(name,"."))
166                 return 1;
167         if(!strcmp(name,".."))
168                 return 1;
169         return 0;
170 }
171
172 #ifdef HAVE_WCHAR_H
173 int isSpecialW(const wchar_t *name)
174 {
175         if(name[0] == '\0')
176                 return 1;
177         if(!wcscmp(name,L"."))
178                 return 1;
179         if(!wcscmp(name,L".."))
180                 return 1;
181         return 0;
182 }
183 #endif
184
185 static int checkForDot(int lookupflags, const wchar_t *name)
186 {
187         return (lookupflags & NO_DOTS) && isSpecialW(name);
188 }
189
190
191 typedef struct lookupState_t {
192         Stream_t *container;
193         int nbContainers;
194         Stream_t *Dir;
195         int nbDirs;
196         const char *filename;
197 } lookupState_t;
198
199 static int isUniqueTarget(const char *name)
200 {
201         return name && strcmp(name, "-");
202 }
203
204 static int handle_leaf(direntry_t *direntry, MainParam_t *mp,
205                        lookupState_t *lookupState)
206 {
207         Stream_t *MyFile=0;
208         int ret;
209
210         if(got_signal)
211                 return ERROR_ONE;
212         if(lookupState) {
213                 /* we are looking for a "target" file */
214                 switch(lookupState->nbDirs) {
215                         case 0: /* no directory yet, open it */
216                                 lookupState->Dir = OpenFileByDirentry(direntry);
217                                 lookupState->nbDirs++;
218                                 /* dump the container, we have
219                                  * better now */
220                                 FREE(&lookupState->container);
221                                 return 0;
222                         case 1: /* we have already a directory */
223                                 FREE(&lookupState->Dir);
224                                 fprintf(stderr,"Ambiguous\n");
225                                 return STOP_NOW | ERROR_ONE;
226                         default:
227                                 return STOP_NOW | ERROR_ONE;
228                 }
229         }
230
231         mp->direntry = direntry;
232         if(IS_DIR(direntry)) {
233                 if(mp->lookupflags & (DO_OPEN | DO_OPEN_DIRS))
234                         MyFile = mp->File = OpenFileByDirentry(direntry);
235                 ret = mp->dirCallback(direntry, mp);
236         } else {
237                 if(mp->lookupflags & DO_OPEN)
238                         MyFile = mp->File = OpenFileByDirentry(direntry);
239                 ret = mp->callback(direntry, mp);
240         }
241         FREE(&MyFile);
242         if(isUniqueTarget(mp->targetName))
243                 ret |= STOP_NOW;
244         return ret;
245 }
246
247 static int _dos_loop(Stream_t *Dir, MainParam_t *mp, const char *filename)
248 {
249         Stream_t *MyFile=0;
250         direntry_t entry;
251         int ret;
252         int r;
253
254         ret = 0;
255         r=0;
256         initializeDirentry(&entry, Dir);
257         while(!got_signal &&
258               (r=vfat_lookup_zt(&entry, filename,
259                                 mp->lookupflags,
260                                 mp->shortname.data, mp->shortname.len,
261                                 mp->longname.data, mp->longname.len)) == 0 ){
262                 mp->File = NULL;
263                 if(!checkForDot(mp->lookupflags,entry.name)) {
264                         MyFile = 0;
265                         if((mp->lookupflags & DO_OPEN) ||
266                            (IS_DIR(&entry) &&
267                             (mp->lookupflags & DO_OPEN_DIRS))) {
268                                 MyFile = mp->File = OpenFileByDirentry(&entry);
269                         }
270                         if(got_signal)
271                                 break;
272                         mp->direntry = &entry;
273                         if(IS_DIR(&entry))
274                                 ret |= mp->dirCallback(&entry,mp);
275                         else
276                                 ret |= mp->callback(&entry, mp);
277                         FREE(&MyFile);
278                 }
279                 if (fat_error(Dir))
280                         ret |= ERROR_ONE;
281                 if(mp->fast_quit && (ret & ERROR_ONE))
282                         break;
283         }
284         if (r == -2)
285             return ERROR_ONE;
286         if(got_signal)
287                 ret |= ERROR_ONE;
288         return ret;
289 }
290
291 static int recurs_dos_loop(MainParam_t *mp, const char *filename0,
292                            const char *filename1,
293                            lookupState_t *lookupState)
294 {
295         /* Dir is de-allocated by the same entity which allocated it */
296         const char *ptr;
297         direntry_t entry;
298         size_t length;
299         int lookupflags;
300         int ret;
301         int have_one;
302         int doing_mcwd;
303         int r;
304
305         while(1) {
306                 /* strip dots and / */
307                 if(!strncmp(filename0,"./", 2)) {
308                         filename0 += 2;
309                         continue;
310                 }
311                 if(!strcmp(filename0,".") && filename1) {
312                         filename0 ++;
313                         continue;
314                 }
315                 if(filename0[0] == '/') {
316                         filename0++;
317                         continue;
318                 }
319                 if(!filename0[0]) {
320                         if(!filename1)
321                                 break;
322                         filename0 = filename1;
323                         filename1 = 0;
324                         continue;
325                 }
326                 break;
327         }
328
329         if(!strncmp(filename0,"../", 3) ||
330            (!strcmp(filename0, "..") && filename1)) {
331                 /* up one level */
332                 mp->File = getDirentry(mp->File)->Dir;
333                 return recurs_dos_loop(mp, filename0+2, filename1, lookupState);
334         }
335
336         doing_mcwd = !!filename1;
337
338         ptr = strchr(filename0, '/');
339         if(!ptr) {
340                 length = strlen(filename0);
341                 ptr = filename1;
342                 filename1 = 0;
343         } else {
344                 length = ptrdiff(ptr, filename0);
345                 ptr++;
346         }
347         if(!ptr) {
348                 if(mp->lookupflags & OPEN_PARENT) {
349                         mp->targetName = filename0;
350                         ret = handle_leaf(getDirentry(mp->File), mp,
351                                           lookupState);
352                         mp->targetName = 0;
353                         return ret;
354                 }
355
356                 if(!strcmp(filename0, ".") || !filename0[0]) {
357                         return handle_leaf(getDirentry(mp->File),
358                                            mp, lookupState);
359                 }
360
361                 if(!strcmp(filename0, "..")) {
362                         return handle_leaf(getParent(getDirentry(mp->File)), mp,
363                                            lookupState);
364                 }
365
366                 lookupflags = mp->lookupflags;
367
368                 if(lookupState) {
369                         lookupState->filename = filename0;
370                         if(lookupState->nbContainers + lookupState->nbDirs > 0){
371                                 /* we have already one target, don't bother
372                                  * with this one. */
373                                 FREE(&lookupState->container);
374                         } else {
375                                 /* no match yet.  Remember this container for
376                                  * later use */
377                                 lookupState->container = COPY(mp->File);
378                         }
379                         lookupState->nbContainers++;
380                 }
381         } else
382                 lookupflags = ACCEPT_DIR | DO_OPEN | NO_DOTS;
383
384         ret = 0;
385         r = 0;
386         have_one = 0;
387         initializeDirentry(&entry, mp->File);
388         while(!(ret & STOP_NOW) &&
389               !got_signal &&
390               (r=vfat_lookup(&entry, filename0, length,
391                              lookupflags | NO_MSG,
392                              mp->shortname.data, mp->shortname.len,
393                              mp->longname.data, mp->longname.len)) == 0 ){
394                 if(checkForDot(lookupflags, entry.name))
395                         /* while following the path, ignore the
396                          * special entries if they were not
397                          * explicitly given */
398                         continue;
399                 have_one = 1;
400                 if(ptr) {
401                         Stream_t *SubDir;
402                         SubDir = mp->File = OpenFileByDirentry(&entry);
403                         ret |= recurs_dos_loop(mp, ptr, filename1, lookupState);
404                         FREE(&SubDir);
405                 } else {
406                         ret |= handle_leaf(&entry, mp, lookupState);
407                         if(isUniqueTarget(mp->targetName))
408                                 return ret | STOP_NOW;
409                 }
410                 if(doing_mcwd)
411                         break;
412         }
413         if (r == -2)
414                 return ERROR_ONE;
415         if(got_signal)
416                 return ret | ERROR_ONE;
417         if(doing_mcwd && !have_one)
418                 return NO_CWD;
419         return ret;
420 }
421
422 static int common_dos_loop(MainParam_t *mp, const char *pathname,
423                            lookupState_t *lookupState, int open_mode)
424
425 {
426         Stream_t *RootDir;
427         const char *cwd;
428         char drive;
429
430         int ret;
431         mp->loop = _dos_loop;
432
433         drive='\0';
434         cwd = "";
435         if(*pathname && pathname[1] == ':') {
436                 drive = ch_toupper(*pathname);
437                 pathname += 2;
438                 if(mp->mcwd[0] == drive)
439                         cwd = mp->mcwd+2;
440         } else if(mp->mcwd[0]) {
441                 drive = mp->mcwd[0];
442                 cwd = mp->mcwd+2;
443         } else {
444                 drive = get_default_drive();
445         }
446
447         if(*pathname=='/') /* absolute path name */
448                 cwd = "";
449
450         RootDir = mp->File = open_root_dir(drive, open_mode, NULL);
451         if(!mp->File)
452                 return ERROR_ONE;
453
454         ret = recurs_dos_loop(mp, cwd, pathname, lookupState);
455         if(ret & NO_CWD) {
456                 /* no CWD */
457                 *mp->mcwd = '\0';
458                 unlink_mcwd();
459                 ret = recurs_dos_loop(mp, "", pathname, lookupState);
460         }
461         FREE(&RootDir);
462         return ret;
463 }
464
465 static int dos_loop(MainParam_t *mp, const char *arg)
466 {
467         return common_dos_loop(mp, arg, 0, mp->openflags);
468 }
469
470
471 static int dos_target_lookup(MainParam_t *mp, const char *arg)
472 {
473         lookupState_t lookupState;
474         int ret;
475         int lookupflags;
476
477         lookupState.nbDirs = 0;
478         lookupState.Dir = 0;
479         lookupState.nbContainers = 0;
480         lookupState.container = 0;
481
482         lookupflags = mp->lookupflags;
483         mp->lookupflags = DO_OPEN | ACCEPT_DIR;
484         ret = common_dos_loop(mp, arg, &lookupState, O_RDWR);
485         mp->lookupflags = lookupflags;
486         if(ret & ERROR_ONE)
487                 return ret;
488
489         if(lookupState.nbDirs) {
490                 mp->targetName = 0;
491                 mp->targetDir = lookupState.Dir;
492                 FREE(&lookupState.container); /* container no longer needed */
493                 return ret;
494         }
495
496         switch(lookupState.nbContainers) {
497                 case 0:
498                         /* no match */
499                         fprintf(stderr,"%s: no match for target\n", arg);
500                         return MISSED_ONE;
501                 case 1:
502                         mp->targetName = strdup(lookupState.filename);
503                         mp->targetDir = lookupState.container;
504                         return ret;
505                 default:
506                         /* too much */
507                         fprintf(stderr, "Ambiguous %s\n", arg);
508                         return ERROR_ONE;
509         }
510 }
511
512 /*
513  * Is target a Unix directory
514  * -1 error occured
515  * 0 regular file
516  * 1 directory
517  */
518 static int unix_is_dir(const char *name)
519 {
520         struct stat buf;
521         if(stat(name, &buf) < 0)
522                 return -1;
523         else
524                 return 1 && S_ISDIR(buf.st_mode);
525 }
526
527 static int unix_target_lookup(MainParam_t *mp, const char *arg)
528 {
529         char *ptr;
530         mp->unixTarget = strdup(arg);
531         /* try complete filename */
532         if(access(mp->unixTarget, F_OK) == 0) {
533                 switch(unix_is_dir(mp->unixTarget)) {
534                 case -1:
535                         return ERROR_ONE;
536                 case 0:
537                         mp->targetName="";
538                         break;
539                 }
540                 return GOT_ONE;
541         }
542         ptr = strrchr(mp->unixTarget, '/');
543         if(!ptr) {
544                 mp->targetName = mp->unixTarget;
545                 mp->unixTarget = strdup(".");
546                 return GOT_ONE;
547         } else {
548                 *ptr = '\0';
549                 mp->targetName = ptr+1;
550                 return GOT_ONE;
551         }
552 }
553
554 int target_lookup(MainParam_t *mp, const char *arg)
555 {
556         if((mp->lookupflags & NO_UNIX) || (arg[0] && arg[1] == ':' ))
557                 return dos_target_lookup(mp, arg);
558         else
559                 return unix_target_lookup(mp, arg);
560 }
561
562 int main_loop(MainParam_t *mp, char **argv, int argc)
563 {
564         int i;
565         int ret, Bret;
566
567         Bret = 0;
568
569         if(argc != 1 && mp->targetName) {
570                 fprintf(stderr,
571                         "Several file names given, but last argument (%s) not a directory\n", mp->targetName);
572                 FREE(&mp->targetDir);
573                 return 1;
574         }
575
576         for (i = 0; i < argc; i++) {
577                 if ( got_signal )
578                         break;
579                 mp->originalArg = argv[i];
580                 mp->basenameHasWildcard = strpbrk(_basename(mp->originalArg),
581                                                   "*[?") != 0;
582                 if (mp->unixcallback && (!argv[i][0]
583 #ifdef OS_mingw32msvc
584 /* On Windows, support only the command-line image drive. */
585                                          || argv[i][0] != ':'
586 #endif
587                                          || argv[i][1] != ':' ))
588                         ret = unix_loop(0, mp, argv[i], 1);
589                 else
590                         ret = dos_loop(mp, argv[i]);
591
592                 if (! (ret & (GOT_ONE | ERROR_ONE)) ) {
593                         /* one argument was unmatched */
594                         fprintf(stderr, "%s: File \"%s\" not found\n",
595                                 progname, argv[i]);
596                         ret |= ERROR_ONE;
597                 }
598                 Bret |= ret;
599                 if(mp->fast_quit && (Bret & (MISSED_ONE | ERROR_ONE)))
600                         break;
601         }
602         FREE(&mp->targetDir);
603         if(Bret & ERROR_ONE)
604                 return 1;
605         if ((Bret & GOT_ONE) && ( Bret & MISSED_ONE))
606                 return 2;
607         if (Bret & MISSED_ONE)
608                 return 1;
609         return 0;
610 }
611
612 static int dispatchToFile(direntry_t *entry, MainParam_t *mp)
613 {
614         if(entry)
615                 return mp->callback(entry, mp);
616         else
617                 return mp->unixcallback(mp);
618 }
619
620
621 void init_mp(MainParam_t *mp)
622 {
623         fix_mcwd(mp->mcwd);
624         mp->openflags = O_RDONLY;
625         mp->targetName = 0;
626         mp->targetDir = 0;
627         mp->unixTarget = 0;
628         mp->dirCallback = dispatchToFile;
629         mp->unixcallback = NULL;
630         mp->shortname.data = mp->longname.data = 0;
631         mp->shortname.len = mp->longname.len = 0;
632         mp->File = 0;
633         mp->fast_quit = 0;
634 }
635
636 const char *mpGetBasename(MainParam_t *mp)
637 {
638         if(mp->direntry) {
639                 wchar_to_native(mp->direntry->name, mp->targetBuffer,
640                                 MAX_VNAMELEN+1, sizeof(mp->targetBuffer));
641                 return mp->targetBuffer;
642         } else
643                 return _basename(mp->unixSourceName);
644 }
645
646 void mpPrintFilename(FILE *fp, MainParam_t *mp)
647 {
648         if(mp->direntry)
649                 fprintPwd(fp, mp->direntry, 0);
650         else
651                 fprintf(fp,"%s",mp->originalArg);
652 }
653
654 const char *mpPickTargetName(MainParam_t *mp)
655 {
656         /* picks the target name: either the one explicitly given by the
657          * user, or the same as the source */
658         if(mp->targetName)
659                 return mp->targetName;
660         else
661                 return mpGetBasename(mp);
662 }
663
664 char *mpBuildUnixFilename(MainParam_t *mp)
665 {
666         const char *target;
667         char *ret;
668         char *tmp;
669
670         target = mpPickTargetName(mp);
671         ret = malloc(strlen(mp->unixTarget) + 2 + strlen(target));
672         if(!ret)
673                 return 0;
674         strcpy(ret, mp->unixTarget);
675         if(*target) {
676                 /* fix for 'mcopy -n x:file existingfile' -- H. Lermen 980816 */
677                 if(!mp->targetName && !mp->targetDir && !unix_is_dir(ret))
678                         return ret;
679                 strcat(ret, "/");
680                 if(!strcmp(target, ".")) {
681                   target="DOT";
682                 } else if(!strcmp(target, "..")) {
683                   target="DOTDOT";
684                 }
685                 while( (tmp=strchr(target, '/')) ) {
686                   strncat(ret, target, ptrdiff(tmp,target));
687                   strcat(ret, "\\");
688                   target=tmp+1;
689                 }
690                 strcat(ret, target);
691         }
692         return ret;
693 }