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