Imported Upstream version 4.0.43
[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 static int _unix_loop(Stream_t *Dir, MainParam_t *mp,
87                       const char *filename UNUSEDP)
88 {
89         return unix_dir_loop(Dir, mp);
90 }
91
92 int unix_loop(Stream_t *Stream UNUSEDP, MainParam_t *mp,
93               char *arg, int follow_dir_link)
94 {
95         int ret;
96         int isdir=0;
97         size_t unixNameLength;
98
99         mp->File = NULL;
100         mp->direntry = NULL;
101         unixNameLength = strlen(arg);
102         if(unixNameLength > 1 && arg[unixNameLength-1] == '/') {
103             /* names ending in slash, and having at least two characters */
104             char *name = strdup(arg);
105             name[unixNameLength-1]='\0';
106             mp->unixSourceName = name;
107         } else {
108             mp->unixSourceName = arg;
109         }
110         /*      mp->dir.attr = ATTR_ARCHIVE;*/
111         mp->loop = _unix_loop;
112         if((mp->lookupflags & DO_OPEN)){
113                 mp->File = SimpleFileOpen(0, 0, arg, O_RDONLY, 0, 0, 0, 0);
114                 if(!mp->File){
115                         perror(arg);
116 #if 0
117                         tmp = _basename(arg);
118                         strncpy(mp->filename, tmp, VBUFSIZE);
119                         mp->filename[VBUFSIZE-1] = '\0';
120 #endif
121                         return ERROR_ONE;
122                 }
123                 GET_DATA(mp->File, 0, 0, &isdir, 0);
124                 if(isdir) {
125 #if !defined(__EMX__) && !defined(OS_mingw32msvc)
126                         struct MT_STAT buf;
127 #endif
128
129                         FREE(&mp->File);
130 #if !defined(__EMX__) && !defined(OS_mingw32msvc)
131                         if(!follow_dir_link &&
132                            MT_LSTAT(arg, &buf) == 0 &&
133                            S_ISLNK(buf.st_mode)) {
134                                 /* skip links to directories in order to avoid
135                                  * infinite loops */
136                                 fprintf(stderr,
137                                         "skipping directory symlink %s\n",
138                                         arg);
139                                 return 0;
140                         }
141 #endif
142                         if(! (mp->lookupflags & ACCEPT_DIR))
143                                 return 0;
144                         mp->File = OpenDir(arg);
145                 }
146         }
147
148         if(isdir)
149                 ret = mp->dirCallback(0, mp);
150         else
151                 ret = mp->unixcallback(mp);
152         FREE(&mp->File);
153         return ret;
154 }
155
156 static int checkForDot(int lookupflags, const wchar_t *name)
157 {
158         return (lookupflags & NO_DOTS) && isSpecialW(name);
159 }
160
161
162 typedef struct lookupState_t {
163         Stream_t *container;
164         int nbContainers;
165         Stream_t *Dir;
166         int nbDirs;
167         const char *filename;
168 } lookupState_t;
169
170 static int isUniqueTarget(const char *name)
171 {
172         return name && strcmp(name, "-");
173 }
174
175 static int handle_leaf(direntry_t *direntry, MainParam_t *mp,
176                        lookupState_t *lookupState,
177                        Stream_t **DeferredFileP)
178 {
179         Stream_t *MyFile=0;
180         int ret;
181
182         if(got_signal)
183                 return ERROR_ONE;
184         if(lookupState) {
185                 /* we are looking for a "target" file */
186                 switch(lookupState->nbDirs) {
187                         case 0: /* no directory yet, open it */
188                                 lookupState->Dir = OpenFileByDirentry(direntry);
189                                 lookupState->nbDirs++;
190                                 /* dump the container, we have
191                                  * better now */
192                                 FREE(&lookupState->container);
193                                 return 0;
194                         case 1: /* we have already a directory */
195                                 FREE(&lookupState->Dir);
196                                 fprintf(stderr,"Ambiguous\n");
197                                 return STOP_NOW | ERROR_ONE;
198                         default:
199                                 return STOP_NOW | ERROR_ONE;
200                 }
201         }
202
203         mp->direntry = direntry;
204         if(IS_DIR(direntry)) {
205                 if(mp->lookupflags & (DO_OPEN | DO_OPEN_DIRS))
206                         MyFile = mp->File = OpenFileByDirentry(direntry);
207                 ret = mp->dirCallback(direntry, mp);
208         } else {
209                 if(mp->lookupflags & DO_OPEN) {
210                         if(DeferredFileP && *DeferredFileP) {
211                                 /* Already a deferred file => close it and error */
212                                 FREE(DeferredFileP);
213                                 fprintf(stderr,
214                                         "Attempt to copy multiple files to non-directory\n");
215                                 return STOP_NOW | ERROR_ONE;
216                         }
217
218                         MyFile = mp->File = OpenFileByDirentry(direntry);
219                         if(DeferredFileP) {
220                                 *DeferredFileP = MyFile;
221                                 return 0;
222                         }
223                 }
224                 ret = mp->callback(direntry, mp);
225         }
226         FREE(&MyFile);
227         return ret;
228 }
229
230 static int _dos_loop(Stream_t *Dir, MainParam_t *mp, const char *filename)
231 {
232         Stream_t *MyFile=0;
233         direntry_t entry;
234         int ret;
235         int r;
236
237         ret = 0;
238         r=0;
239         initializeDirentry(&entry, Dir);
240         while(!got_signal &&
241               (r=vfat_lookup_zt(&entry, filename,
242                                 mp->lookupflags,
243                                 mp->shortname.data, mp->shortname.len,
244                                 mp->longname.data, mp->longname.len)) == 0 ){
245                 mp->File = NULL;
246                 if(!checkForDot(mp->lookupflags,entry.name)) {
247                         MyFile = 0;
248                         if((mp->lookupflags & DO_OPEN) ||
249                            (IS_DIR(&entry) &&
250                             (mp->lookupflags & DO_OPEN_DIRS))) {
251                                 MyFile = mp->File = OpenFileByDirentry(&entry);
252                         }
253                         if(got_signal)
254                                 break;
255                         mp->direntry = &entry;
256                         if(IS_DIR(&entry))
257                                 ret |= mp->dirCallback(&entry,mp);
258                         else
259                                 ret |= mp->callback(&entry, mp);
260                         FREE(&MyFile);
261                 }
262                 if (fat_error(Dir))
263                         ret |= ERROR_ONE;
264                 if(mp->fast_quit && (ret & ERROR_ONE))
265                         break;
266         }
267         if (r == -2)
268             return ERROR_ONE;
269         if(got_signal)
270                 ret |= ERROR_ONE;
271         return ret;
272 }
273
274 static int recurs_dos_loop(MainParam_t *mp, const char *filename0,
275                            const char *filename1,
276                            lookupState_t *lookupState,
277                            Stream_t **DeferredFileP)
278 {
279         /* Dir is de-allocated by the same entity which allocated it */
280         const char *ptr;
281         direntry_t entry;
282         size_t length;
283         int lookupflags;
284         int ret;
285         int have_one;
286         int doing_mcwd;
287         int r;
288
289         while(1) {
290                 /* strip dots and / */
291                 if(!strncmp(filename0,"./", 2)) {
292                         filename0 += 2;
293                         continue;
294                 }
295                 if(!strcmp(filename0,".") && filename1) {
296                         filename0 ++;
297                         continue;
298                 }
299                 if(filename0[0] == '/') {
300                         filename0++;
301                         continue;
302                 }
303                 if(!filename0[0]) {
304                         if(!filename1)
305                                 break;
306                         filename0 = filename1;
307                         filename1 = 0;
308                         continue;
309                 }
310                 break;
311         }
312
313         if(!strncmp(filename0,"../", 3) ||
314            (!strcmp(filename0, "..") && filename1)) {
315                 /* up one level */
316                 mp->File = getDirentry(mp->File)->Dir;
317                 return recurs_dos_loop(mp, filename0+2, filename1, lookupState,
318                                        DeferredFileP);
319         }
320
321         doing_mcwd = !!filename1;
322
323         ptr = strchr(filename0, '/');
324         if(!ptr) {
325                 length = strlen(filename0);
326                 ptr = filename1;
327                 filename1 = 0;
328         } else {
329                 length = ptrdiff(ptr, filename0);
330                 ptr++;
331         }
332         if(!ptr) {
333                 if(mp->lookupflags & OPEN_PARENT) {
334                         mp->targetName = filename0;
335                         ret = handle_leaf(getDirentry(mp->File), mp,
336                                           lookupState, NULL);
337                         mp->targetName = 0;
338                         return ret;
339                 }
340
341                 if(!strcmp(filename0, ".") || !filename0[0]) {
342                         return handle_leaf(getDirentry(mp->File),
343                                            mp, lookupState, NULL);
344                 }
345
346                 if(!strcmp(filename0, "..")) {
347                         return handle_leaf(getParent(getDirentry(mp->File)), mp,
348                                            lookupState, NULL);
349                 }
350
351                 lookupflags = mp->lookupflags;
352
353                 if(lookupState) {
354                         lookupState->filename = filename0;
355                         if(lookupState->nbContainers + lookupState->nbDirs > 0){
356                                 /* we have already one target, don't bother
357                                  * with this one. */
358                                 FREE(&lookupState->container);
359                         } else {
360                                 /* no match yet.  Remember this container for
361                                  * later use */
362                                 lookupState->container = COPY(mp->File);
363                         }
364                         lookupState->nbContainers++;
365                 }
366         } else
367                 lookupflags = ACCEPT_DIR | DO_OPEN | NO_DOTS;
368
369         ret = 0;
370         r = 0;
371         have_one = 0;
372         initializeDirentry(&entry, mp->File);
373         while(!(ret & STOP_NOW) &&
374               !got_signal &&
375               (r=vfat_lookup(&entry, filename0, length,
376                              lookupflags | NO_MSG,
377                              mp->shortname.data, mp->shortname.len,
378                              mp->longname.data, mp->longname.len)) == 0 ){
379                 if(checkForDot(lookupflags, entry.name))
380                         /* while following the path, ignore the
381                          * special entries if they were not
382                          * explicitly given */
383                         continue;
384                 have_one = 1;
385                 if(ptr) {
386                         Stream_t *SubDir;
387                         SubDir = mp->File = OpenFileByDirentry(&entry);
388                         ret |= recurs_dos_loop(mp, ptr, filename1, lookupState,
389                                                DeferredFileP);
390                         FREE(&SubDir);
391                 } else {
392                         ret |= handle_leaf(&entry, mp, lookupState,
393                                            DeferredFileP);
394                 }
395                 if(doing_mcwd)
396                         break;
397         }
398         if (r == -2)
399                 return ERROR_ONE;
400         if(got_signal)
401                 return ret | ERROR_ONE;
402         if(doing_mcwd && !have_one)
403                 return NO_CWD;
404         return ret;
405 }
406
407 static int common_dos_loop(MainParam_t *mp, const char *pathname,
408                            lookupState_t *lookupState, int open_mode)
409
410 {
411         Stream_t *RootDir;
412         const char *cwd;
413         char drive;
414         Stream_t *DeferredFile=NULL;
415         Stream_t **DeferredFileP=NULL;
416
417         int ret;
418         mp->loop = _dos_loop;
419
420         drive='\0';
421         cwd = "";
422         if(*pathname && pathname[1] == ':') {
423                 drive = ch_toupper(*pathname);
424                 pathname += 2;
425                 if(mp->mcwd[0] == drive)
426                         cwd = mp->mcwd+2;
427         } else if(mp->mcwd[0]) {
428                 drive = mp->mcwd[0];
429                 cwd = mp->mcwd+2;
430         } else {
431                 drive = get_default_drive();
432         }
433
434         if(*pathname=='/') /* absolute path name */
435                 cwd = "";
436
437         RootDir = mp->File = open_root_dir(drive, open_mode, NULL);
438         if(!mp->File)
439                 return ERROR_ONE;
440
441         if(mp->originalArg && strpbrk(mp->originalArg, "*[?") != 0 &&
442            (mp->lookupflags & DEFERABLE) &&
443            isUniqueTarget(mp->targetName))
444                 DeferredFileP = &DeferredFile;
445         
446         ret = recurs_dos_loop(mp, cwd, pathname, lookupState, DeferredFileP);
447         if(ret & NO_CWD) {
448                 /* no CWD */
449                 *mp->mcwd = '\0';
450                 unlink_mcwd();
451                 ret = recurs_dos_loop(mp, "", pathname, lookupState, DeferredFileP);
452         }
453         if(DeferredFile) {
454                 mp->File = DeferredFile;
455                 ret = mp->callback(NULL, mp);
456                 FREE(&DeferredFile);
457         }
458         FREE(&RootDir);
459         return ret;
460 }
461
462 static int dos_loop(MainParam_t *mp, const char *arg)
463 {
464         return common_dos_loop(mp, arg, 0, mp->openflags);
465 }
466
467
468 int dos_target_lookup(MainParam_t *mp, const char *arg)
469 {
470         lookupState_t lookupState;
471         int ret;
472         int lookupflags;
473
474         lookupState.nbDirs = 0;
475         lookupState.Dir = 0;
476         lookupState.nbContainers = 0;
477         lookupState.container = 0;
478
479         lookupflags = mp->lookupflags;
480         mp->lookupflags = DO_OPEN | ACCEPT_DIR;
481         ret = common_dos_loop(mp, arg, &lookupState, O_RDWR);
482         mp->lookupflags = lookupflags;
483         if(ret & ERROR_ONE)
484                 return ret;
485
486         if(lookupState.nbDirs) {
487                 mp->targetName = 0;
488                 mp->targetDir = lookupState.Dir;
489                 FREE(&lookupState.container); /* container no longer needed */
490                 return ret;
491         }
492
493         switch(lookupState.nbContainers) {
494                 case 0:
495                         /* no match */
496                         fprintf(stderr,"%s: no match for target\n", arg);
497                         return MISSED_ONE;
498                 case 1:
499                         mp->targetName = strdup(lookupState.filename);
500                         mp->targetDir = lookupState.container;
501                         return ret;
502                 default:
503                         /* too much */
504                         fprintf(stderr, "Ambiguous %s\n", arg);
505                         return ERROR_ONE;
506         }
507 }
508
509 int main_loop(MainParam_t *mp, char **argv, int argc)
510 {
511         int i;
512         int ret, Bret;
513
514         Bret = 0;
515
516         if(argc != 1 && mp->targetName) {
517                 fprintf(stderr,
518                         "Several file names given, but last argument (%s) not a directory\n", mp->targetName);
519                 FREE(&mp->targetDir);
520                 return 1;
521         }
522
523         for (i = 0; i < argc; i++) {
524                 if ( got_signal )
525                         break;
526                 mp->originalArg = argv[i];
527                 mp->basenameHasWildcard = strpbrk(_basename(mp->originalArg),
528                                                   "*[?") != 0;
529                 if (mp->unixcallback && (!argv[i][0]
530 #ifdef OS_mingw32msvc
531 /* On Windows, support only the command-line image drive. */
532                                          || argv[i][0] != ':'
533 #endif
534                                          || argv[i][1] != ':' ))
535                         ret = unix_loop(0, mp, argv[i], 1);
536                 else
537                         ret = dos_loop(mp, argv[i]);
538
539                 if (! (ret & (GOT_ONE | ERROR_ONE)) ) {
540                         /* one argument was unmatched */
541                         fprintf(stderr, "%s: File \"%s\" not found\n",
542                                 progname, argv[i]);
543                         ret |= ERROR_ONE;
544                 }
545                 Bret |= ret;
546                 if(mp->fast_quit && (Bret & (MISSED_ONE | ERROR_ONE)))
547                         break;
548         }
549         FREE(&mp->targetDir);
550         if(Bret & ERROR_ONE)
551                 return 1;
552         if ((Bret & GOT_ONE) && ( Bret & MISSED_ONE))
553                 return 2;
554         if (Bret & MISSED_ONE)
555                 return 1;
556         return 0;
557 }
558
559 static int dispatchToFile(direntry_t *entry, MainParam_t *mp)
560 {
561         if(entry)
562                 return mp->callback(entry, mp);
563         else
564                 return mp->unixcallback(mp);
565 }
566
567
568 void init_mp(MainParam_t *mp)
569 {
570         fix_mcwd(mp->mcwd);
571         mp->openflags = O_RDONLY;
572         mp->lookupflags = 0;
573         mp->targetName = 0;
574         mp->targetDir = 0;
575         mp->dirCallback = dispatchToFile;
576         mp->unixcallback = NULL;
577         mp->shortname.data = mp->longname.data = 0;
578         mp->shortname.len = mp->longname.len = 0;
579         mp->File = 0;
580         mp->fast_quit = 0;
581         mp->originalArg = 0;
582 }
583
584 const char *mpGetBasename(MainParam_t *mp)
585 {
586         if(mp->direntry) {
587                 wchar_to_native(mp->direntry->name, mp->targetBuffer,
588                                 MAX_VNAMELEN+1, sizeof(mp->targetBuffer));
589                 return mp->targetBuffer;
590         } else
591                 return _basename(mp->unixSourceName);
592 }
593
594 void mpPrintFilename(FILE *fp, MainParam_t *mp)
595 {
596         if(mp->direntry)
597                 fprintPwd(fp, mp->direntry, 0);
598         else
599                 fprintf(fp,"%s",mp->originalArg);
600 }
601
602 const char *mpPickTargetName(MainParam_t *mp)
603 {
604         /* picks the target name: either the one explicitly given by the
605          * user, or the same as the source */
606         if(mp->targetName)
607                 return mp->targetName;
608         else
609                 return mpGetBasename(mp);
610 }