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