1/* Copyright 1995-1998,2000-2003,2005,2007-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 * mk_direntry.c 18 * Make new directory entries, and handles name clashes 19 * 20 */ 21 22/* 23 * This file is used by those commands that need to create new directory entries 24 */ 25 26#include "sysincludes.h" 27#include "msdos.h" 28#include "mtools.h" 29#include "vfat.h" 30#include "nameclash.h" 31#include "fs.h" 32#include "stream.h" 33#include "mainloop.h" 34#include "file_name.h" 35 36/** 37 * Converts input to shortname 38 * @param un unix name (in Unix charset) 39 * 40 * @return 1 if name had to be mangled 41 */ 42static __inline__ int convert_to_shortname(doscp_t *cp, ClashHandling_t *ch, 43 const char *un, dos_name_t *dn) 44{ 45 int mangled; 46 47 /* Then do conversion to dn */ 48 ch->name_converter(cp, un, 0, &mangled, dn); 49 dn->sentinel = '\0'; 50 return mangled; 51} 52 53static __inline__ void chomp(char *line) 54{ 55 int l = strlen(line); 56 while(l > 0 && (line[l-1] == '\n' || line[l-1] == '\r')) { 57 line[--l] = '\0'; 58 } 59} 60 61/** 62 * Asks for an alternative new name for a file, in case of a clash 63 */ 64static __inline__ int ask_rename(doscp_t *cp, ClashHandling_t *ch, 65 dos_name_t *shortname, 66 char *longname, 67 int isprimary) 68{ 69 int mangled; 70 71 /* TODO: Would be nice to suggest "autorenamed" version of name, press 72 * <Return> to get it. 73 */ 74#if 0 75 fprintf(stderr,"Entering ask_rename, isprimary=%d.\n", isprimary); 76#endif 77 78 if(!opentty(0)) 79 return 0; 80 81#define maxsize (isprimary ? MAX_VNAMELEN+1 : 11+1) 82#define name (isprimary ? argname : shortname) 83 84 mangled = 0; 85 do { 86 char tname[4*MAX_VNAMELEN+1]; 87 fprintf(stderr, "New %s name for \"%s\": ", 88 isprimary ? "primary" : "secondary", longname); 89 fflush(stderr); 90 if (! fgets(tname, 4*MAX_VNAMELEN+1, opentty(0))) 91 return 0; 92 chomp(tname); 93 if (isprimary) 94 strcpy(longname, tname); 95 else 96 mangled = convert_to_shortname(cp, 97 ch, tname, shortname); 98 } while (mangled & 1); 99 return 1; 100#undef maxsize 101#undef name 102} 103 104/** 105 * This function determines the action to be taken in case there is a problem 106 * with target name (clash, illegal characters, or reserved) 107 * The decision either comes from the default (ch), or the user will be 108 * prompted if there is no default 109 */ 110static __inline__ clash_action ask_namematch(doscp_t *cp, 111 dos_name_t *dosname, 112 char *longname, 113 int isprimary, 114 ClashHandling_t *ch, 115 int no_overwrite, 116 int reason) 117{ 118 /* User's answer letter (from keyboard). Only first letter is used, 119 * but we allocate space for 10 in order to account for extra garbage 120 * that user may enter 121 */ 122 char ans[10]; 123 124 /** 125 * Return value: action to be taken 126 */ 127 clash_action a; 128 129 /** 130 * Should this decision be made permanent (do no longer ask same 131 * question) 132 */ 133 int perm; 134 135 /** 136 * Buffer for shortname 137 */ 138 char name_buffer[4*13]; 139 140 /** 141 * Name to be printed 142 */ 143 char *name; 144 145#define EXISTS 0 146#define RESERVED 1 147#define ILLEGALS 2 148 149 static const char *reasons[]= { 150 "already exists", 151 "is reserved", 152 "contains illegal character(s)"}; 153 154 a = ch->action[isprimary]; 155 156 if(a == NAMEMATCH_NONE && !opentty(1)) { 157 /* no default, and no tty either . Skip the troublesome file */ 158 return NAMEMATCH_SKIP; 159 } 160 161 if (!isprimary) 162 name = unix_normalize(cp, name_buffer, dosname); 163 else 164 name = longname; 165 166 perm = 0; 167 while (a == NAMEMATCH_NONE) { 168 fprintf(stderr, "%s file name \"%s\" %s.\n", 169 isprimary ? "Long" : "Short", name, reasons[reason]); 170 fprintf(stderr, 171 "a)utorename A)utorename-all r)ename R)ename-all "); 172 if(!no_overwrite) 173 fprintf(stderr,"o)verwrite O)verwrite-all"); 174 fprintf(stderr, 175 "\ns)kip S)kip-all q)uit (aArR"); 176 if(!no_overwrite) 177 fprintf(stderr,"oO"); 178 fprintf(stderr,"sSq): "); 179 fflush(stderr); 180 fflush(opentty(1)); 181 if (mtools_raw_tty) { 182 int rep; 183 rep = fgetc(opentty(1)); 184 fputs("\n", stderr); 185 if(rep == EOF) 186 ans[0] = 'q'; 187 else 188 ans[0] = rep; 189 } else { 190 fgets(ans, 9, opentty(0)); 191 } 192 perm = isupper((unsigned char)ans[0]); 193 switch(tolower((unsigned char)ans[0])) { 194 case 'a': 195 a = NAMEMATCH_AUTORENAME; 196 break; 197 case 'r': 198 if(isprimary) 199 a = NAMEMATCH_PRENAME; 200 else 201 a = NAMEMATCH_RENAME; 202 break; 203 case 'o': 204 if(no_overwrite) 205 continue; 206 a = NAMEMATCH_OVERWRITE; 207 break; 208 case 's': 209 a = NAMEMATCH_SKIP; 210 break; 211 case 'q': 212 perm = 0; 213 a = NAMEMATCH_QUIT; 214 break; 215 default: 216 perm = 0; 217 } 218 } 219 220 /* Keep track of this action in case this file collides again */ 221 ch->action[isprimary] = a; 222 if (perm) 223 ch->namematch_default[isprimary] = a; 224 225 /* if we were asked to overwrite be careful. We can't set the action 226 * to overwrite, else we get won't get a chance to specify another 227 * action, should overwrite fail. Indeed, we'll be caught in an 228 * infinite loop because overwrite will fail the same way for the 229 * second time */ 230 if(a == NAMEMATCH_OVERWRITE) 231 ch->action[isprimary] = NAMEMATCH_NONE; 232 return a; 233} 234 235/* 236 * Processes a name match 237 * dosname short dosname (ignored if is_primary) 238 * 239 * 240 * Returns: 241 * 2 if file is to be overwritten 242 * 1 if file was renamed 243 * 0 if it was skipped 244 * 245 * If a short name is involved, handle conversion between the 11-character 246 * fixed-length record DOS name and a literal null-terminated name (e.g. 247 * "COMMAND COM" (no null) <-> "COMMAND.COM" (null terminated)). 248 * 249 * Also, immediately copy the original name so that messages can use it. 250 */ 251static __inline__ clash_action process_namematch(doscp_t *cp, 252 dos_name_t *dosname, 253 char *longname, 254 int isprimary, 255 ClashHandling_t *ch, 256 int no_overwrite, 257 int reason) 258{ 259 clash_action action; 260 261#if 0 262 fprintf(stderr, 263 "process_namematch: name=%s, default_action=%d, ask=%d.\n", 264 name, default_action, ch->ask); 265#endif 266 267 action = ask_namematch(cp, dosname, longname, 268 isprimary, ch, no_overwrite, reason); 269 270 switch(action){ 271 case NAMEMATCH_QUIT: 272 got_signal = 1; 273 return NAMEMATCH_SKIP; 274 case NAMEMATCH_SKIP: 275 return NAMEMATCH_SKIP; 276 case NAMEMATCH_RENAME: 277 case NAMEMATCH_PRENAME: 278 /* We need to rename the file now. This means we must pass 279 * back through the loop, a) ensuring there isn't a potential 280 * new name collision, and b) finding a big enough VSE. 281 * Change the name, so that it won't collide again. 282 */ 283 ask_rename(cp, ch, dosname, longname, isprimary); 284 return action; 285 case NAMEMATCH_AUTORENAME: 286 /* Very similar to NAMEMATCH_RENAME, except that we need to 287 * first generate the name. 288 * TODO: Remember previous name so we don't 289 * keep trying the same one. 290 */ 291 if (isprimary) { 292 autorename_long(longname, 1); 293 return NAMEMATCH_PRENAME; 294 } else { 295 autorename_short(dosname, 1); 296 return NAMEMATCH_RENAME; 297 } 298 case NAMEMATCH_OVERWRITE: 299 if(no_overwrite) 300 return NAMEMATCH_SKIP; 301 else 302 return NAMEMATCH_OVERWRITE; 303 default: 304 return NAMEMATCH_NONE; 305 } 306} 307 308static int contains_illegals(const char *string, const char *illegals, 309 int len) 310{ 311 for(; *string && len--; string++) 312 if((*string < ' ' && *string != '\005' && !(*string & 0x80)) || 313 strchr(illegals, *string)) 314 return 1; 315 return 0; 316} 317 318static int is_reserved(char *ans, int islong) 319{ 320 unsigned int i; 321 static const char *dev3[] = {"CON", "AUX", "PRN", "NUL", " "}; 322 static const char *dev4[] = {"COM", "LPT" }; 323 324 for (i = 0; i < sizeof(dev3)/sizeof(*dev3); i++) 325 if (!strncasecmp(ans, dev3[i], 3) && 326 ((islong && !ans[3]) || 327 (!islong && !strncmp(ans+3," ",5)))) 328 return 1; 329 330 for (i = 0; i < sizeof(dev4)/sizeof(*dev4); i++) 331 if (!strncasecmp(ans, dev4[i], 3) && 332 (ans[3] >= '1' && ans[3] <= '4') && 333 ((islong && !ans[4]) || 334 (!islong && !strncmp(ans+4," ",4)))) 335 return 1; 336 337 return 0; 338} 339 340static __inline__ clash_action get_slots(Stream_t *Dir, 341 dos_name_t *dosname, 342 char *longname, 343 struct scan_state *ssp, 344 ClashHandling_t *ch) 345{ 346 int error; 347 clash_action ret; 348 int match_pos=0; 349 direntry_t entry; 350 int isprimary; 351 int no_overwrite; 352 int reason; 353 int pessimisticShortRename; 354 doscp_t *cp = GET_DOSCONVERT(Dir); 355 356 pessimisticShortRename = (ch->action[0] == NAMEMATCH_AUTORENAME); 357 358 entry.Dir = Dir; 359 no_overwrite = 1; 360 if((is_reserved(longname,1)) || 361 longname[strspn(longname,". ")] == '\0'){ 362 reason = RESERVED; 363 isprimary = 1; 364 } else if(contains_illegals(longname,long_illegals,1024)) { 365 reason = ILLEGALS; 366 isprimary = 1; 367 } else if(is_reserved(dosname->base,0)) { 368 reason = RESERVED; 369 ch->use_longname = 1; 370 isprimary = 0; 371 } else if(contains_illegals(dosname->base,short_illegals,11)) { 372 reason = ILLEGALS; 373 ch->use_longname = 1; 374 isprimary = 0; 375 } else { 376 reason = EXISTS; 377 switch (lookupForInsert(Dir, 378 &entry, 379 dosname, longname, ssp, 380 ch->ignore_entry, 381 ch->source_entry, 382 pessimisticShortRename && 383 ch->use_longname, 384 ch->use_longname)) { 385 case -1: 386 return NAMEMATCH_ERROR; 387 388 case 0: 389 return NAMEMATCH_SKIP; 390 /* Single-file error error or skip request */ 391 392 case 5: 393 return NAMEMATCH_GREW; 394 /* Grew directory, try again */ 395 396 case 6: 397 return NAMEMATCH_SUCCESS; /* Success */ 398 } 399 match_pos = -2; 400 if (ssp->longmatch > -1) { 401 /* Primary Long Name Match */ 402#ifdef debug 403 fprintf(stderr, 404 "Got longmatch=%d for name %s.\n", 405 longmatch, longname); 406#endif 407 match_pos = ssp->longmatch; 408 isprimary = 1; 409 } else if ((ch->use_longname & 1) && (ssp->shortmatch != -1)) { 410 /* Secondary Short Name Match */ 411#ifdef debug 412 fprintf(stderr, 413 "Got secondary short name match for name %s.\n", 414 longname); 415#endif 416 417 match_pos = ssp->shortmatch; 418 isprimary = 0; 419 } else if (ssp->shortmatch >= 0) { 420 /* Primary Short Name Match */ 421#ifdef debug 422 fprintf(stderr, 423 "Got primary short name match for name %s.\n", 424 longname); 425#endif 426 match_pos = ssp->shortmatch; 427 isprimary = 1; 428 } else 429 return NAMEMATCH_RENAME; 430 431 if(match_pos > -1) { 432 entry.entry = match_pos; 433 dir_read(&entry, &error); 434 if (error) 435 return NAMEMATCH_ERROR; 436 /* if we can't overwrite, don't propose it */ 437 no_overwrite = (match_pos == ch->source || IS_DIR(&entry)); 438 } 439 } 440 ret = process_namematch(cp, dosname, longname, 441 isprimary, ch, no_overwrite, reason); 442 443 if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){ 444 if((entry.dir.attr & 0x5) && 445 (ask_confirmation("file is read only, overwrite anyway (y/n) ? "))) 446 return NAMEMATCH_RENAME; 447 /* Free up the file to be overwritten */ 448 if(fatFreeWithDirentry(&entry)) 449 return NAMEMATCH_ERROR; 450 451#if 0 452 if(isprimary && 453 match_pos - ssp->match_free + 1 >= ssp->size_needed){ 454 /* reuse old entry and old short name for overwrite */ 455 ssp->free_start = match_pos - ssp->size_needed + 1; 456 ssp->free_size = ssp->size_needed; 457 ssp->slot = match_pos; 458 ssp->got_slots = 1; 459 strncpy(dosname, dir.name, 3); 460 strncpy(dosname + 8, dir.ext, 3); 461 return ret; 462 } else 463#endif 464 { 465 wipeEntry(&entry); 466 return NAMEMATCH_RENAME; 467 } 468 } 469 470 return ret; 471} 472 473 474static __inline__ int write_slots(Stream_t *Dir, 475 dos_name_t *dosname, 476 char *longname, 477 struct scan_state *ssp, 478 write_data_callback *cb, 479 void *arg, 480 int Case) 481{ 482 direntry_t entry; 483 484 /* write the file */ 485 if (fat_error(Dir)) 486 return 0; 487 488 entry.Dir = Dir; 489 entry.entry = ssp->slot; 490 native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0); 491 entry.name[MAX_VNAMELEN]='\0'; 492 entry.dir.Case = Case & (EXTCASE | BASECASE); 493 if (cb(dosname, longname, arg, &entry) >= 0) { 494 if ((ssp->size_needed > 1) && 495 (ssp->free_end - ssp->free_start >= ssp->size_needed)) { 496 ssp->slot = write_vfat(Dir, dosname, longname, 497 ssp->free_start, &entry); 498 } else { 499 ssp->size_needed = 1; 500 write_vfat(Dir, dosname, 0, 501 ssp->free_start, &entry); 502 } 503 /* clear_vses(Dir, ssp->free_start + ssp->size_needed, 504 ssp->free_end); */ 505 } else 506 return 0; 507 508 return 1; /* Successfully wrote the file */ 509} 510 511static void stripspaces(char *name) 512{ 513 char *p,*non_space; 514 515 non_space = name; 516 for(p=name; *p; p++) 517 if (*p != ' ') 518 non_space = p; 519 if(name[0]) 520 non_space[1] = '\0'; 521} 522 523 524static int _mwrite_one(Stream_t *Dir, 525 char *argname, 526 char *shortname, 527 write_data_callback *cb, 528 void *arg, 529 ClashHandling_t *ch) 530{ 531 char longname[VBUFSIZE]; 532 const char *dstname; 533 dos_name_t dosname; 534 int expanded; 535 struct scan_state scan; 536 clash_action ret; 537 doscp_t *cp = GET_DOSCONVERT(Dir); 538 539 expanded = 0; 540 541 if(isSpecial(argname)) { 542 fprintf(stderr, "Cannot create entry named . or ..\n"); 543 return -1; 544 } 545 546 if(ch->name_converter == dos_name) { 547 if(shortname) 548 stripspaces(shortname); 549 if(argname) 550 stripspaces(argname); 551 } 552 553 if(shortname){ 554 convert_to_shortname(cp, ch, shortname, &dosname); 555 if(ch->use_longname & 1){ 556 /* short name mangled, treat it as a long name */ 557 argname = shortname; 558 shortname = 0; 559 } 560 } 561 562 if (argname[0] && (argname[1] == ':')) { 563 /* Skip drive letter */ 564 dstname = argname + 2; 565 } else { 566 dstname = argname; 567 } 568 569 /* Copy original argument dstname to working value longname */ 570 strncpy(longname, dstname, VBUFSIZE-1); 571 572 if(shortname) { 573 ch->use_longname = 574 convert_to_shortname(cp, ch, shortname, &dosname); 575 if(strcmp(shortname, longname)) 576 ch->use_longname |= 1; 577 } else { 578 ch->use_longname = 579 convert_to_shortname(cp, ch, longname, &dosname); 580 } 581 582 ch->action[0] = ch->namematch_default[0]; 583 ch->action[1] = ch->namematch_default[1]; 584 585 while (1) { 586 switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){ 587 case NAMEMATCH_ERROR: 588 return -1; /* Non-file-specific error, 589 * quit */ 590 591 case NAMEMATCH_SKIP: 592 return -1; /* Skip file (user request or 593 * error) */ 594 595 case NAMEMATCH_PRENAME: 596 ch->use_longname = 597 convert_to_shortname(cp, ch, 598 longname, 599 &dosname); 600 continue; 601 case NAMEMATCH_RENAME: 602 continue; /* Renamed file, loop again */ 603 604 case NAMEMATCH_GREW: 605 /* No collision, and not enough slots. 606 * Try to grow the directory 607 */ 608 if (expanded) { /* Already tried this 609 * once, no good */ 610 fprintf(stderr, 611 "%s: No directory slots\n", 612 progname); 613 return -1; 614 } 615 expanded = 1; 616 617 if (dir_grow(Dir, scan.max_entry)) 618 return -1; 619 continue; 620 case NAMEMATCH_OVERWRITE: 621 case NAMEMATCH_SUCCESS: 622 return write_slots(Dir, &dosname, longname, 623 &scan, cb, arg, 624 ch->use_longname); 625 default: 626 fprintf(stderr, 627 "Internal error: clash_action=%d\n", 628 ret); 629 return -1; 630 } 631 632 } 633} 634 635int mwrite_one(Stream_t *Dir, 636 const char *_argname, 637 const char *_shortname, 638 write_data_callback *cb, 639 void *arg, 640 ClashHandling_t *ch) 641{ 642 char *argname; 643 char *shortname; 644 int ret; 645 646 if(_argname) 647 argname = strdup(_argname); 648 else 649 argname = 0; 650 if(_shortname) 651 shortname = strdup(_shortname); 652 else 653 shortname = 0; 654 ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch); 655 if(argname) 656 free(argname); 657 if(shortname) 658 free(shortname); 659 return ret; 660} 661 662void init_clash_handling(ClashHandling_t *ch) 663{ 664 ch->ignore_entry = -1; 665 ch->source_entry = -2; 666 ch->nowarn = 0; /*Don't ask, just do default action if name collision */ 667 ch->namematch_default[0] = NAMEMATCH_AUTORENAME; 668 ch->namematch_default[1] = NAMEMATCH_NONE; 669 ch->name_converter = dos_name; /* changed by mlabel */ 670 ch->source = -2; 671} 672 673int handle_clash_options(ClashHandling_t *ch, char c) 674{ 675 int isprimary; 676 if(isupper(c)) 677 isprimary = 0; 678 else 679 isprimary = 1; 680 c = tolower(c); 681 switch(c) { 682 case 'o': 683 /* Overwrite if primary name matches */ 684 ch->namematch_default[isprimary] = NAMEMATCH_OVERWRITE; 685 return 0; 686 case 'r': 687 /* Rename primary name interactively */ 688 ch->namematch_default[isprimary] = NAMEMATCH_RENAME; 689 return 0; 690 case 's': 691 /* Skip file if primary name collides */ 692 ch->namematch_default[isprimary] = NAMEMATCH_SKIP; 693 return 0; 694 case 'm': 695 ch->namematch_default[isprimary] = NAMEMATCH_NONE; 696 return 0; 697 case 'a': 698 ch->namematch_default[isprimary] = NAMEMATCH_AUTORENAME; 699 return 0; 700 default: 701 return -1; 702 } 703} 704 705void dosnameToDirentry(const struct dos_name_t *dn, struct directory *dir) { 706 strncpy(dir->name, dn->base, 8); 707 strncpy(dir->ext, dn->ext, 3); 708} 709