1 /*
2 * Copyright (c) 2015 Steve White
3 * Copyright (c) 2022 Travis Geiselbrecht
4 *
5 * Use of this source code is governed by a MIT-style
6 * license that can be found in the LICENSE file or at
7 * https://opensource.org/licenses/MIT
8 */
9 #include "dir.h"
10
11 #include <lk/cpp.h>
12 #include <lk/err.h>
13 #include <lk/trace.h>
14 #include <endian.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19
20 #include "fat_fs.h"
21 #include "fat_priv.h"
22 #include "file_iterator.h"
23
24 #define LOCAL_TRACE FAT_GLOBAL_TRACE(0)
25
26 fat_dir::~fat_dir() = default;
27
28 // structure that represents an open dir handle. holds the offset into the directory
29 // that is being walked.
30 struct fat_dir_cookie {
31 fat_dir *dir;
32
33 struct list_node node;
34
35 // next directory index offset in bytes, 0xffffffff for EOD
36 uint32_t index;
37 static const uint32_t index_eod = 0xffffffff;
38 };
39
40 namespace {
41
42 // walk one entry into the dir, starting at byte offset into the directory block iterator.
43 // both dbi and offset will be modified during the call.
44 // filles out the entry and returns a pointer into the passed in buffer in out_filename.
45 // NOTE: *must* pass at least a MAX_FILE_NAME_LEN byte char pointer in the filename_buffer slot.
fat_find_next_entry(fat_fs * fat,file_block_iterator & dbi,uint32_t & offset,dir_entry * entry,char filename_buffer[MAX_FILE_NAME_LEN],char ** out_filename)46 status_t fat_find_next_entry(fat_fs *fat, file_block_iterator &dbi, uint32_t &offset, dir_entry *entry,
47 char filename_buffer[MAX_FILE_NAME_LEN], char **out_filename) {
48
49 DEBUG_ASSERT(entry && filename_buffer && out_filename);
50 DEBUG_ASSERT(offset <= fat->info().bytes_per_sector); // passing offset == bytes_per_sector is okay
51
52 // lfn parsing state
53 struct lfn_parse_state {
54 size_t pos = 0;
55 uint8_t last_sequence = 0xff; // 0xff means we haven't seen anything
56 // since last reset
57 uint8_t checksum = 0;
58
59 void reset() {
60 last_sequence = 0xff;
61 }
62 } lfn_state;
63
64 for (;;) {
65 if (LOCAL_TRACE >= 2) {
66 LTRACEF("dir sector:\n");
67 hexdump8_ex(dbi.get_bcache_ptr(0), fat->info().bytes_per_sector, 0);
68 }
69
70 // walk within a sector
71 while (offset < fat->info().bytes_per_sector) {
72 LTRACEF_LEVEL(2, "looking at offset %u\n", offset);
73 const uint8_t *ent = dbi.get_bcache_ptr(offset);
74 if (ent[0] == 0) { // no more entries
75 // we're completely done
76 LTRACEF("completely done\n");
77 return ERR_NOT_FOUND;
78 } else if (ent[0] == 0xE5) { // deleted entry
79 LTRACEF("deleted entry\n");
80 lfn_state.reset();
81 offset += DIR_ENTRY_LENGTH;
82 continue;
83 } else if (ent[0x0B] == (uint8_t)fat_attribute::volume_id) {
84 // skip volume ids
85 LTRACEF("skipping volume id\n");
86 lfn_state.reset();
87 offset += DIR_ENTRY_LENGTH;
88 continue;
89 } else if (ent[0x0B] == (uint8_t)fat_attribute::lfn) {
90 // part of a LFN sequence
91 uint8_t sequence = ent[0] & ~0x40;
92 if (ent[0] & 0x40) {
93 // end sequence, start a new backwards fill of the lfn name
94 lfn_state.pos = MAX_FILE_NAME_LEN;
95 filename_buffer[--lfn_state.pos] = 0;
96 lfn_state.last_sequence = sequence;
97 lfn_state.checksum = ent[0x0d];
98 LTRACEF_LEVEL(2, "start of new LFN entry, sequence %u\n", sequence);
99 } else {
100 if (lfn_state.last_sequence != sequence + 1) {
101 // our entry is out of sequence? drop it and start over
102 LTRACEF("ent out of sequence %u (last sequence %u)\n", sequence, lfn_state.last_sequence);
103 lfn_state.reset();
104 offset += DIR_ENTRY_LENGTH;
105 continue;
106 }
107 if (lfn_state.checksum != ent[0x0d]) {
108 // all of the long sequences need to match the checksum
109 LTRACEF("ent mismatches previous checksum\n");
110 lfn_state.reset();
111 offset += DIR_ENTRY_LENGTH;
112 continue;
113 }
114 lfn_state.last_sequence = sequence;
115 }
116
117 // walk backwards through the entry, picking out unicode characters
118 // table of unicode character offsets:
119 const size_t table[] = { 30, 28, 24, 22, 20, 18, 16, 14, 9, 7, 5, 3, 1 };
120 for (auto off : table) {
121 uint16_t c = fat_read16(ent, off);
122 if (c != 0xffff && c != 0x0) {
123 // TODO: properly deal with unicode -> utf8
124 filename_buffer[--lfn_state.pos] = c & 0xff;
125 }
126 }
127
128 LTRACEF_LEVEL(2, "lfn filename thus far: '%s' sequence %hhu\n", filename_buffer + lfn_state.pos, sequence);
129
130 // iterate one more entry, since we need to at least need to find the corresponding SFN
131 offset += DIR_ENTRY_LENGTH;
132 continue;
133 } else {
134 // regular entry, extract the short file name
135 char short_filename[8 + 1 + 3 + 1]; // max short name (8 . 3 NULL)
136 size_t fname_pos = 0;
137
138 // Ignore trailing spaces in filename and/or extension
139 int fn_len = 8, ext_len = 3;
140 for (int i = 7; i >= 0; i--) {
141 if (ent[i] == 0x20) {
142 fn_len--;
143 } else {
144 break;
145 }
146 }
147 for (size_t i = 10; i >= 8; i--) {
148 if (ent[i] == 0x20) {
149 ext_len--;
150 } else {
151 break;
152 }
153 }
154
155 for (int i = 0; i < fn_len; i++) {
156 short_filename[fname_pos++] = ent[i];
157 }
158 if (ext_len > 0) {
159 short_filename[fname_pos++] = '.';
160 for (int i=0; i < ext_len; i++) {
161 short_filename[fname_pos++] = ent[8 + i];
162 }
163 }
164 short_filename[fname_pos++] = 0;
165 DEBUG_ASSERT(fname_pos <= sizeof(short_filename));
166
167 // now we have the SFN, see if we just got finished parsing a corresponding LFN
168 // in the previous entries
169 if (lfn_state.last_sequence == 1) {
170 // TODO: compute checksum and make sure it matches
171 *out_filename = filename_buffer + lfn_state.pos;
172 } else {
173 // copy the parsed short file name into the out buffer
174 strlcpy(filename_buffer, short_filename, sizeof(short_filename));
175 *out_filename = filename_buffer;
176 }
177
178 lfn_state.reset();
179 offset += DIR_ENTRY_LENGTH;
180
181 // fall through, we've found a file entry
182 }
183
184 LTRACEF("found filename '%s'\n", *out_filename);
185
186 // fill out the passed in dir entry and exit
187 uint16_t target_cluster = fat_read16(ent, 0x1a);
188 entry->length = fat_read32(ent, 0x1c);
189 entry->attributes = (fat_attribute)ent[0x0B];
190 entry->start_cluster = target_cluster;
191 return NO_ERROR;
192 }
193
194 DEBUG_ASSERT(offset <= fat->info().bytes_per_sector);
195
196 // move to the next sector
197 status_t err = dbi.next_sector();
198 if (err < 0) {
199 break;
200 }
201 // starting over at offset 0 in the new sector
202 offset = 0;
203 }
204
205 // we're out of entries
206 return ERR_NOT_FOUND;
207 }
208
fat_find_file_in_dir(fat_fs * fat,uint32_t starting_cluster,const char * name,dir_entry * entry,uint32_t * found_offset)209 status_t fat_find_file_in_dir(fat_fs *fat, uint32_t starting_cluster, const char *name, dir_entry *entry, uint32_t *found_offset) {
210 LTRACEF("start_cluster %u, name '%s', out entry %p\n", starting_cluster, name, entry);
211
212 DEBUG_ASSERT(fat->lock.is_held());
213
214 // cache the length of the string we're matching against
215 const size_t namelen = strlen(name);
216
217 // kick start our directory sector iterator
218 file_block_iterator dbi(fat, starting_cluster);
219 status_t err = dbi.next_sectors(0);
220 if (err < 0) {
221 return err;
222 }
223
224 uint32_t offset = 0;
225 for (;;) {
226 char filename_buffer[MAX_FILE_NAME_LEN]; // max fat file name length
227 char *filename;
228
229 // step forward one entry and see if we got something
230 err = fat_find_next_entry(fat, dbi, offset, entry, filename_buffer, &filename);
231 if (err < 0) {
232 return err;
233 }
234
235 const size_t filenamelen = strlen(filename);
236
237 // see if we've matched an entry
238 if (filenamelen == namelen && !strnicmp(name, filename, filenamelen)) {
239 // we have, return with a good status
240 *found_offset = offset;
241 return NO_ERROR;
242 }
243 }
244 }
245
246 } // namespace
247
fat_walk(fat_fs * fat,const char * path,dir_entry * out_entry,dir_entry_location * loc)248 status_t fat_walk(fat_fs *fat, const char *path, dir_entry *out_entry, dir_entry_location *loc) {
249 LTRACEF("path %s\n", path);
250
251 DEBUG_ASSERT(fat->lock.is_held());
252
253 // routine to push the path element ahead one bump
254 // will leave path pointing at the next element, and path_element_size
255 // in characters for the next element (or 0 if finished).
256 size_t path_element_size = 0;
257 auto path_increment = [&path, &path_element_size]() {
258 path += path_element_size;
259 path_element_size = 0;
260
261 // we're at the end of the string
262 if (*path == 0) {
263 return;
264 }
265
266 // push path past the next /
267 while (*path == '/' && path != 0) {
268 path++;
269 }
270
271 // count the size of the next element
272 const char *ptr = path;
273 while (*ptr != '/' && *ptr != 0) {
274 ptr++;
275 path_element_size++;
276 }
277 };
278
279 // increment it once past leading / and establish an initial path_element_size
280 path_increment();
281 LTRACEF("first path '%s' path_element_size %zu\n", path, path_element_size);
282
283 // set up the starting cluster to search
284 uint32_t dir_start_cluster;
285 if (fat->info().root_cluster) {
286 dir_start_cluster = fat->info().root_cluster;
287 } else {
288 // fat 12/16 has a linear root dir, cluster 0 is a special case to fat_find_file_in_dir below
289 dir_start_cluster = 0;
290 }
291
292 // output entry
293 dir_entry entry {};
294
295 // walk the directory structure
296 for (;;) {
297 char name_element[MAX_FILE_NAME_LEN];
298 strlcpy(name_element, path, MIN(sizeof(name_element), path_element_size + 1));
299
300 LTRACEF("searching for element %s\n", name_element);
301
302 uint32_t found_offset = 0;
303 auto status = fat_find_file_in_dir(fat, dir_start_cluster, name_element, &entry, &found_offset);
304 if (status < 0) {
305 return ERR_NOT_FOUND;
306 }
307
308 // we found something
309 LTRACEF("found dir entry attributes %#hhx length %u start_cluster %u\n",
310 (uint8_t)entry.attributes, entry.length, entry.start_cluster);
311
312 // push the name element forward one notch so we can see if we're at the end (or iterate once again)
313 path_increment();
314 if (path_element_size > 0) {
315 // did we find a directory on an inner node of the path? we can continue iterating
316 if (entry.attributes == fat_attribute::directory) {
317 dir_start_cluster = entry.start_cluster;
318 } else {
319 LTRACEF("found a non dir at a non terminal path entry, exit\n");
320 return ERR_NOT_FOUND;
321 }
322 } else {
323 // we got a hit at the terminal entry of the path, pass it out to the caller as a success
324 *out_entry = entry;
325 loc->starting_dir_cluster = dir_start_cluster;
326 loc->dir_offset = found_offset;
327 return NO_ERROR;
328 }
329 }
330 }
331
opendir_priv(const dir_entry & entry,const dir_entry_location & loc,fat_dir_cookie ** out_cookie)332 status_t fat_dir::opendir_priv(const dir_entry &entry, const dir_entry_location &loc, fat_dir_cookie **out_cookie) {
333 // fill in our file info based on the entry
334 start_cluster_ = entry.start_cluster;
335 length_ = 0; // dirs all have 0 length entry
336 dir_loc_ = loc;
337 inc_ref();
338
339 // create a dir cookie
340 auto dir_cookie = new fat_dir_cookie;
341 dir_cookie->dir = this;
342 dir_cookie->index = 0;
343
344 // add it to the dir object
345 list_add_tail(&cookies_, &dir_cookie->node);
346
347 *out_cookie = dir_cookie;
348
349 return NO_ERROR;
350 }
351
opendir(fscookie * cookie,const char * name,dircookie ** dcookie)352 status_t fat_dir::opendir(fscookie *cookie, const char *name, dircookie **dcookie) {
353 auto fat = (fat_fs *)cookie;
354
355 LTRACEF("cookie %p name '%s' dircookie %p\n", cookie, name, dcookie);
356
357 AutoLock guard(fat->lock);
358
359 dir_entry entry;
360 dir_entry_location loc;
361
362 // special case for /
363 if (name[0] == 0 || !strcmp(name, "/")) {
364 entry.attributes = fat_attribute::directory;
365 entry.length = 0;
366 if (fat->info().fat_bits == 32) {
367 entry.start_cluster = fat->info().root_cluster;
368 } else {
369 entry.start_cluster = 0;
370 }
371
372 // special case for the root dir
373 // 0:0 is not sufficient, since we could actually find a file in the root dir
374 // on a fat 12/16 volume (magic cluster 0) at offset 0. cluster 1 is never used
375 // so mark root dir as 1:0
376 loc.starting_dir_cluster = 1;
377 loc.dir_offset = 0;
378 } else {
379 status_t err = fat_walk(fat, name, &entry, &loc);
380 if (err != NO_ERROR) {
381 return err;
382 }
383 }
384
385 // if we walked and found a proper directory, it's a hit
386 if (entry.attributes == fat_attribute::directory) {
387 fat_dir *dir;
388
389 // see if this dir is already present in the fs list
390 fat_file *file = fat->lookup_file(loc);
391 if (file) {
392 // XXX replace with hand rolled RTTI
393 dir = reinterpret_cast<fat_dir *>(file);
394 } else {
395 dir = new fat_dir(fat);
396 }
397 DEBUG_ASSERT(dir);
398
399 fat_dir_cookie *dir_cookie;
400
401 status_t err = dir->opendir_priv(entry, loc, &dir_cookie);
402 if (err < 0) {
403 // weird state, should we dec the ref?
404 PANIC_UNIMPLEMENTED;
405 return err;
406 }
407 DEBUG_ASSERT(dir_cookie);
408
409 *dcookie = (dircookie *)dir_cookie;
410 return NO_ERROR;
411 } else {
412 return ERR_NOT_FILE;
413 }
414
415 return ERR_NOT_IMPLEMENTED;
416 };
417
readdir_priv(fat_dir_cookie * cookie,struct dirent * ent)418 status_t fat_dir::readdir_priv(fat_dir_cookie *cookie, struct dirent *ent) {
419 LTRACEF("dircookie %p ent %p, current index %u\n", cookie, ent, cookie->index);
420
421 if (!ent)
422 return ERR_INVALID_ARGS;
423
424 // make sure the cookie makes sense
425 DEBUG_ASSERT((cookie->index % DIR_ENTRY_LENGTH) == 0);
426
427 char filename_buffer[MAX_FILE_NAME_LEN];
428 char *filename;
429 dir_entry entry;
430
431 {
432 AutoLock guard(fs_->lock);
433
434 // kick start our directory sector iterator
435 LTRACEF("start cluster %u\n", start_cluster_);
436 file_block_iterator dbi(fs_, start_cluster_);
437
438 // move it forward to our index point
439 // also loads the buffer
440 status_t err = dbi.next_sectors(cookie->index / fs_->info().bytes_per_sector);
441 if (err < 0) {
442 return err;
443 }
444
445 // reset how many sectors the dbi has pushed forward so we can account properly for index shifts later
446 dbi.reset_sector_inc_count();
447
448 // pass the index in units of sector offset
449 uint32_t offset = cookie->index % fs_->info().bytes_per_sector;
450 err = fat_find_next_entry(fs_, dbi, offset, &entry, filename_buffer, &filename);
451 if (err < 0) {
452 return err;
453 }
454
455 // bump the index forward by extracting how much the sector iterator pushed things forward
456 uint32_t index_inc = offset - (cookie->index % fs_->info().bytes_per_sector);
457 index_inc += dbi.get_sector_inc_count() * fs_->info().bytes_per_sector;
458 LTRACEF("calculated index increment %u (old index %u, offset %u, sector_inc_count %u)\n",
459 index_inc, cookie->index, offset, dbi.get_sector_inc_count());
460 cookie->index += index_inc;
461 }
462
463 // copy the info into the fs layer's entry
464 strlcpy(ent->name, filename, MIN(sizeof(ent->name), MAX_FILE_NAME_LEN));
465
466 return NO_ERROR;
467 }
468
readdir(dircookie * dcookie,struct dirent * ent)469 status_t fat_dir::readdir(dircookie *dcookie, struct dirent *ent) {
470 auto cookie = (fat_dir_cookie *)dcookie;
471 auto dir = cookie->dir;
472
473 return dir->readdir_priv(cookie, ent);
474 }
475
closedir_priv(fat_dir_cookie * cookie,bool * last_ref)476 status_t fat_dir::closedir_priv(fat_dir_cookie *cookie, bool *last_ref) {
477 LTRACEF("dircookie %p\n", cookie);
478
479 AutoLock guard(fs_->lock);
480
481 // remove the dircookie from the list
482 DEBUG_ASSERT(list_in_list(&cookie->node));
483 list_delete(&cookie->node);
484
485 // delete it
486 delete cookie;
487
488 // drop a ref to the dir
489 *last_ref = dec_ref();
490 if (*last_ref) {
491 DEBUG_ASSERT(list_is_empty(&cookies_));
492 }
493
494 return NO_ERROR;
495 }
496
closedir(dircookie * dcookie)497 status_t fat_dir::closedir(dircookie *dcookie) {
498 auto cookie = (fat_dir_cookie *)dcookie;
499 auto dir = cookie->dir;
500
501 bool last_ref;
502 status_t err = dir->closedir_priv(cookie, &last_ref);
503 if (err < 0) {
504 return err;
505 }
506
507 if (last_ref) {
508 LTRACEF("last ref, deleting %p (%u:%u)\n", dir, dir->dir_loc().starting_dir_cluster, dir->dir_loc().dir_offset);
509 delete dir;
510 }
511
512 return NO_ERROR;
513 }
514
515