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 "file.h"
10
11 #include <lk/err.h>
12 #include <lib/bio.h>
13 #include <lib/fs.h>
14 #include <lk/trace.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <lk/debug.h>
18
19 #include "fat_fs.h"
20 #include "fat_priv.h"
21 #include "dir.h"
22
23 #include "file_iterator.h"
24
25 #define LOCAL_TRACE FAT_GLOBAL_TRACE(0)
26
fat_file(fat_fs * f)27 fat_file::fat_file(fat_fs *f) : fs_(f) {}
28 fat_file::~fat_file() = default;
29
inc_ref()30 void fat_file::inc_ref() {
31 ref_++;
32 LTRACEF_LEVEL(2, "file %p (%u:%u): ref now %i\n", this, dir_loc_.starting_dir_cluster, dir_loc_.dir_offset, ref_);
33 if (ref_ == 1) {
34 DEBUG_ASSERT(!list_in_list(&node_));
35 fs_->add_to_file_list(this);
36 }
37 DEBUG_ASSERT(list_in_list(&node_));
38 }
39
dec_ref()40 bool fat_file::dec_ref() {
41 ref_--;
42 LTRACEF_LEVEL(2, "file %p (%u:%u): ref now %i\n", this, dir_loc_.starting_dir_cluster, dir_loc_.dir_offset, ref_);
43 if (ref_ == 0) {
44 DEBUG_ASSERT(list_in_list(&node_));
45 list_delete(&node_);
46 return true;
47 }
48
49 return false;
50 }
51
open_file_priv(const dir_entry & entry,const dir_entry_location & loc)52 status_t fat_file::open_file_priv(const dir_entry &entry, const dir_entry_location &loc) {
53 DEBUG_ASSERT(fs_->lock.is_held());
54
55 LTRACEF("found file at location %u:%u\n", loc.starting_dir_cluster, loc.dir_offset);
56
57 // move this out to the wrapper function so we can properly deal with dirs
58 //
59 // did we get a file?
60 if (entry.attributes != fat_attribute::directory) {
61 // XXX better attribute testing
62 start_cluster_ = entry.start_cluster;
63 length_ = entry.length;
64 attributes_ = entry.attributes;
65 dir_loc_ = loc;
66 inc_ref();
67 return NO_ERROR;
68 } else if (entry.attributes == fat_attribute::directory) {
69 // we can open directories, but just not do anything with it except stat
70 start_cluster_ = entry.start_cluster;
71 length_ = 0;
72 attributes_ = entry.attributes;
73 dir_loc_ = loc;
74 inc_ref();
75 return NO_ERROR;
76 } else {
77 return ERR_NOT_VALID;
78 }
79 }
80
open_file(fscookie * cookie,const char * path,filecookie ** fcookie)81 status_t fat_file::open_file(fscookie *cookie, const char *path, filecookie **fcookie) {
82 fat_fs *fs = (fat_fs *)cookie;
83
84 LTRACEF("fscookie %p path '%s' fcookie %p\n", cookie, path, fcookie);
85
86 AutoLock guard(fs->lock);
87
88 // look for the file in the fs
89 dir_entry entry;
90 dir_entry_location loc;
91 status_t err = fat_walk(fs, path, &entry, &loc);
92 if (err != NO_ERROR) {
93 return err;
94 }
95
96 // we found it, see if there's an existing file object
97 fat_file *file = fs->lookup_file(loc);
98 if (!file) {
99 // didn't find an existing one, create a new object
100 file = new fat_file(fs);
101 }
102 DEBUG_ASSERT(file);
103
104 // perform file object private open
105 err = file->open_file_priv(entry, loc);
106 if (err < 0) {
107 delete file;
108 return err;
109 }
110
111 *fcookie = (filecookie *)file;
112
113 return err;
114 }
115
read_file_priv(void * _buf,const off_t offset,size_t len)116 ssize_t fat_file::read_file_priv(void *_buf, const off_t offset, size_t len) {
117 uint8_t *buf = (uint8_t *)_buf;
118
119 LTRACEF("file %p buf %p offset %lld len %zu\n", this, _buf, offset, len);
120
121 if (is_dir()) {
122 return ERR_NOT_FILE;
123 }
124
125 // negative offsets are invalid
126 if (offset < 0) {
127 return ERR_INVALID_ARGS;
128 }
129
130 AutoLock guard(fs_->lock);
131
132 // trim the read to the file
133 if (offset >= length_) {
134 return 0;
135 }
136
137 // above test should ensure offset < 32bit because file->length is 32bit unsigned
138 DEBUG_ASSERT(offset <= UINT32_MAX);
139
140 if (offset + len > length_) {
141 len = length_ - offset;
142 }
143
144 LTRACEF("trimmed offset %lld len %zu\n", offset, len);
145
146 // create a file block iterator and push it forward to the starting point
147 uint32_t logical_cluster = offset / fs_->info().bytes_per_cluster;
148 uint32_t sector_within_cluster = (offset % fs_->info().bytes_per_cluster) / fs_->info().bytes_per_sector;
149 uint32_t offset_within_sector = offset % fs_->info().bytes_per_sector;
150
151 LTRACEF("starting off logical cluster %u, sector within %u, offset within %u\n",
152 logical_cluster, sector_within_cluster, offset_within_sector);
153
154 file_block_iterator fbi(fs_, start_cluster_);
155
156 // move it forward to our index point
157 // also loads the buffer
158 status_t err = fbi.next_sectors(logical_cluster * fs_->info().sectors_per_cluster + sector_within_cluster);
159 if (err < 0) {
160 LTRACEF("error moving up to starting point!\n");
161 return err;
162 }
163
164 ssize_t amount_read = 0;
165 size_t buf_offset = 0; // offset into the output buffer
166 while (buf_offset < len) {
167 size_t to_read = MIN(fs_->info().bytes_per_sector - offset_within_sector, len - buf_offset);
168
169 LTRACEF("top of loop: sector offset %u, buf offset %zu, remaining len %zu, to_read %zu\n",
170 offset_within_sector, buf_offset, len, to_read);
171
172 // grab a pointer directly to the block cache
173 const uint8_t *ptr;
174 ptr = fbi.get_bcache_ptr(offset_within_sector);
175 if (!ptr) {
176 LTRACEF("error getting pointer to file in cache\n");
177 return ERR_IO;
178 }
179
180 // make sure we dont do something silly
181 DEBUG_ASSERT(buf_offset + to_read <= len);
182 DEBUG_ASSERT(offset_within_sector + to_read <= fs_->info().bytes_per_sector);
183
184 // copy out to the buffer
185 memcpy(buf + buf_offset, ptr, to_read);
186
187 // next iteration of the loop
188 amount_read += to_read;
189 buf_offset += to_read;
190
191 // go to the next sector
192 offset_within_sector += to_read;
193 DEBUG_ASSERT(offset_within_sector <= fs_->info().bytes_per_sector);
194 if (offset_within_sector == fs_->info().bytes_per_sector) {
195 offset_within_sector = 0;
196 // push the iterator forward to next sector
197 err = fbi.next_sector();
198 if (err < 0) {
199 return err;
200 }
201 }
202 }
203
204 return amount_read;
205 }
206
read_file(filecookie * fcookie,void * _buf,const off_t offset,size_t len)207 ssize_t fat_file::read_file(filecookie *fcookie, void *_buf, const off_t offset, size_t len) {
208 fat_file *file = (fat_file *)fcookie;
209
210 return file->read_file_priv(_buf, offset, len);
211 }
212
stat_file_priv(struct file_stat * stat)213 status_t fat_file::stat_file_priv(struct file_stat *stat) {
214 AutoLock guard(fs_->lock);
215
216 stat->size = length_;
217 stat->is_dir = is_dir();
218 return NO_ERROR;
219 }
220
stat_file(filecookie * fcookie,struct file_stat * stat)221 status_t fat_file::stat_file(filecookie *fcookie, struct file_stat *stat) {
222 fat_file *file = (fat_file *)fcookie;
223
224 return file->stat_file_priv(stat);
225 }
226
close_file_priv(bool * last_ref)227 status_t fat_file::close_file_priv(bool *last_ref) {
228 AutoLock guard(fs_->lock);
229
230 // drop a ref to it, which may remove from the global list
231 // and return whether or not it was the last ref
232 *last_ref = dec_ref();
233
234 return NO_ERROR;
235 }
236
close_file(filecookie * fcookie)237 status_t fat_file::close_file(filecookie *fcookie) {
238 fat_file *file = (fat_file *)fcookie;
239
240 bool last_ref;
241 status_t err = file->close_file_priv(&last_ref);
242 if (err < 0) {
243 return err;
244 }
245
246 // if this was the last ref, delete the file
247 if (last_ref) {
248 LTRACEF("last ref, deleting %p (%u:%u)\n", file, file->dir_loc().starting_dir_cluster, file->dir_loc().dir_offset);
249 delete file;
250 }
251
252 return NO_ERROR;
253 }
254