1/* 2 Copyright 1999-2001, Be Incorporated. All Rights Reserved. 3 This file may be used under the terms of the Be Sample Code License. 4*/ 5#include <KernelExport.h> 6 7#include <fsproto.h> 8#include <lock.h> 9#include <cache.h> 10 11#include <stdlib.h> 12#include <dirent.h> 13#include <stdio.h> 14#include <string.h> 15#include <time.h> 16 17#include "iter.h" 18#include "dosfs.h" 19#include "attr.h" 20#include "dir.h" 21#include "dlist.h" 22#include "fat.h" 23#include "util.h" 24#include "vcache.h" 25 26#include "encodings.h" 27 28#define DPRINTF(a,b) if (debug_dir > (a)) dprintf b 29 30// used here and in encodings.cpp 31const char acceptable[]="!#$%&'()-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`{}~"; 32const char illegal[] = "\\/:*?\"<>|"; 33 34#define DIRCOOKIE_MAGIC 'AiC ' 35 36typedef struct dircookie 37{ 38 uint32 magic; 39 uint32 current_index; 40} dircookie; 41 42static CHECK_MAGIC(dircookie,struct dircookie, DIRCOOKIE_MAGIC) 43static status_t findfile(nspace *vol, vnode *dir, const char *file, 44 vnode_id *vnid, vnode **node, bool check_case, 45 bool check_dups, bool *dups_exist); 46 47// private structure for returning data from _next_dirent_() 48struct _dirent_info_ { 49 uint32 sindex; 50 uint32 eindex; 51 uint32 mode; 52 uint32 cluster; 53 uint32 size; 54 uint32 time; 55}; 56 57// scans dir for the next entry, using the state stored in a struct diri. 58static status_t _next_dirent_(struct diri *iter, struct _dirent_info_ *oinfo, 59 char *filename, int len) 60{ 61 uint8 *buffer, hash = 0; /* quiet warning */ 62 uchar uni[1024]; 63 uint16 *puni; 64 uint32 i; 65 66 // lfn state 67 uint32 start_index = 0xffff, filename_len = 0 /* quiet warning */, 68 lfn_count = 0 /* quiet warning */; 69 70 if (check_diri_magic(iter, "_next_dirent_")) return EINVAL; 71 72 if (iter->current_block == NULL) 73 return ENOENT; 74 75 if (len < 15) { 76 DPRINTF(0, ("_next_dirent_: len too short (%x)\n", len)); 77 return ENOMEM; 78 } 79 80 buffer = iter->current_block + ((iter->current_index) % (iter->csi.vol->bytes_per_sector / 0x20)) * 0x20; 81 82 for (;buffer != NULL;buffer = diri_next_entry(iter)) { 83 DPRINTF(2, ("_next_dirent_: %lx/%lx/%lx\n", iter->csi.cluster, iter->csi.sector, iter->current_index)); 84 if (buffer[0] == 0) { // quit if at end of table 85 if (start_index != 0xffff) { 86 dprintf("lfn entry (%s) with no alias\n", filename); 87 } 88 return ENOENT; 89 } 90 91 if (buffer[0] == 0xe5) { // skip erased entries 92 if (start_index != 0xffff) { 93 dprintf("lfn entry (%s) with intervening erased entries\n", filename); 94 start_index = 0xffff; 95 } 96 DPRINTF(2, ("entry erased, skipping...\n")); 97 continue; 98 } 99 100 if (buffer[0xb] == 0xf) { // long file name 101 if ((buffer[0xc] != 0) || 102 (buffer[0x1a] != 0) || (buffer[0x1b] != 0)) { 103 dprintf("invalid long file name: reserved fields munged\n"); 104 continue; 105 } 106 if (start_index == 0xffff) { 107 if ((buffer[0] & 0x40) == 0) { 108 dprintf("bad lfn start entry in directory\n"); 109 continue; 110 } 111 hash = buffer[0xd]; 112 lfn_count = buffer[0] & 0x1f; 113 start_index = iter->current_index; 114 puni = (uint16 *)(uni + 2*13*(lfn_count - 1)); 115 for (i=1;i<0x20;i+=2) { 116 if (*(uint16 *)&buffer[i] == 0xffff) 117 break; 118 *puni++ = *(uint16 *)&buffer[i]; 119 if (i == 0x9) i+=3; 120 if (i == 0x18) i+=2; 121 } 122 *puni++ = 0; 123 filename_len = (uchar *)(puni) - uni; 124 125 continue; 126 } else { 127 if (buffer[0xd] != hash) { 128 dprintf("error in long file name: hash values don't match\n"); 129 start_index = 0xffff; 130 continue; 131 } 132 if (buffer[0] != --lfn_count) { 133 dprintf("bad lfn entry in directory\n"); 134 start_index = 0xffff; 135 continue; 136 } 137 138 puni = (uint16 *)(uni + 2*13*(lfn_count - 1)); 139 for (i=1;i<0x20;i+=2) { 140 if ((buffer[i] == 0xff) && (buffer[i+1] == 0xff)) { 141 dprintf("bad lfn entry in directory\n"); 142 start_index = 0xffff; 143 break; 144 } 145 *puni++ = *(uint16 *)&buffer[i]; 146 if (i == 0x9) i+=3; 147 if (i == 0x18) i+=2; 148 } 149 continue; 150 } 151 } 152 153 break; 154 } 155 156 // hit end of directory entries with no luck 157 if (buffer == NULL) 158 return ENOENT; 159 160 // process long name 161 if (start_index != 0xffff) { 162 if (lfn_count != 1) { 163 dprintf("unfinished lfn in directory\n"); 164 start_index = 0xffff; 165 } else { 166 if (unicode_to_utf8(uni, filename_len, (uint8*)filename, len)) { 167 // rewind to beginning of call 168 dprintf("error: long file name too long\n"); 169 170 diri_free(iter); 171 diri_init(iter->csi.vol, iter->starting_cluster, start_index, iter); 172 return ENAMETOOLONG; 173 } else if (hash_msdos_name((const char *)buffer) != hash) { 174 dprintf("error: long file name (%s) hash and short file name don't match\n", filename); 175 start_index = 0xffff; 176 } 177 } 178 } 179 180 // process short name 181 if (start_index == 0xffff) { 182 start_index = iter->current_index; 183 // korli : seen on FreeBSD /src/sys/fs/msdosfs/direntry.h 184 msdos_to_utf8(buffer, (uchar *)filename, len, buffer[0xc] & 0x18); 185 } 186 187 if (oinfo) { 188 oinfo->sindex = start_index; 189 oinfo->eindex = iter->current_index; 190 oinfo->mode = buffer[0xb]; 191 oinfo->cluster = read16(buffer,0x1a); 192 if (iter->csi.vol->fat_bits == 32) 193 oinfo->cluster += 0x10000*read16(buffer,0x14); 194 oinfo->size = read32(buffer,0x1c); 195 oinfo->time = read32(buffer,0x16); 196 } 197 198 diri_next_entry(iter); 199 200 return B_NO_ERROR; 201} 202 203static status_t get_next_dirent(nspace *vol, vnode *dir, struct diri *iter, 204 vnode_id *vnid, char *filename, int len) 205{ 206 struct _dirent_info_ info; 207 status_t result; 208 209 if (check_nspace_magic(vol, "get_next_dirent")) return EINVAL; 210 211 do { 212 result = _next_dirent_(iter, &info, filename, len); 213 214 if (result < 0) return result; 215 // only hide volume label entries in the root directory 216 } while ((info.mode & FAT_VOLUME) && (dir->vnid == vol->root_vnode.vnid)); 217 218 if (!strcmp(filename, ".")) { 219 // assign vnode based on parent 220 if (vnid) *vnid = dir->vnid; 221 } else if (!strcmp(filename, "..")) { 222 // assign vnode based on parent of parent 223 if (vnid) *vnid = dir->dir_vnid; 224 } else { 225 if (vnid) { 226 vnode_id loc = (IS_DATA_CLUSTER(info.cluster)) ? 227 GENERATE_DIR_CLUSTER_VNID(dir->vnid, info.cluster) : 228 GENERATE_DIR_INDEX_VNID(dir->vnid, info.sindex); 229 bool added_to_vcache = false; 230 231 /* if it matches a loc in the lookup table, we are done. */ 232 result = vcache_loc_to_vnid(vol, loc, vnid); 233 if (result == ENOENT) { 234 /* ...else check if it matches any vnid's in the lookup table */ 235 if (find_vnid_in_vcache(vol, loc) == B_OK) { 236 /* if it does, create a random one since we can't reuse 237 * existing vnid's */ 238 *vnid = generate_unique_vnid(vol); 239 /* and add it to the vcache */ 240 if ((result = add_to_vcache(vol, *vnid, loc)) < 0) 241 return result; 242 added_to_vcache = true; 243 } else { 244 /* otherwise we are free to use it */ 245 *vnid = loc; 246 } 247 } else if (result != B_OK) { 248 dprintf("get_next_dirent: unknown error (%s)\n", strerror(result)); 249 return result; 250 } 251 252 if (info.mode & FAT_SUBDIR) { 253 if (dlist_find(vol, info.cluster) == -1LL) { 254 if ((result = dlist_add(vol, *vnid)) < 0) { 255 if (added_to_vcache) 256 remove_from_vcache(vol, *vnid); 257 return result; 258 } 259 } 260 } 261 } 262 } 263 264 DPRINTF(2, ("get_next_dirent: found %s (vnid %Lx)\n", filename, *vnid)); 265 266 return B_NO_ERROR; 267} 268 269status_t check_dir_empty(nspace *vol, vnode *dir) 270{ 271 uint32 i; 272 struct diri iter; 273 status_t result = B_ERROR; /* quiet warning */ 274 275 if (check_nspace_magic(vol, "check_dir_empty")) return EINVAL; 276 if (check_vnode_magic(dir, "check_dir_empty")) return EINVAL; 277 278 if (diri_init(vol, dir->cluster, 0, &iter) == NULL) { 279 dprintf("check_dir_empty: error opening directory\n"); 280 ASSERT(0); 281 return B_ERROR; 282 } 283 284 i = (dir->vnid == vol->root_vnode.vnid) ? 2 : 0; 285 286 for (;i<3;i++) { 287 char filename[512]; 288 result = _next_dirent_(&iter, NULL, filename, 512); 289 290 if (result < 0) { 291 if ((i == 2) && (result == ENOENT)) result = B_OK; 292 break; 293 } 294 295 if (((i == 0) && strcmp(filename, ".")) || 296 ((i == 1) && strcmp(filename, "..")) || 297 // weird case where ./.. are stored as long file names 298 ((i < 2) && (iter.current_index != i+1))) { 299 dprintf("check_dir_empty: malformed directory\n"); 300 result = ENOTDIR; 301 break; 302 } 303 304 result = ENOTEMPTY; 305 } 306 307 diri_free(&iter); 308 309 return result; 310} 311 312status_t findfile_case(nspace *vol, vnode *dir, const char *file, 313 vnode_id *vnid, vnode **node) 314{ 315 return findfile(vol, dir, file, vnid, node, true, false, NULL); 316} 317 318status_t findfile_nocase(nspace *vol, vnode *dir, const char *file, 319 vnode_id *vnid, vnode **node) 320{ 321 return findfile(vol, dir, file, vnid, node, false, false, NULL); 322} 323 324status_t findfile_nocase_duplicates(nspace *vol, vnode *dir, const char *file, 325 vnode_id *vnid, vnode **node, bool *dups_exist) 326{ 327 return findfile(vol, dir, file, vnid, node, false, true, dups_exist); 328} 329 330status_t findfile_case_duplicates(nspace *vol, vnode *dir, const char *file, 331 vnode_id *vnid, vnode **node, bool *dups_exist) 332{ 333 return findfile(vol, dir, file, vnid, node, true, true, dups_exist); 334} 335 336static status_t findfile(nspace *vol, vnode *dir, const char *file, 337 vnode_id *vnid, vnode **node, bool check_case, 338 bool check_dups, bool *dups_exist) 339{ 340 /* Starting at the base, find the file in the subdir 341 and return its vnode id */ 342 /* The check_case flags determines whether or not the search 343 is done for the exact case or not. If it is not, it will 344 return the first occurance of the match. */ 345 /* The check_dups flag instructs the function to find the 346 first filename match based on the case sensitivity in 347 check_case, but continue searching to see if there are 348 any other case-insensitive matches. If there are, the 349 dups_exist flag is set to true. */ 350 int result = 0; 351 vnode_id found_vnid = 0; 352 bool found_file = false; 353 354// dprintf("findfile: %s in %Lx, case %d dups %d\n", file, dir->vnid, check_case, check_dups); 355 356 if (check_nspace_magic(vol, "findfile")) return EINVAL; 357 if (check_vnode_magic(dir, "findfile")) return EINVAL; 358 359 DPRINTF(1, ("findfile: %s in %Lx\n", file, dir->vnid)); 360 361 if(dups_exist != NULL) *dups_exist = false; 362 else check_dups = false; 363 364 if ((strcmp(file,".") == 0) && (dir->vnid == vol->root_vnode.vnid)) { 365 found_file = true; 366 found_vnid = dir->vnid; 367 } else if ((strcmp(file, "..") == 0) && (dir->vnid == vol->root_vnode.vnid)) { 368 found_file = true; 369 found_vnid = dir->dir_vnid; 370 } else { 371 struct diri diri; 372 373 // XXX: do it in a smarter way 374 diri_init(vol, dir->cluster, 0, &diri); 375 376 while (1) 377 { 378 char filename[512]; 379 vnode_id _vnid; 380 381 result = get_next_dirent(vol, dir, &diri, &_vnid, filename, 512); 382 383 if (result != B_NO_ERROR) 384 break; 385 386 if(check_case) { 387 if (!found_file && !strcmp(filename, file)) { 388 found_file = true; 389 found_vnid = _vnid; 390 } else if(check_dups && !strcasecmp(filename, file)) { 391 *dups_exist = true; 392 } 393 } else { 394 if (!strcasecmp(filename, file)) { 395 if(check_dups && found_file) { 396 *dups_exist = true; 397 } 398 found_file = true; 399 found_vnid = _vnid; 400 } 401 } 402 403 if(found_file && (!check_dups || (check_dups && *dups_exist))) { 404 break; 405 } 406 } 407 diri_free(&diri); 408 } 409 if (found_file) { 410 if (vnid) *vnid = found_vnid; 411 if (node) result = get_vnode(vol->id, found_vnid, (void **)node); 412 result = B_OK; 413 } else { 414 result = ENOENT; 415 } 416#if 0 417 dprintf("findfile: returning %d", result); 418 if(dups_exist) 419 dprintf(" dups_exist %d\n", *dups_exist); 420 else 421 dprintf("\n"); 422#endif 423 return result; 424} 425 426status_t erase_dir_entry(nspace *vol, vnode *node) 427{ 428 status_t result; 429 uint32 i; 430 char filename[512]; 431 uint8 *buffer; 432 struct _dirent_info_ info; 433 struct diri diri; 434 435 DPRINTF(0, ("erasing directory entries %lx through %lx\n", node->sindex, node->eindex)); 436 buffer = diri_init(vol,VNODE_PARENT_DIR_CLUSTER(node), node->sindex, &diri); 437 438 // first pass: check if the entry is still valid 439 if (buffer == NULL) { 440 dprintf("erase_dir_entry: error reading directory\n"); 441 return ENOENT; 442 } 443 444 result = _next_dirent_(&diri, &info, filename, 512); 445 diri_free(&diri); 446 447 if (result < 0) return result; 448 449 if ((info.sindex != node->sindex) || 450 (info.eindex != node->eindex)) { 451 // any other attributes may be in a state of flux due to wstat calls 452 dprintf("erase_dir_entry: directory entry doesn't match\n"); 453 return B_ERROR; 454 } 455 456 // second pass: actually erase the entry 457 buffer = diri_init(vol, VNODE_PARENT_DIR_CLUSTER(node), node->sindex, &diri); 458 for (i=node->sindex;(i<=node->eindex)&&(buffer);buffer=diri_next_entry(&diri),i++) { 459 buffer[0] = 0xe5; // mark entry erased 460 diri_mark_dirty(&diri); 461 } 462 diri_free(&diri); 463 464 return 0; 465} 466 467// shrink directory to the size needed 468// errors here are neither likely nor problematic 469// w95 doesn't seem to do this, so it's possible to create a 470// really large directory that consumes all available space! 471status_t compact_directory(nspace *vol, vnode *dir) 472{ 473 uint32 last = 0; 474 struct diri diri; 475 status_t error = B_ERROR; /* quiet warning */ 476 477 DPRINTF(0, ("compacting directory with vnode id %Lx\n", dir->vnid)); 478 479 // root directory can't shrink in fat12 and fat16 480 if (IS_FIXED_ROOT(dir->cluster)) 481 return 0; 482 483 diri_init(vol, dir->cluster, 0, &diri); 484 while (diri.current_block) { 485 char filename[512]; 486 struct _dirent_info_ info; 487 488 error = _next_dirent_(&diri, &info, filename, 512); 489 490 if (error == B_OK) { 491 // don't compact away volume labels in the root dir 492 if (!(info.mode & FAT_VOLUME) || (dir->vnid != vol->root_vnode.vnid)) 493 last = diri.current_index; 494 } else if (error == ENOENT) { 495 uint32 clusters = (last + vol->bytes_per_sector / 0x20 * vol->sectors_per_cluster - 1) / (vol->bytes_per_sector / 0x20) / vol->sectors_per_cluster; 496 error = 0; 497 498 // special case for fat32 root directory; we don't want 499 // it to disappear 500 if (clusters == 0) clusters = 1; 501 502 if (clusters * vol->bytes_per_sector * vol->sectors_per_cluster < dir->st_size) { 503 DPRINTF(0, ("shrinking directory to %lx clusters\n", clusters)); 504 error = set_fat_chain_length(vol, dir, clusters); 505 dir->st_size = clusters*vol->bytes_per_sector*vol->sectors_per_cluster; 506 dir->iteration++; 507 } 508 break; 509 } else { 510 dprintf("compact_directory: unknown error from _next_dirent_ (%s)\n", strerror(error)); 511 break; 512 } 513 } 514 diri_free(&diri); 515 516 return error; 517} 518 519// name is array of char[11] as returned by findfile 520static status_t find_short_name(nspace *vol, vnode *dir, const uchar *name) 521{ 522 struct diri diri; 523 uint8 *buffer; 524 status_t result = ENOENT; 525 526 buffer = diri_init(vol, dir->cluster, 0, &diri); 527 while (buffer) { 528 if (buffer[0] == 0) 529 break; 530 531 if (buffer[0xb] != 0xf) { // not long file name 532 if (!memcmp(name, buffer, 11)) { 533 result = B_OK; 534 break; 535 } 536 } 537 538 buffer = diri_next_entry(&diri); 539 } 540 541 diri_free(&diri); 542 543 return result; 544} 545 546struct _entry_info_ { 547 uint32 mode; 548 uint32 cluster; 549 uint32 size; 550 time_t time; 551}; 552 553static status_t _create_dir_entry_(nspace *vol, vnode *dir, struct _entry_info_ *info, 554 const char nshort[11], const char *nlong, uint32 len, uint32 *ns, uint32 *ne) 555{ 556 status_t error = B_ERROR; /* quiet warning */ 557 uint32 required_entries, i; 558 uint8 *buffer, hash; 559 bool last_entry; 560 struct diri diri; 561 562 // short name cannot be the same as that of a device 563 // this list was created by running strings on io.sys 564 const char *device_names[] = { 565 "CON ", 566 "AUX ", 567 "PRN ", 568 "CLOCK$ ", 569 "COM1 ", 570 "LPT1 ", 571 "LPT2 ", 572 "LPT3 ", 573 "COM2 ", 574 "COM3 ", 575 "COM4 ", 576 "CONFIG$ ", 577 NULL 578 }; 579 580 // check short name against device names 581 for (i=0;device_names[i];i++) { 582 // only first 8 characters seem to matter 583 if (!memcmp(nshort, device_names[i], 8)) 584 return EPERM; 585 } 586 587 if ((info->cluster != 0) && !IS_DATA_CLUSTER(info->cluster)) { 588 dprintf("_create_dir_entry_ for bad cluster (%lx)\n", info->cluster); 589 return EINVAL; 590 } 591 592 /* convert byte length of unicode name to directory entries */ 593 required_entries = (len + 24) / 26 + 1; 594 595 // find a place to put the entries 596 *ns = 0; 597 last_entry = true; 598 diri_init(vol, dir->cluster, 0, &diri); 599 while (diri.current_block) { 600 char filename[512]; 601 struct _dirent_info_ info; 602 error = _next_dirent_(&diri, &info, filename, 512); 603 if (error == B_OK) { 604 if (info.sindex - *ns >= required_entries) { 605 last_entry = false; 606 break; 607 } 608 *ns = diri.current_index; 609 } else if (error == ENOENT) { 610 // hit end of directory marker 611 break; 612 } else { 613 dprintf("_create_dir_entry_: unknown error from _next_dirent_ (%s)\n", strerror(error)); 614 break; 615 } 616 } 617 618 // if at end of directory, last_entry flag will be true as it should be 619 620 diri_free(&diri); 621 622 if ((error != B_OK) && (error != ENOENT)) return error; 623 624 *ne = *ns + required_entries - 1; 625 626 for (i=*ns;i<=*ne;i++) { 627 ASSERT(find_loc_in_vcache(vol, \ 628 GENERATE_DIR_INDEX_VNID(dir->cluster, i)) == ENOENT); 629 } 630 631 DPRINTF(0, ("directory entry runs from %lx to %lx (dirsize = %Lx) (is%s last entry)\n", *ns, *ne, dir->st_size, last_entry ? "" : "n't")); 632 633 // check if the directory needs to be expanded 634 if (*ne * 0x20 >= dir->st_size) { 635 uint32 clusters_needed; 636 637 // can't expand fat12 and fat16 root directories :( 638 if (IS_FIXED_ROOT(dir->cluster)) { 639 DPRINTF(0, ("_create_dir_entry_: out of space in root directory\n")); 640 return ENOSPC; 641 } 642 643 // otherwise grow directory to fit 644 clusters_needed = ((*ne + 1) * 0x20 + 645 vol->bytes_per_sector*vol->sectors_per_cluster - 1) / 646 vol->bytes_per_sector / vol->sectors_per_cluster; 647 648 DPRINTF(0, ("expanding directory from %Lx to %lx clusters\n", dir->st_size/vol->bytes_per_sector/vol->sectors_per_cluster, clusters_needed)); 649 if ((error = set_fat_chain_length(vol, dir, clusters_needed)) < 0) 650 return error; 651 dir->st_size = vol->bytes_per_sector*vol->sectors_per_cluster*clusters_needed; 652 dir->iteration++; 653 } 654 655 // starting blitting entries 656 buffer = diri_init(vol,dir->cluster, *ns, &diri); 657 hash = hash_msdos_name(nshort); 658 659 // write lfn entries 660 for (i=1;(i<required_entries)&&buffer;i++) { 661 const char *p = nlong + (required_entries - i - 1)*26; // go to unicode offset 662 memset(buffer, 0, 0x20); 663 buffer[0] = required_entries - i + ((i == 1) ? 0x40 : 0); 664 buffer[0x0b] = 0x0f; 665 buffer[0x0d] = hash; 666 memcpy(buffer+1,p,10); 667 memcpy(buffer+0x0e,p+10,12); 668 memcpy(buffer+0x1c,p+22,4); 669 diri_mark_dirty(&diri); 670 buffer = diri_next_entry(&diri); 671 } 672 673 ASSERT(buffer != NULL); 674 if (buffer == NULL) { // this should never happen... 675 DPRINTF(0, ("_create_dir_entry_: the unthinkable has occured\n")); 676 diri_free(&diri); 677 return B_ERROR; 678 } 679 680 // write directory entry 681 memcpy(buffer, nshort, 11); 682 buffer[0x0b] = info->mode; 683 memset(buffer+0xc, 0, 0x16-0xc); 684 i = time_t2dos(info->time); 685 buffer[0x16] = i & 0xff; 686 buffer[0x17] = (i >> 8) & 0xff; 687 buffer[0x18] = (i >> 16) & 0xff; 688 buffer[0x19] = (i >> 24) & 0xff; 689 i = info->cluster; 690 if (info->size == 0) i = 0; // cluster = 0 for 0 byte files 691 buffer[0x1a] = i & 0xff; 692 buffer[0x1b] = (i >> 8) & 0xff; 693 if (vol->fat_bits == 32) { 694 buffer[0x14] = (i >> 16) & 0xff; 695 buffer[0x15] = (i >> 24) & 0xff; 696 } 697 i = (info->mode & FAT_SUBDIR) ? 0 : info->size; 698 buffer[0x1c] = i & 0xff; 699 buffer[0x1d] = (i >> 8) & 0xff; 700 buffer[0x1e] = (i >> 16) & 0xff; 701 buffer[0x1f] = (i >> 24) & 0xff; 702 diri_mark_dirty(&diri); 703 704 if (last_entry) { 705 // add end of directory markers to the rest of the 706 // cluster; need to clear all the other entries or else 707 // scandisk will complain. 708 while ((buffer = diri_next_entry(&diri)) != NULL) { 709 memset(buffer, 0, 0x20); 710 diri_mark_dirty(&diri); 711 } 712 } 713 714 diri_free(&diri); 715 716 return 0; 717} 718 719// doesn't do any name checking 720status_t create_volume_label(nspace *vol, const char name[11], uint32 *index) 721{ 722 status_t err; 723 uint32 dummy; 724 struct _entry_info_ info = { 725 FAT_ARCHIVE | FAT_VOLUME, 0, 0, 0 726 }; 727 time(&info.time); 728 729 // check if name already exists 730 err = find_short_name(vol, &(vol->root_vnode), (uchar *)name); 731 if (err == B_OK) 732 return EEXIST; 733 if (err != ENOENT) 734 return err; 735 736 return _create_dir_entry_(vol, &(vol->root_vnode), &info, name, NULL, 0, index, &dummy); 737} 738 739bool is_filename_legal(const char *name) 740{ 741 unsigned int i; 742 unsigned int len = strlen(name); 743 744 if (len <= 0) return false; 745 746 // names ending with a dot are not allowed 747 if (name[len - 1] == '.') return false; 748 // names ending with a space are not allowed 749 if (name[len - 1] == ' ') return false; 750 751 // XXX illegal character search can be made faster 752 for(i=0; i<len; i++) { 753 if(name[i] & 0x80) continue; //belongs to an utf8 char 754 if(strchr(illegal, name[i])) return false; 755 if((unsigned char)name[i] < 32) return false; 756 } 757 return true; 758} 759 760status_t create_dir_entry(nspace *vol, vnode *dir, vnode *node, 761 const char *name, uint32 *ns, uint32 *ne) 762{ 763 status_t error; 764 int32 len; 765 unsigned char nlong[512], nshort[11]; 766 int encoding; 767 struct _entry_info_ info; 768 769 // check name legality before doing anything 770 if(!is_filename_legal(name)) 771 return EINVAL; 772 773 // check if name already exists 774 error = findfile_nocase(vol,dir,name,NULL,NULL); 775 if (error == B_OK) { 776 DPRINTF(0, ("%s already found in directory %Lx\n", name, dir->vnid)); 777 return EEXIST; 778 } 779 if (error != ENOENT) 780 return error; 781 782 // check name legality while converting. we ignore the case conversion 783 // flag, i.e. (filename "blah" will always have a patched short name), 784 // because the whole case conversion system in dos is brain damaged; 785 // remanants of CP/M no less. 786 787 // existing names pose a problem; in these cases, we'll just live with 788 // two identical short names. not a great solution, but there's little 789 // we can do about it. 790 len = utf8_to_unicode(name, nlong, 512); 791 if (len <= 0) { 792 DPRINTF(0, ("Error converting utf8 name '%s' to unicode\n", name)); 793 return len ? len : B_ERROR; 794 } 795 memset(nlong + len, 0xff, 512 - len); /* pad with 0xff */ 796 797 error = generate_short_name((uchar *)name, nlong, len, nshort, &encoding); 798 if (error) { 799 DPRINTF(0, ("Error generating short name for '%s'\n", name)); 800 return error; 801 } 802 803 // if there is a long name, patch short name if necessary and check for duplication 804 if (requires_long_name(name, nlong)) { 805 char tshort[11]; // temporary short name 806 int iter = 1; 807 808 memcpy(tshort, nshort, 11); 809 810 if (requires_munged_short_name((uchar *)name, nshort, encoding)) 811 error = B_OK; 812 else 813 error = find_short_name(vol, dir, nshort); 814 815 if (error == B_OK) { 816 do { 817 memcpy(nshort, tshort, 11); 818 DPRINTF(0, ("trying short name %11.11s\n", nshort)); 819 munge_short_name1(nshort, iter, encoding); 820 } while (((error = find_short_name(vol, dir, nshort)) == B_OK) && (++iter < 10)); 821 } 822 823 if ((error != B_OK) && (error != ENOENT)) return error; 824 825 if (error == B_OK) { 826 // XXX: possible infinite loop here 827 do { 828 memcpy(nshort, tshort, 11); 829 DPRINTF(0, ("trying short name %11.11s\n", nshort)); 830 munge_short_name2(nshort, encoding); 831 } while ((error = find_short_name(vol, dir, nshort)) == B_OK); 832 833 if (error != ENOENT) return error; 834 } 835 } else { 836 len = 0; /* entry doesn't need a long name */ 837 } 838 839 DPRINTF(0, ("creating directory entry (%11.11s)\n", nshort)); 840 841 info.mode = node->mode; 842 info.cluster = node->cluster; 843 info.size = node->st_size; 844 info.time = node->st_time; 845 846 return _create_dir_entry_(vol, dir, &info, (char *)nshort, (char *)nlong, len, ns, ne); 847} 848 849int dosfs_read_vnode(void *_vol, vnode_id vnid, char reenter, void **_node) 850{ 851 nspace *vol = (nspace*)_vol; 852 int result = B_NO_ERROR; 853 vnode_id loc, dir_vnid; 854 vnode *entry; 855 struct _dirent_info_ info; 856 struct diri iter; 857 char filename[512]; /* need this for setting mime type */ 858 859 if (!reenter) { LOCK_VOL(vol); } 860 861 *_node = NULL; 862 863 if (check_nspace_magic(vol, "dosfs_read_vnode")) { 864 if (!reenter) UNLOCK_VOL(vol); 865 return EINVAL; 866 } 867 868 DPRINTF(0, ("dosfs_read_vnode (vnode id %Lx)\n", vnid)); 869 870 if (vnid == vol->root_vnode.vnid) 871 { 872 dprintf("??? dosfs_read_vnode called on root node ???\n"); 873 *_node = (void *)&(vol->root_vnode); 874 goto bi; 875 } 876 877 if (vcache_vnid_to_loc(vol, vnid, &loc) != B_OK) 878 loc = vnid; 879 880 if (IS_ARTIFICIAL_VNID(loc) || IS_INVALID_VNID(loc)) { 881 DPRINTF(0, ("dosfs_read_vnode: unknown vnid %Lx (loc %Lx)\n", vnid, loc)); 882 result = ENOENT; 883 goto bi; 884 } 885 886 if ((dir_vnid = dlist_find(vol, DIR_OF_VNID(loc))) == -1LL) { 887 DPRINTF(0, ("dosfs_read_vnode: unknown directory at cluster %lx\n", DIR_OF_VNID(loc))); 888 result = ENOENT; 889 goto bi; 890 } 891 892 if (diri_init(vol, DIR_OF_VNID(loc), 893 IS_DIR_CLUSTER_VNID(loc) ? 0 : INDEX_OF_DIR_INDEX_VNID(loc), 894 &iter) == NULL) { 895 dprintf("dosfs_read_vnode: error initializing directory for vnid %Lx (loc %Lx)\n", vnid, loc); 896 result = ENOENT; 897 goto bi; 898 } 899 900 while (1) { 901 result = _next_dirent_(&iter, &info, filename, 512); 902 if (result < 0) { 903 dprintf("dosfs_read_vnode: error finding vnid %Lx (loc %Lx) (%s)\n", vnid, loc, strerror(result)); 904 goto bi2; 905 } 906 907 if (IS_DIR_CLUSTER_VNID(loc)) { 908 if (info.cluster == CLUSTER_OF_DIR_CLUSTER_VNID(loc)) 909 break; 910 } else { 911 if (info.sindex == INDEX_OF_DIR_INDEX_VNID(loc)) 912 break; 913 dprintf("dosfs_read_vnode: error finding vnid %Lx (loc %Lx) (%s)\n", vnid, loc, strerror(result)); 914 result = ENOENT; 915 goto bi2; 916 } 917 } 918 919 if ((entry = calloc(sizeof(struct vnode), 1)) == NULL) { 920 DPRINTF(0, ("dosfs_read_vnode: out of memory\n")); 921 result = ENOMEM; 922 goto bi2; 923 } 924 925 entry->magic = VNODE_MAGIC; 926 entry->vnid = vnid; 927 entry->dir_vnid = dir_vnid; 928 entry->disk_image = 0; 929 if (vol->respect_disk_image) { 930 if ((dir_vnid == vol->root_vnode.vnid) && !strcmp(filename, "BEOS")) { 931 vol->beos_vnid = vnid; 932 entry->disk_image = 1; 933 } 934 if ((dir_vnid == vol->beos_vnid) && !strcmp(filename, "IMAGE.BE")) { 935 entry->disk_image = 2; 936 } 937 } 938 entry->iteration = 0; 939 entry->sindex = info.sindex; 940 entry->eindex = info.eindex; 941 entry->cluster = info.cluster; 942 entry->mode = info.mode; 943 entry->st_size = info.size; 944 entry->dirty = false; 945 if (info.mode & FAT_SUBDIR) { 946 entry->st_size = count_clusters(vol,entry->cluster) * vol->sectors_per_cluster * vol->bytes_per_sector; 947 } 948 if (entry->cluster) 949 entry->end_cluster = get_nth_fat_entry(vol, info.cluster, 950 (entry->st_size + vol->bytes_per_sector * vol->sectors_per_cluster - 1) / 951 vol->bytes_per_sector / vol->sectors_per_cluster - 1); 952 else 953 entry->end_cluster = 0; 954 entry->st_time = dos2time_t(info.time); 955#if TRACK_FILENAME 956 entry->filename = malloc(sizeof(filename) + 1); 957 if (entry->filename) strcpy(entry->filename, filename); 958#endif 959 if(!(entry->mode & FAT_SUBDIR)) 960 set_mime_type(entry, filename); 961 962 *_node = entry; 963 964bi2:diri_free(&iter); 965bi: if (!reenter) UNLOCK_VOL(vol); 966 967 if (result != B_OK) DPRINTF(0, ("dosfs_read_vnode (%s)\n", strerror(result))); 968 969 return result; 970} 971 972int dosfs_walk(void *_vol, void *_dir, const char *file, char **newpath, vnode_id *vnid) 973{ 974 /* Starting at the base, find file in the subdir, and return path 975 string and vnode id of file. */ 976 nspace *vol = (nspace*)_vol; 977 vnode *dir = (vnode*)_dir; 978 vnode *vnode = NULL; 979 int result = ENOENT; 980 981 /* we can ignore newpath since there are no symbolic links */ 982 TOUCH(newpath); 983 984 LOCK_VOL(vol); 985 986 if (check_nspace_magic(vol, "dosfs_walk") || 987 check_vnode_magic(dir, "dosfs_walk")) { 988 UNLOCK_VOL(vol); 989 return EINVAL; 990 } 991 992 DPRINTF(0, ("dosfs_walk: find %Lx/%s\n", dir->vnid, file)); 993 994 result = findfile_case(vol, dir, file, vnid, &vnode); 995 if (result != B_OK) { 996 DPRINTF(0, ("dosfs_walk (%s)\n", strerror(result))); 997 } else { 998 DPRINTF(0, ("dosfs_walk: found vnid %Lx\n", *vnid)); 999 } 1000 1001 UNLOCK_VOL(vol); 1002 1003 return result; 1004} 1005 1006int dosfs_access(void *_vol, void *_node, int mode) 1007{ 1008 status_t result = B_OK; 1009 nspace *vol = (nspace *)_vol; 1010 vnode *node = (vnode *)_node; 1011 1012 LOCK_VOL(vol); 1013 1014 if (check_nspace_magic(vol, "dosfs_access") || 1015 check_vnode_magic(node, "dosfs_access")) { 1016 UNLOCK_VOL(vol); 1017 return EINVAL; 1018 } 1019 1020 DPRINTF(0, ("dosfs_access (vnode id %Lx, mode %x)\n", node->vnid, mode)); 1021 1022 if ((mode & O_RWMASK) != O_RDONLY) { 1023 if (vol->flags & B_FS_IS_READONLY) { 1024 dprintf("dosfs_access: can't write on read-only volume\n"); 1025 result = EROFS; 1026 } else if (node->mode & FAT_READ_ONLY) { 1027 dprintf("can't open read-only file for writing\n"); 1028 result = EPERM; 1029 } else if (node->disk_image != 0) { 1030 dprintf("can't open disk image file for writing\n"); 1031 result = EPERM; 1032 } 1033 } 1034 1035 UNLOCK_VOL(vol); 1036 1037 return result; 1038} 1039 1040int dosfs_readlink(void *_vol, void *_node, char *buf, size_t *bufsize) 1041{ 1042 TOUCH(_vol); TOUCH(_node); TOUCH(buf); TOUCH(bufsize); 1043 1044 // no links in fat... 1045 DPRINTF(0, ("dosfs_readlink called\n")); 1046 1047 return EINVAL; 1048} 1049 1050int dosfs_opendir(void *_vol, void *_node, void **_cookie) 1051{ 1052 nspace *vol = (nspace*)_vol; 1053 vnode *node = (vnode*)_node; 1054 dircookie *cookie = NULL; 1055 int result; 1056 1057 if (_cookie == NULL) { 1058 dprintf("dosfs_opendir called with null _cookie\n"); 1059 return EINVAL; 1060 } 1061 1062 LOCK_VOL(vol); 1063 1064 if (check_nspace_magic(vol, "dosfs_opendir") || 1065 check_vnode_magic(node, "dosfs_opendir")) { 1066 UNLOCK_VOL(vol); 1067 return EINVAL; 1068 } 1069 1070 DPRINTF(0, ("dosfs_opendir (vnode id %Lx)\n", node->vnid)); 1071 1072 *_cookie = NULL; 1073 1074 if (!(node->mode & FAT_SUBDIR)) { 1075 /* bash will try to opendir files unless OPENDIR_NOT_ROBUST is 1076 * defined, so we'll suppress this message; it's more of a problem 1077 * with the application than with the file system, anyway 1078 */ 1079 DPRINTF(0, ("dosfs_opendir error: vnode not a directory\n")); 1080 result = ENOTDIR; 1081 goto bi; 1082 } 1083 1084 if ((cookie = (dircookie *)malloc(sizeof(dircookie))) == NULL) { 1085 dprintf("dosfs_opendir: out of memory error\n"); 1086 result = ENOMEM; 1087 goto bi; 1088 } 1089 1090 cookie->magic = DIRCOOKIE_MAGIC; 1091 cookie->current_index = 0; 1092 1093 result = B_NO_ERROR; 1094 1095bi: 1096 *_cookie = (void*)cookie; 1097 1098 if (result != B_OK) DPRINTF(0, ("dosfs_opendir (%s)\n", strerror(result))); 1099 1100 UNLOCK_VOL(vol); 1101 1102 return result; 1103} 1104 1105int dosfs_readdir(void *_vol, void *_dir, void *_cookie, long *num, 1106 struct dirent *entry, size_t bufsize) 1107{ 1108 int result = ENOENT; 1109 nspace* vol = (nspace*)_vol; 1110 vnode *dir = (vnode *)_dir; 1111 dircookie* cookie = (dircookie*)_cookie; 1112 struct diri diri; 1113 1114 LOCK_VOL(vol); 1115 1116 if (check_nspace_magic(vol, "dosfs_readdir") || 1117 check_vnode_magic(dir, "dosfs_readdir") || 1118 check_dircookie_magic(cookie, "dosfs_readdir")) { 1119 UNLOCK_VOL(vol); 1120 return EINVAL; 1121 } 1122 1123 DPRINTF(0, ("dosfs_readdir: vnode id %Lx, index %lx\n", dir->vnid, cookie->current_index)); 1124 1125 // simulate '.' and '..' entries for root directory 1126 if (dir->vnid == vol->root_vnode.vnid) { 1127 if (cookie->current_index >= 2) { 1128 cookie->current_index -= 2; 1129 } else { 1130 if (cookie->current_index++ == 0) { 1131 strcpy(entry->d_name, "."); 1132 entry->d_reclen = 2; 1133 } else { 1134 strcpy(entry->d_name, ".."); 1135 entry->d_reclen = 3; 1136 } 1137 *num = 1; 1138 entry->d_ino = vol->root_vnode.vnid; 1139 entry->d_dev = vol->id; 1140 result = B_NO_ERROR; 1141 goto bi; 1142 } 1143 } 1144 1145 diri_init(vol, dir->cluster, cookie->current_index, &diri); 1146 result = get_next_dirent(vol, dir, &diri, &(entry->d_ino), entry->d_name, bufsize - sizeof(struct dirent) - 1); 1147 cookie->current_index = diri.current_index; 1148 diri_free(&diri); 1149 1150 if (dir->vnid == vol->root_vnode.vnid) 1151 cookie->current_index += 2; 1152 1153 if (result == B_NO_ERROR) { 1154 *num = 1; 1155 entry->d_dev = vol->id; 1156 entry->d_reclen = strlen(entry->d_name) + 1; 1157 DPRINTF(0, ("dosfs_readdir: found file %s\n", entry->d_name)); 1158 } else if (result == ENOENT) { 1159 // When you get to the end, don't return an error, just return 0 1160 // in *num. 1161 *num = 0; 1162 result = B_NO_ERROR; 1163 } else { 1164 dprintf("dosfs_readdir: error returned by get_next_dirent (%s)\n", strerror(result)); 1165 } 1166bi: 1167 if (result != B_OK) DPRINTF(0, ("dosfs_readdir (%s)\n", strerror(result))); 1168 1169 UNLOCK_VOL(vol); 1170 1171 return result; 1172} 1173 1174int dosfs_rewinddir(void *_vol, void *_node, void* _cookie) 1175{ 1176 nspace *vol = _vol; 1177 vnode *node = _node; 1178 dircookie *cookie = (dircookie*)_cookie; 1179 1180 LOCK_VOL(vol); 1181 1182 if (check_nspace_magic(vol, "dosfs_rewinddir") || 1183 check_vnode_magic(node, "dosfs_rewinddir") || 1184 check_dircookie_magic(cookie, "dosfs_rewinddir")) { 1185 UNLOCK_VOL(vol); 1186 return EINVAL; 1187 } 1188 1189 DPRINTF(0, ("dosfs_rewinddir (vnode id %Lx)\n", node->vnid)); 1190 1191 cookie->current_index = 0; 1192 1193 UNLOCK_VOL(vol); 1194 1195 return B_OK; 1196} 1197 1198int dosfs_closedir(void *_vol, void *_node, void *_cookie) 1199{ 1200 TOUCH(_vol); TOUCH(_node); TOUCH(_cookie); 1201 1202 DPRINTF(0, ("dosfs_closedir called\n")); 1203 1204 return 0; 1205} 1206 1207int dosfs_free_dircookie(void *_vol, void *node, void *_cookie) 1208{ 1209 nspace *vol = _vol; 1210 dircookie *cookie = _cookie; 1211 1212 LOCK_VOL(vol); 1213 1214 if (check_nspace_magic(vol, "dosfs_free_dircookie") || 1215 check_vnode_magic((vnode *)node, "dosfs_free_dircookie") || 1216 check_dircookie_magic((dircookie *)cookie, "dosfs_free_dircookie")) { 1217 UNLOCK_VOL(vol); 1218 return EINVAL; 1219 } 1220 1221 if (cookie == NULL) { 1222 dprintf("error: dosfs_free_dircookie called with null cookie\n"); 1223 UNLOCK_VOL(vol); 1224 return EINVAL; 1225 } 1226 1227 DPRINTF(0, ("dosfs_free_dircookie (vnode id %Lx)\n", ((vnode*)node)->vnid)); 1228 1229 cookie->magic = ~DIRCOOKIE_MAGIC; 1230 free(cookie); 1231 1232 UNLOCK_VOL(vol); 1233 1234 return 0; 1235} 1236 1237