1 /*
2 * Copyright (c) 2024, Google Inc. All rights reserved.
3 * Author: codycswong@google.com (Cody Wong)
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files
7 * (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24 #include <dev/virtio/9p.h>
25
26 #include <stdio.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <lk/err.h>
31 #include <lk/trace.h>
32
33 #include "v9fs_priv.h"
34
35 #define LOCAL_TRACE 0
36
v9fs_open_file(fscookie * cookie,const char * path,filecookie ** fcookie)37 status_t v9fs_open_file(fscookie *cookie, const char *path,
38 filecookie **fcookie)
39 {
40 v9fs_t *v9fs = (v9fs_t *)cookie;
41 char temppath[FS_MAX_PATH_LEN];
42 uint32_t flags;
43 int ret;
44
45 LTRACEF("v9fs (%p) path (%s) fcookie (%p)\n", v9fs, path, fcookie);
46
47 strlcpy(temppath, path, sizeof(temppath));
48
49 /* create the file object */
50 v9fs_file_t *file = calloc(1, sizeof(v9fs_file_t));
51
52 if (!file) {
53 ret = ERR_NO_MEMORY;
54 goto err;
55 }
56
57 file->pg_buf.size = 0;
58 file->pg_buf.index = 0;
59 file->pg_buf.dirty = false;
60 file->pg_buf.need_update = true;
61 mutex_init(&file->lock);
62
63 file->fid.fid = get_unused_fid(v9fs);
64
65 virtio_9p_msg_t twalk = {
66 .msg_type = P9_TWALK,
67 .tag = P9_TAG_DEFAULT,
68 .msg.twalk = {
69 .fid = v9fs->root.fid, .newfid = file->fid.fid
70 }
71 };
72 virtio_9p_msg_t rwalk = {};
73
74 path_to_wname(temppath, &twalk.msg.twalk.nwname, twalk.msg.twalk.wname);
75
76 if ((ret = virtio_9p_rpc(v9fs->dev, &twalk, &rwalk)) != NO_ERROR)
77 goto err;
78
79 if (rwalk.msg_type != P9_RWALK ||
80 rwalk.msg.rwalk.nwqid != twalk.msg.twalk.nwname) {
81 ret = ERR_NOT_FOUND;
82 goto err;
83 }
84
85 // assume all file are opened as "w+"
86 flags = O_RDWR;
87
88 virtio_9p_msg_t tlopen = {
89 .msg_type= P9_TLOPEN,
90 .tag = P9_TAG_DEFAULT,
91 .msg.tlopen = {
92 .fid = file->fid.fid, .flags = flags,
93 }
94 };
95 virtio_9p_msg_t rlopen = {};
96
97 if ((ret = virtio_9p_rpc(v9fs->dev, &tlopen, &rlopen)) != NO_ERROR)
98 goto des_rwalk;
99
100 file->v9fs = v9fs;
101 file->fid.qid = rlopen.msg.rlopen.qid;
102 file->fid.iounit = rlopen.msg.rlopen.iounit;
103
104 *fcookie = (filecookie *)file;
105 list_add_tail(&v9fs->files, &file->node);
106
107 virtio_9p_msg_destroy(&rlopen);
108 virtio_9p_msg_destroy(&rwalk);
109
110 return NO_ERROR;
111
112 des_rwalk:
113 virtio_9p_msg_destroy(&rwalk);
114
115 err:
116 LTRACEF("open file (%s) failed: %d\n", path, ret);
117 free(file);
118 return ret;
119 }
120
v9fs_create_file(fscookie * cookie,const char * path,filecookie ** fcookie,uint64_t len)121 status_t v9fs_create_file(fscookie *cookie, const char *path,
122 filecookie **fcookie, uint64_t len)
123 {
124 v9fs_t *v9fs = (v9fs_t *)cookie;
125 char temppath[FS_MAX_PATH_LEN];
126 char *filename;
127 uint32_t flags, mode;
128 status_t ret;
129
130 LTRACEF("v9fs (%p) path (%s) fcookie (%p) len (%llu)\n", v9fs, path,
131 fcookie, len);
132
133 strlcpy(temppath, path, sizeof(temppath));
134
135 /* create the file object */
136 v9fs_file_t *file = calloc(1, sizeof(v9fs_file_t));
137
138 if (!file) {
139 ret = ERR_NO_MEMORY;
140 goto err;
141 }
142
143 file->pg_buf.size = 0;
144 file->pg_buf.index = 0;
145 file->pg_buf.dirty = false;
146 file->pg_buf.need_update = true;
147 mutex_init(&file->lock);
148
149 file->fid.fid = get_unused_fid(v9fs);
150
151 virtio_9p_msg_t twalk = {
152 .msg_type = P9_TWALK,
153 .tag = P9_TAG_DEFAULT,
154 .msg.twalk = {
155 .fid = v9fs->root.fid, .newfid = file->fid.fid
156 }
157 };
158 virtio_9p_msg_t rwalk = {};
159
160 // separate the directory and the filename
161 filename = strrchr(temppath, '/');
162 if (!filename || filename == temppath) { // create on the root dir
163 filename = temppath;
164 twalk.msg.twalk.nwname = 0;
165 } else { // create on a dir
166 // parse the parent directory
167 *filename++ = '\0';
168 path_to_wname(temppath, &twalk.msg.twalk.nwname, twalk.msg.twalk.wname);
169 }
170
171 // walk to the parent directory
172 if ((ret = virtio_9p_rpc(v9fs->dev, &twalk, &rwalk)) != NO_ERROR)
173 goto err;
174
175 if (rwalk.msg_type != P9_RWALK ||
176 rwalk.msg.rwalk.nwqid != twalk.msg.twalk.nwname) {
177 ret = ERR_NOT_DIR;
178 goto err;
179 }
180
181 // assume the file is created as 0666
182 mode = S_IRUSR | S_IWUSR |
183 S_IRGRP | S_IWGRP |
184 S_IROTH | S_IWOTH;
185 // assume all file are opened as "w+"
186 flags = O_RDWR | O_CREAT | O_TRUNC;
187
188 virtio_9p_msg_t tlcreate = {
189 .msg_type= P9_TLCREATE,
190 .tag = P9_TAG_DEFAULT,
191 .msg.tlcreate = {
192 .fid = file->fid.fid, .flags = flags, .mode = mode,
193 .name = filename,
194 }
195 };
196 virtio_9p_msg_t rlcreate = {};
197
198 if ((ret = virtio_9p_rpc(v9fs->dev, &tlcreate, &rlcreate)) != NO_ERROR)
199 goto des_rwalk;
200
201 file->v9fs = v9fs;
202 file->fid.qid = rlcreate.msg.rlopen.qid;
203 file->fid.iounit = rlcreate.msg.rlopen.iounit;
204
205 *fcookie = (filecookie *)file;
206 list_add_tail(&v9fs->files, &file->node);
207
208 virtio_9p_msg_destroy(&rlcreate);
209 virtio_9p_msg_destroy(&rwalk);
210
211 return NO_ERROR;
212
213 des_rwalk:
214 virtio_9p_msg_destroy(&rwalk);
215
216 err:
217 LTRACEF("create file (%s) failed: %d\n", path, ret);
218 free(file);
219 return ret;
220 }
221
read_file_impl(v9fs_file_t * file,void * buf,off_t offset,size_t len)222 static ssize_t read_file_impl(v9fs_file_t *file, void *buf, off_t offset,
223 size_t len)
224 {
225 status_t err = NO_ERROR;
226 ssize_t rlen = 0;
227 uint32_t readcount;
228
229 while (len > 0) {
230 virtio_9p_msg_t tread = {
231 .msg_type= P9_TREAD,
232 .tag = P9_TAG_DEFAULT,
233 .msg.tread = {
234 .fid = file->fid.fid, .offset = offset,
235 .count = MIN(len, PAGE_SIZE)
236 }
237 };
238 virtio_9p_msg_t rread = {};
239
240 if ((err = virtio_9p_rpc(file->v9fs->dev, &tread, &rread)) != NO_ERROR)
241 break;
242
243 if (rread.msg_type != P9_RREAD) {
244 err = ERR_IO;
245 break;
246 }
247
248 readcount = rread.msg.rread.count;
249
250 memcpy(&((uint8_t *)buf)[rlen], rread.msg.rread.data, readcount);
251
252 offset += readcount;
253 rlen += readcount;
254 len -= readcount;
255
256 virtio_9p_msg_destroy(&rread);
257
258 // read to the end of the file
259 if (rread.msg.rread.count == 0)
260 break;
261 }
262
263 return err == NO_ERROR ? rlen : err;
264 }
265
write_file_impl(v9fs_file_t * file,const void * buf,off_t offset,size_t len)266 static ssize_t write_file_impl(v9fs_file_t *file, const void *buf, off_t offset,
267 size_t len)
268 {
269 status_t err = NO_ERROR;
270 ssize_t wlen = 0;
271 uint32_t writecount;
272 const uint8_t *cpos = buf;
273
274 while (len > 0) {
275 virtio_9p_msg_t twrite = {
276 .msg_type= P9_TWRITE,
277 .tag = P9_TAG_DEFAULT,
278 .msg.twrite = {
279 .fid = file->fid.fid, .offset = offset, .data = cpos,
280 .count = MIN(len, PAGE_SIZE)
281 }
282 };
283 virtio_9p_msg_t rwrite = {};
284
285 if ((err = virtio_9p_rpc(file->v9fs->dev, &twrite, &rwrite)) !=
286 NO_ERROR)
287 break;
288
289 if (rwrite.msg_type != P9_RWRITE) {
290 err = ERR_IO;
291 break;
292 }
293
294 writecount = rwrite.msg.rwrite.count;
295
296 offset += writecount;
297 cpos += writecount;
298 wlen += writecount;
299 len -= writecount;
300
301 virtio_9p_msg_destroy(&rwrite);
302 }
303
304 return err == NO_ERROR ? wlen : err;
305 }
306
307 #define fs_page_index(off) ((off) / V9FS_FILE_PAGE_BUFFER_SIZE)
308 #define fs_page_start_by_offset(off) \
309 ROUNDDOWN((off), V9FS_FILE_PAGE_BUFFER_SIZE)
310 #define fs_page_start_by_index(idx) ((idx) * V9FS_FILE_PAGE_BUFFER_SIZE)
311
clamp(int val,int min,int max)312 static inline int clamp(int val, int min, int max)
313 {
314 const int t = (val < min) ? min : val;
315 return (t > max) ? max : t;
316 }
317
fs_valid_page(off_t offset,off_t size)318 static bool fs_valid_page(off_t offset, off_t size)
319 {
320 // the access size must not be larger than the size of a page buffer
321 if (size >= V9FS_FILE_PAGE_BUFFER_SIZE)
322 return false;
323
324 // the access range lies in a page
325 return fs_page_index(offset) == fs_page_index(offset + size - 1);
326 }
327
fs_page_hit(struct fs_page_buffer * buf,off_t offset)328 static bool fs_page_hit(struct fs_page_buffer *buf, off_t offset)
329 {
330 return fs_page_index(offset) == buf->index;
331 }
332
fs_page_update(v9fs_file_t * file,off_t offset)333 static void fs_page_update(v9fs_file_t *file, off_t offset)
334 {
335 ssize_t rsize;
336
337 if (fs_page_hit(&file->pg_buf, offset) &&
338 !file->pg_buf.need_update) {
339 // page hit and don't need to update
340 return;
341 }
342
343 if (file->pg_buf.dirty) {
344 write_file_impl(file, file->pg_buf.data,
345 fs_page_start_by_index(file->pg_buf.index),
346 file->pg_buf.size);
347 file->pg_buf.dirty = false;
348 }
349
350 memset(file->pg_buf.data, 0, V9FS_FILE_PAGE_BUFFER_SIZE);
351 rsize =
352 read_file_impl(file, file->pg_buf.data, fs_page_start_by_offset(offset),
353 V9FS_FILE_PAGE_BUFFER_SIZE);
354 file->pg_buf.size = rsize;
355 file->pg_buf.index = fs_page_index(offset);
356 file->pg_buf.need_update = false;
357 file->pg_buf.dirty = false;
358 }
359
fs_page_read(v9fs_file_t * file,void * buf,off_t offset,size_t size)360 static ssize_t fs_page_read(v9fs_file_t *file, void *buf, off_t offset,
361 size_t size)
362 {
363 ssize_t rsize;
364
365 fs_page_update(file, offset);
366
367 offset %= V9FS_FILE_PAGE_BUFFER_SIZE;
368 rsize = clamp(size, 0, file->pg_buf.size - offset);
369 memcpy(buf, file->pg_buf.data + offset, rsize);
370
371 return rsize;
372 }
373
fs_page_write(v9fs_file_t * file,const void * buf,off_t offset,size_t size)374 static ssize_t fs_page_write(v9fs_file_t *file, const void *buf, off_t offset,
375 size_t size)
376 {
377 fs_page_update(file, offset);
378
379 offset %= V9FS_FILE_PAGE_BUFFER_SIZE;
380 memcpy(file->pg_buf.data + offset, buf, size);
381 file->pg_buf.size = offset + size;
382 file->pg_buf.dirty = true;
383
384 return size;
385 }
386
v9fs_read_file(filecookie * fcookie,void * buf,off_t offset,size_t len)387 ssize_t v9fs_read_file(filecookie *fcookie, void *buf, off_t offset, size_t len)
388 {
389 v9fs_file_t *file = (v9fs_file_t *)fcookie;
390 ssize_t rsize;
391 status_t ret;
392
393 LTRACEF("file (%p) buf (%p) offset (%lld) len (%zd)\n", file, buf,
394 offset, len);
395
396 if (!fcookie && !buf)
397 return ERR_INVALID_ARGS;
398
399 if ((ret = mutex_acquire_timeout(&file->lock, V9FS_FILE_LOCK_TIMEOUT)) !=
400 NO_ERROR) {
401 return ret;
402 }
403
404 if (fs_valid_page(offset, len)) {
405 rsize = fs_page_read(file, buf, offset, len);
406 } else {
407 rsize = read_file_impl(file, buf, offset, len);
408 }
409
410 mutex_release(&file->lock);
411
412 return rsize;
413 }
414
v9fs_write_file(filecookie * fcookie,const void * buf,off_t offset,size_t len)415 ssize_t v9fs_write_file(filecookie *fcookie, const void *buf, off_t offset,
416 size_t len)
417 {
418 v9fs_file_t *file = (v9fs_file_t *)fcookie;
419 ssize_t rsize;
420 status_t ret;
421
422 LTRACEF("file (%p) buf (%p) offset (%lld) len (%zd)\n", file, buf,
423 offset, len);
424
425 if (!fcookie && !buf)
426 return ERR_INVALID_ARGS;
427
428 if ((ret = mutex_acquire_timeout(&file->lock, V9FS_FILE_LOCK_TIMEOUT)) !=
429 NO_ERROR) {
430 return ret;
431 }
432
433 if (fs_valid_page(offset, len)) {
434 rsize = fs_page_write(file, buf, offset, len);
435 } else {
436 rsize = write_file_impl(file, buf, offset, len);
437 }
438
439 mutex_release(&file->lock);
440
441 return rsize;
442 }
443
v9fs_close_file(filecookie * fcookie)444 status_t v9fs_close_file(filecookie *fcookie)
445 {
446 v9fs_file_t *file = (v9fs_file_t *)fcookie;
447 status_t ret;
448
449 LTRACEF("file (%p)\n", file);
450
451 if (!file)
452 return NO_ERROR;
453
454 if ((ret = mutex_acquire_timeout(&file->lock, V9FS_FILE_LOCK_TIMEOUT)) !=
455 NO_ERROR) {
456 return ret;
457 }
458
459 if (file->pg_buf.dirty) {
460 // writeback the dirty page
461 write_file_impl(file, file->pg_buf.data,
462 fs_page_start_by_index(file->pg_buf.index),
463 file->pg_buf.size);
464 file->pg_buf.dirty = false;
465 }
466
467 put_fid(file->v9fs, file->fid.fid);
468 list_delete(&file->node);
469
470 mutex_release(&file->lock);
471
472 free(file);
473
474 return NO_ERROR;
475 }
476
v9fs_stat_file(filecookie * fcookie,struct file_stat * stat)477 status_t v9fs_stat_file(filecookie *fcookie, struct file_stat *stat)
478 {
479 v9fs_file_t *file = (v9fs_file_t *)fcookie;
480 status_t ret;
481
482 LTRACEF("file (%p) stat (%p)\n", file, stat);
483
484 if ((ret = mutex_acquire_timeout(&file->lock, V9FS_FILE_LOCK_TIMEOUT)) !=
485 NO_ERROR) {
486 return ret;
487 }
488
489 virtio_9p_msg_t tgatt = {
490 .msg_type = P9_TGETATTR,
491 .tag = P9_TAG_DEFAULT,
492 .msg.tgetattr = {
493 .fid = file->fid.fid, .request_mask = P9_GETATTR_BASIC,
494 }
495 };
496 virtio_9p_msg_t rgatt = {};
497
498 if ((ret = virtio_9p_rpc(file->v9fs->dev, &tgatt, &rgatt)) != NO_ERROR)
499 return ret;
500 if (rgatt.msg_type != P9_RGETATTR) {
501 ret = ERR_BUSY;
502 goto err;
503 }
504
505 stat->size = rgatt.msg.rgetattr.size;
506 stat->is_dir = S_ISDIR(rgatt.msg.rgetattr.mode);
507
508 err:
509 virtio_9p_msg_destroy(&rgatt);
510
511 mutex_release(&file->lock);
512
513 return ret;
514 }
515