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_dir(fscookie * cookie,const char * path,dircookie ** dcookie)37 status_t v9fs_open_dir(fscookie *cookie, const char *path, dircookie **dcookie)
38 {
39     v9fs_t *v9fs = (v9fs_t *)cookie;
40     char temppath[FS_MAX_PATH_LEN];
41     uint32_t flags;
42     int ret;
43 
44     LTRACEF("v9fs (%p) path (%s) dcookie (%p)\n", v9fs, path, dcookie);
45 
46     strlcpy(temppath, path, sizeof(temppath));
47 
48     /* create the dir object */
49     v9fs_dir_t *dir = calloc(1, sizeof(v9fs_dir_t));
50 
51     if (!dir) {
52         ret = ERR_NO_MEMORY;
53         goto err;
54     }
55 
56     dir->fid.fid = get_unused_fid(v9fs);
57 
58     virtio_9p_msg_t twalk = {
59         .msg_type = P9_TWALK,
60         .tag = P9_TAG_DEFAULT,
61         .msg.twalk = {
62             .fid = v9fs->root.fid, .newfid = dir->fid.fid
63         }
64     };
65     virtio_9p_msg_t rwalk = {};
66 
67     path_to_wname(temppath, &twalk.msg.twalk.nwname, twalk.msg.twalk.wname);
68 
69     LTRACEF("twalk.msg.twalk.nwname (%u)\n", twalk.msg.twalk.nwname);
70     for (int i = 0; i < twalk.msg.twalk.nwname; i++) {
71         LTRACEF("twalk.msg.twalk.wname[%d] (%s)\n", i, twalk.msg.twalk.wname[i]);
72 
73         if (strlen(twalk.msg.twalk.wname[i]) == 0) {
74             twalk.msg.twalk.wname[i] = "/";
75         }
76     }
77 
78     if ((ret = virtio_9p_rpc(v9fs->dev, &twalk, &rwalk)) != NO_ERROR)
79         goto err;
80 
81     if (rwalk.msg_type != P9_RWALK ||
82             rwalk.msg.rwalk.nwqid != twalk.msg.twalk.nwname) {
83         ret = ERR_NOT_FOUND;
84         goto err;
85     }
86 
87     // assume all dir are opened as readonly directory
88     flags = O_DIRECTORY | O_RDONLY;
89 
90     virtio_9p_msg_t tlopen = {
91         .msg_type= P9_TLOPEN,
92         .tag = P9_TAG_DEFAULT,
93         .msg.tlopen = {
94             .fid = dir->fid.fid, .flags = flags,
95         }
96     };
97     virtio_9p_msg_t rlopen = {};
98 
99     if ((ret = virtio_9p_rpc(v9fs->dev, &tlopen, &rlopen)) != NO_ERROR)
100         goto des_rwalk;
101 
102     if (rlopen.msg_type != P9_RLOPEN) {
103         ret = ERR_ACCESS_DENIED;
104         goto des_rwalk;
105     }
106 
107     if (rlopen.msg.rlopen.qid.type != P9_QTDIR) {
108         ret = ERR_NOT_DIR;
109         goto des_rwalk;
110     }
111 
112     dir->v9fs = v9fs;
113     dir->fid.qid = rlopen.msg.rlopen.qid;
114     dir->fid.iounit = rlopen.msg.rlopen.iounit;
115     dir->offset = 0;
116     dir->head = 0;
117     dir->tail = 0;
118 
119     *dcookie = (dircookie *)dir;
120     list_add_tail(&v9fs->dirs, &dir->node);
121 
122     virtio_9p_msg_destroy(&rlopen);
123     virtio_9p_msg_destroy(&rwalk);
124 
125     return NO_ERROR;
126 
127 des_rwalk:
128     virtio_9p_msg_destroy(&rwalk);
129     put_fid(v9fs, dir->fid.fid);
130 
131 err:
132     free(dir);
133     LTRACEF("failed: %d\n", ret);
134     return ret;
135 }
136 
v9fs_mkdir(fscookie * cookie,const char * path)137 status_t v9fs_mkdir(fscookie *cookie, const char *path)
138 {
139     v9fs_t *v9fs = (v9fs_t *)cookie;
140     char temppath[FS_MAX_PATH_LEN];
141     char *dirname;
142     uint32_t dfid, mode;
143     int ret;
144 
145     LTRACEF("v9fs (%p) path (%s)\n", v9fs, path);
146 
147     strlcpy(temppath, path, sizeof(temppath));
148 
149     dfid = 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 = dfid,
156         }
157     };
158     virtio_9p_msg_t rwalk = {};
159 
160     // separate the directory and the dirname
161     dirname = strrchr(temppath, '/');
162     if (!dirname || dirname == temppath) {        // create on the root dir
163         dirname = temppath;
164         twalk.msg.twalk.nwname = 0;
165     } else {                                      // create on a dir
166         // parse the parent directory
167         *dirname++ = '\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 dir is created as 040755
182     mode = S_IFDIR | S_IRWXU |
183            S_IRGRP | S_IWGRP |
184            S_IROTH | S_IWOTH;
185 
186     virtio_9p_msg_t tmkdir = {
187         .msg_type= P9_TMKDIR,
188         .tag = P9_TAG_DEFAULT,
189         .msg.tmkdir = {
190             .dfid = dfid, .name = dirname, .mode = mode,
191         }
192     };
193     virtio_9p_msg_t rmkdir = {};
194 
195     if ((ret = virtio_9p_rpc(v9fs->dev, &tmkdir, &rmkdir)) != NO_ERROR)
196         goto des_rwalk;
197 
198     if (rmkdir.msg_type != P9_RMKDIR) {
199         ret = ERR_NOT_ALLOWED;
200     }
201 
202     virtio_9p_msg_destroy(&rmkdir);
203 
204 des_rwalk:
205     virtio_9p_msg_destroy(&rwalk);
206 
207 err:
208     put_fid(v9fs, dfid);
209     return ret;
210 }
211 
v9fs_read_dir(dircookie * dcookie,struct dirent * ent)212 status_t v9fs_read_dir(dircookie *dcookie, struct dirent *ent)
213 {
214     v9fs_dir_t *dir = (v9fs_dir_t *)dcookie;
215     p9_dirent_t p9_dent;
216     status_t ret = NO_ERROR;
217     ssize_t sread = 0;
218 
219     LTRACEF("dir (%p) ent (%p)\n", dir, ent);
220 
221     if (!dir && !ent)
222         return ERR_INVALID_ARGS;
223 
224     while (true) {
225         // read directory entries from v9p to fill buffer
226         if (dir->head == dir->tail) {
227             virtio_9p_msg_t treaddir = {
228                 .msg_type= P9_TREADDIR,
229                 .tag = P9_TAG_DEFAULT,
230                 .msg.treaddir = {
231                     .fid = dir->fid.fid, .offset = dir->offset,
232                     .count = PAGE_SIZE,
233                 }
234             };
235             virtio_9p_msg_t rreaddir = {};
236 
237             if ((ret = virtio_9p_rpc(dir->v9fs->dev, &treaddir, &rreaddir)) !=
238                     NO_ERROR) {
239                 return ret;
240             }
241 
242             if (rreaddir.msg_type != P9_RREADDIR) {
243                 virtio_9p_msg_destroy(&rreaddir);
244                 return ERR_IO;
245             }
246 
247             dir->head = 0;
248             dir->tail = MIN(rreaddir.msg.rreaddir.count, PAGE_SIZE);
249             memcpy(&dir->data, rreaddir.msg.rreaddir.data, dir->tail);
250             LTRACEF("head (%u) tail (%u) data (%p)\n", dir->head, dir->tail,
251                     dir->data);
252 
253             virtio_9p_msg_destroy(&rreaddir);
254 
255             if (dir->tail == 0) {
256                 // no more entries
257                 return ERR_OUT_OF_RANGE;
258             }
259         }
260 
261         // read the dirent from buffer
262         while (dir->head < dir->tail) {
263             sread = p9_dirent_read(dir->data + dir->head, dir->tail - dir->head,
264                                    &p9_dent);
265             if (sread < NO_ERROR) {
266                 // invalid dirent
267                 return sread;
268             }
269 
270             LTRACEF(
271                 "head (%u) tail (%u) sread (%ld) offset (%llu) name "
272                 "(%s)\n",
273                 dir->head, dir->tail, sread, p9_dent.offset, p9_dent.name);
274 
275             dir->head += sread;
276             snprintf(ent->name, FS_MAX_FILE_LEN, "%s", p9_dent.name);
277             dir->offset = p9_dent.offset;
278 
279             p9_dirent_destroy(&p9_dent);
280 
281             return NO_ERROR;
282         }
283     }
284 
285     ASSERT(false); // Impossible to get here
286 }
287 
v9fs_close_dir(dircookie * dcookie)288 status_t v9fs_close_dir(dircookie *dcookie)
289 {
290     v9fs_dir_t *dir = (v9fs_dir_t *)dcookie;
291 
292     LTRACEF("dir (%p)\n", dir);
293 
294     if (!dir)
295         return NO_ERROR;
296 
297     put_fid(dir->v9fs, dir->fid.fid);
298     list_delete(&dir->node);
299     free(dir);
300 
301     return NO_ERROR;
302 }
303