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