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 37static 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 strncpy(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 = 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 88int unix_dir_loop(Stream_t *Stream, MainParam_t *mp); 89int unix_loop(Stream_t *Stream, MainParam_t *mp, char *arg, 90 int follow_dir_link); 91 92static int _unix_loop(Stream_t *Dir, MainParam_t *mp, const char *filename) 93{ 94 return unix_dir_loop(Dir, mp); 95} 96 97int unix_loop(Stream_t *Stream, MainParam_t *mp, char *arg, int follow_dir_link) 98{ 99 int ret; 100 int isdir; 101 int 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(Stream, 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 161int 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 173int 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 185static int checkForDot(int lookupflags, const wchar_t *name) 186{ 187 return (lookupflags & NO_DOTS) && isSpecialW(name); 188} 189 190 191typedef 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 199static int isUniqueTarget(const char *name) 200{ 201 return name && strcmp(name, "-"); 202} 203 204static 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,"Ambigous\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 247static 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(&entry, filename, -1, 259 mp->lookupflags, mp->shortname, 260 mp->longname)) == 0 ){ 261 mp->File = NULL; 262 if(!checkForDot(mp->lookupflags,entry.name)) { 263 MyFile = 0; 264 if((mp->lookupflags & DO_OPEN) || 265 (IS_DIR(&entry) && 266 (mp->lookupflags & DO_OPEN_DIRS))) { 267 MyFile = mp->File = OpenFileByDirentry(&entry); 268 } 269 if(got_signal) 270 break; 271 mp->direntry = &entry; 272 if(IS_DIR(&entry)) 273 ret |= mp->dirCallback(&entry,mp); 274 else 275 ret |= mp->callback(&entry, mp); 276 FREE(&MyFile); 277 } 278 if (fat_error(Dir)) 279 ret |= ERROR_ONE; 280 if(mp->fast_quit && (ret & ERROR_ONE)) 281 break; 282 } 283 if (r == -2) 284 return ERROR_ONE; 285 if(got_signal) 286 ret |= ERROR_ONE; 287 return ret; 288} 289 290static int recurs_dos_loop(MainParam_t *mp, const char *filename0, 291 const char *filename1, 292 lookupState_t *lookupState) 293{ 294 /* Dir is de-allocated by the same entity which allocated it */ 295 const char *ptr; 296 direntry_t entry; 297 int length; 298 int lookupflags; 299 int ret; 300 int have_one; 301 int doing_mcwd; 302 int r; 303 304 while(1) { 305 /* strip dots and / */ 306 if(!strncmp(filename0,"./", 2)) { 307 filename0 += 2; 308 continue; 309 } 310 if(!strcmp(filename0,".") && filename1) { 311 filename0 ++; 312 continue; 313 } 314 if(filename0[0] == '/') { 315 filename0++; 316 continue; 317 } 318 if(!filename0[0]) { 319 if(!filename1) 320 break; 321 filename0 = filename1; 322 filename1 = 0; 323 continue; 324 } 325 break; 326 } 327 328 if(!strncmp(filename0,"../", 3) || 329 (!strcmp(filename0, "..") && filename1)) { 330 /* up one level */ 331 mp->File = getDirentry(mp->File)->Dir; 332 return recurs_dos_loop(mp, filename0+2, filename1, lookupState); 333 } 334 335 doing_mcwd = !!filename1; 336 337 ptr = strchr(filename0, '/'); 338 if(!ptr) { 339 length = strlen(filename0); 340 ptr = filename1; 341 filename1 = 0; 342 } else { 343 length = ptr - filename0; 344 ptr++; 345 } 346 if(!ptr) { 347 if(mp->lookupflags & OPEN_PARENT) { 348 mp->targetName = filename0; 349 ret = handle_leaf(getDirentry(mp->File), mp, 350 lookupState); 351 mp->targetName = 0; 352 return ret; 353 } 354 355 if(!strcmp(filename0, ".") || !filename0[0]) { 356 return handle_leaf(getDirentry(mp->File), 357 mp, lookupState); 358 } 359 360 if(!strcmp(filename0, "..")) { 361 return handle_leaf(getParent(getDirentry(mp->File)), mp, 362 lookupState); 363 } 364 365 lookupflags = mp->lookupflags; 366 367 if(lookupState) { 368 lookupState->filename = filename0; 369 if(lookupState->nbContainers + lookupState->nbDirs > 0){ 370 /* we have already one target, don't bother 371 * with this one. */ 372 FREE(&lookupState->container); 373 } else { 374 /* no match yet. Remember this container for 375 * later use */ 376 lookupState->container = COPY(mp->File); 377 } 378 lookupState->nbContainers++; 379 } 380 } else 381 lookupflags = ACCEPT_DIR | DO_OPEN | NO_DOTS; 382 383 ret = 0; 384 r = 0; 385 have_one = 0; 386 initializeDirentry(&entry, mp->File); 387 while(!(ret & STOP_NOW) && 388 !got_signal && 389 (r=vfat_lookup(&entry, filename0, length, 390 lookupflags | NO_MSG, 391 mp->shortname, mp->longname)) == 0 ){ 392 if(checkForDot(lookupflags, entry.name)) 393 /* while following the path, ignore the 394 * special entries if they were not 395 * explicitly given */ 396 continue; 397 have_one = 1; 398 if(ptr) { 399 Stream_t *SubDir; 400 SubDir = mp->File = OpenFileByDirentry(&entry); 401 ret |= recurs_dos_loop(mp, ptr, filename1, lookupState); 402 FREE(&SubDir); 403 } else { 404 ret |= handle_leaf(&entry, mp, lookupState); 405 if(isUniqueTarget(mp->targetName)) 406 return ret | STOP_NOW; 407 } 408 if(doing_mcwd) 409 break; 410 } 411 if (r == -2) 412 return ERROR_ONE; 413 if(got_signal) 414 return ret | ERROR_ONE; 415 if(doing_mcwd && !have_one) 416 return NO_CWD; 417 return ret; 418} 419 420static int common_dos_loop(MainParam_t *mp, const char *pathname, 421 lookupState_t *lookupState, int open_mode) 422 423{ 424 Stream_t *RootDir; 425 const char *cwd; 426 char drive; 427 428 int ret; 429 mp->loop = _dos_loop; 430 431 drive='\0'; 432 cwd = ""; 433 if(*pathname && pathname[1] == ':') { 434 drive = toupper(*pathname); 435 pathname += 2; 436 if(mp->mcwd[0] == drive) 437 cwd = mp->mcwd+2; 438 } else if(mp->mcwd[0]) { 439 drive = mp->mcwd[0]; 440 cwd = mp->mcwd+2; 441 } else { 442 drive = get_default_drive(); 443 } 444 445 if(*pathname=='/') /* absolute path name */ 446 cwd = ""; 447 448 RootDir = mp->File = open_root_dir(drive, open_mode, NULL); 449 if(!mp->File) 450 return ERROR_ONE; 451 452 ret = recurs_dos_loop(mp, cwd, pathname, lookupState); 453 if(ret & NO_CWD) { 454 /* no CWD */ 455 *mp->mcwd = '\0'; 456 unlink_mcwd(); 457 ret = recurs_dos_loop(mp, "", pathname, lookupState); 458 } 459 FREE(&RootDir); 460 return ret; 461} 462 463static int dos_loop(MainParam_t *mp, const char *arg) 464{ 465 return common_dos_loop(mp, arg, 0, mp->openflags); 466} 467 468 469static int dos_target_lookup(MainParam_t *mp, const char *arg) 470{ 471 lookupState_t lookupState; 472 int ret; 473 int lookupflags; 474 475 lookupState.nbDirs = 0; 476 lookupState.Dir = 0; 477 lookupState.nbContainers = 0; 478 lookupState.container = 0; 479 480 lookupflags = mp->lookupflags; 481 mp->lookupflags = DO_OPEN | ACCEPT_DIR; 482 ret = common_dos_loop(mp, arg, &lookupState, O_RDWR); 483 mp->lookupflags = lookupflags; 484 if(ret & ERROR_ONE) 485 return ret; 486 487 if(lookupState.nbDirs) { 488 mp->targetName = 0; 489 mp->targetDir = lookupState.Dir; 490 FREE(&lookupState.container); /* container no longer needed */ 491 return ret; 492 } 493 494 switch(lookupState.nbContainers) { 495 case 0: 496 /* no match */ 497 fprintf(stderr,"%s: no match for target\n", arg); 498 return MISSED_ONE; 499 case 1: 500 mp->targetName = strdup(lookupState.filename); 501 mp->targetDir = lookupState.container; 502 return ret; 503 default: 504 /* too much */ 505 fprintf(stderr, "Ambigous %s\n", arg); 506 return ERROR_ONE; 507 } 508} 509 510static int unix_target_lookup(MainParam_t *mp, const char *arg) 511{ 512 char *ptr; 513 mp->unixTarget = strdup(arg); 514 /* try complete filename */ 515 if(access(mp->unixTarget, F_OK) == 0) 516 return GOT_ONE; 517 ptr = strrchr(mp->unixTarget, '/'); 518 if(!ptr) { 519 mp->targetName = mp->unixTarget; 520 mp->unixTarget = strdup("."); 521 return GOT_ONE; 522 } else { 523 *ptr = '\0'; 524 mp->targetName = ptr+1; 525 return GOT_ONE; 526 } 527} 528 529int target_lookup(MainParam_t *mp, const char *arg) 530{ 531 if((mp->lookupflags & NO_UNIX) || (arg[0] 532#ifdef OS_mingw32msvc 533/* On Windows, support only the command-line image drive. */ 534 && arg[0] == ':' 535#endif 536 && arg[1] == ':' )) 537 return dos_target_lookup(mp, arg); 538 else 539 return unix_target_lookup(mp, arg); 540} 541 542int main_loop(MainParam_t *mp, char **argv, int argc) 543{ 544 int i; 545 int ret, Bret; 546 547 Bret = 0; 548 549 if(argc != 1 && mp->targetName) { 550 fprintf(stderr, 551 "Several file names given, but last argument (%s) not a directory\n", mp->targetName); 552 } 553 554 for (i = 0; i < argc; i++) { 555 if ( got_signal ) 556 break; 557 mp->originalArg = argv[i]; 558 mp->basenameHasWildcard = strpbrk(_basename(mp->originalArg), 559 "*[?") != 0; 560 if (mp->unixcallback && (!argv[i][0] 561#ifdef OS_mingw32msvc 562/* On Windows, support only the command-line image drive. */ 563 || argv[i][0] != ':' 564#endif 565 || argv[i][1] != ':' )) 566 ret = unix_loop(0, mp, argv[i], 1); 567 else 568 ret = dos_loop(mp, argv[i]); 569 570 if (! (ret & (GOT_ONE | ERROR_ONE)) ) { 571 /* one argument was unmatched */ 572 fprintf(stderr, "%s: File \"%s\" not found\n", 573 progname, argv[i]); 574 ret |= ERROR_ONE; 575 } 576 Bret |= ret; 577 if(mp->fast_quit && (Bret & (MISSED_ONE | ERROR_ONE))) 578 break; 579 } 580 FREE(&mp->targetDir); 581 if(Bret & ERROR_ONE) 582 return 1; 583 if ((Bret & GOT_ONE) && ( Bret & MISSED_ONE)) 584 return 2; 585 if (Bret & MISSED_ONE) 586 return 1; 587 return 0; 588} 589 590static int dispatchToFile(direntry_t *entry, MainParam_t *mp) 591{ 592 if(entry) 593 return mp->callback(entry, mp); 594 else 595 return mp->unixcallback(mp); 596} 597 598 599void init_mp(MainParam_t *mp) 600{ 601 fix_mcwd(mp->mcwd); 602 mp->openflags = O_RDONLY; 603 mp->targetName = 0; 604 mp->targetDir = 0; 605 mp->unixTarget = 0; 606 mp->dirCallback = dispatchToFile; 607 mp->unixcallback = NULL; 608 mp->shortname = mp->longname = 0; 609 mp->File = 0; 610 mp->fast_quit = 0; 611} 612 613const char *mpGetBasename(MainParam_t *mp) 614{ 615 if(mp->direntry) { 616 wchar_to_native(mp->direntry->name, mp->targetBuffer, 617 MAX_VNAMELEN+1); 618 return mp->targetBuffer; 619 } else 620 return _basename(mp->unixSourceName); 621} 622 623void mpPrintFilename(FILE *fp, MainParam_t *mp) 624{ 625 if(mp->direntry) 626 fprintPwd(fp, mp->direntry, 0); 627 else 628 fprintf(fp,"%s",mp->originalArg); 629} 630 631const char *mpPickTargetName(MainParam_t *mp) 632{ 633 /* picks the target name: either the one explicitly given by the 634 * user, or the same as the source */ 635 if(mp->targetName) 636 return mp->targetName; 637 else 638 return mpGetBasename(mp); 639} 640 641char *mpBuildUnixFilename(MainParam_t *mp) 642{ 643 const char *target; 644 char *ret; 645 char *tmp; 646 647 target = mpPickTargetName(mp); 648 ret = malloc(strlen(mp->unixTarget) + 2 + strlen(target)); 649 if(!ret) 650 return 0; 651 strcpy(ret, mp->unixTarget); 652 if(*target) { 653#if 1 /* fix for 'mcopy -n x:file existingfile' -- H. Lermen 980816 */ 654 if(!mp->targetName && !mp->targetDir) { 655 struct MT_STAT buf; 656 if (!MT_STAT(ret, &buf) && !S_ISDIR(buf.st_mode)) 657 return ret; 658 } 659#endif 660 strcat(ret, "/"); 661 if(!strcmp(target, ".")) { 662 target="DOT"; 663 } else if(!strcmp(target, "..")) { 664 target="DOTDOT"; 665 } 666 while( (tmp=strchr(target, '/')) ) { 667 strncat(ret, target, tmp-target); 668 strcat(ret, "\\"); 669 target=tmp+1; 670 } 671 strcat(ret, target); 672 } 673 return ret; 674} 675