1 /*-
2 * Copyright (c) 2022 Netflix, Inc
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 #include <sys/types.h>
8 #include "stand.h"
9 #include "host_syscall.h"
10 #include "kboot.h"
11
12 #define HOST_PATH_MAX 1025
13
14 extern struct devsw host_dev;
15
16 const char *hostfs_root = "/";
17
18 enum FTYPE {
19 regular,
20 dir,
21 };
22
23 typedef struct _hostfs_file {
24 enum FTYPE hf_type;
25 int hf_fd;
26 /* The following are only used for FTYPE == dir */
27 char hf_dents[2048];
28 char *hf_curdent;
29 int hf_dentlen; /* Valid part of hf_dents */
30 } hostfs_file;
31
32 static hostfs_file *
hostfs_alloc(void)33 hostfs_alloc(void)
34 {
35 hostfs_file *hf;
36
37 hf = malloc(sizeof(*hf));
38 if (hf != NULL)
39 memset(hf, 0, sizeof(*hf));
40 return (hf);
41 }
42
43 static void
hostfs_free(hostfs_file * hf)44 hostfs_free(hostfs_file *hf)
45 {
46 free(hf);
47 }
48
49 static int
hostfs_open(const char * fn,struct open_file * f)50 hostfs_open(const char *fn, struct open_file *f)
51 {
52 hostfs_file *hf;
53 struct host_kstat ksb;
54 char path[HOST_PATH_MAX];
55
56 if (f->f_dev != &host_dev) {
57 return (EINVAL);
58 }
59
60 /*
61 * Normally, we root everything at hostfs_root. However, there are two
62 * exceptions that make it easier to write code. First is /sys and /proc
63 * are special Linux filesystems, so we pass those paths
64 * through. Second, if the path starts with //, then we strip off the
65 * first / and pass it through (in a weird way, this is actually in
66 * POSIX: hosts are allowed to do specail things with paths that start
67 * with two //, but one or three or more are required to be treated as
68 * one).
69 */
70 if (strncmp("/sys/", fn, 5) == 0 || strncmp("/proc/", fn, 6) == 0)
71 strlcpy(path, fn, sizeof(path));
72 else if (fn[0] == '/' && fn[1] == '/' && fn[2] != '/')
73 strlcpy(path, fn + 1, sizeof(path));
74 else
75 snprintf(path, sizeof(path), "%s/%s", hostfs_root, fn);
76 hf = hostfs_alloc();
77 hf->hf_fd = host_open(path, HOST_O_RDONLY, 0);
78 if (hf->hf_fd < 0) {
79 hostfs_free(hf);
80 return (EINVAL);
81 }
82
83 if (host_fstat(hf->hf_fd, &ksb) < 0) {
84 hostfs_free(hf);
85 return (EINVAL);
86 }
87 if (S_ISDIR(hf->hf_fd)) {
88 hf->hf_type = dir;
89 } else {
90 hf->hf_type = regular;
91 }
92 f->f_fsdata = hf;
93 return (0);
94 }
95
96 static int
hostfs_close(struct open_file * f)97 hostfs_close(struct open_file *f)
98 {
99 hostfs_file *hf = f->f_fsdata;
100
101 host_close(hf->hf_fd);
102 hostfs_free(hf);
103 f->f_fsdata = NULL;
104
105 return (0);
106 }
107
108 static int
hostfs_read(struct open_file * f,void * start,size_t size,size_t * resid)109 hostfs_read(struct open_file *f, void *start, size_t size, size_t *resid)
110 {
111 hostfs_file *hf = f->f_fsdata;
112 ssize_t sz;
113
114 sz = host_read(hf->hf_fd, start, size);
115 if (sz < 0)
116 return (host_to_stand_errno(sz));
117 *resid = size - sz;
118
119 return (0);
120 }
121
122 static off_t
hostfs_seek(struct open_file * f,off_t offset,int whence)123 hostfs_seek(struct open_file *f, off_t offset, int whence)
124 {
125 hostfs_file *hf = f->f_fsdata;
126 uint32_t offl, offh;
127 long err;
128 uint64_t res;
129
130 /*
131 * Assumes Linux host with 'reduced' system call wrappers. Also assume
132 * host and libstand have same whence encoding (safe since it all comes
133 * from V7 later ISO-C). Also assumes we have to support powerpc still,
134 * it's interface is weird for legacy reasons....
135 */
136 res = (uint64_t)offset;
137 offl = res & 0xfffffffful;
138 offh = (res >> 32) & 0xfffffffful;
139 err = host_llseek(hf->hf_fd, offh, offl, &res, whence);
140 /*
141 * Since we're interfacing to the raw linux system call, we have to
142 * carefully check. We have to translate the errno value from the host
143 * to libsa's conventions.
144 */
145 if (is_linux_error(err)) {
146 errno = host_to_stand_errno(err);
147 return (-1);
148 }
149 return (res);
150 }
151
152 static int
hostfs_stat(struct open_file * f,struct stat * sb)153 hostfs_stat(struct open_file *f, struct stat *sb)
154 {
155 struct host_kstat ksb;
156 hostfs_file *hf = f->f_fsdata;
157
158 if (host_fstat(hf->hf_fd, &ksb) < 0)
159 return (EINVAL);
160 /*
161 * Translate Linux stat info to lib stand's notion (which uses FreeBSD's
162 * stat structure, missing fields are zero and commented below).
163 */
164 memset(sb, 0, sizeof(*sb));
165 sb->st_dev = ksb.st_dev;
166 sb->st_ino = ksb.st_ino;
167 sb->st_nlink = ksb.st_nlink;
168 sb->st_mode = ksb.st_mode;
169 sb->st_uid = ksb.st_uid;
170 sb->st_gid = ksb.st_gid;
171 sb->st_rdev = ksb.st_rdev;
172 /* No st_?time_ext on i386 */
173 sb->st_atim.tv_sec = ksb.st_atime_sec;
174 sb->st_atim.tv_nsec = ksb.st_atime_nsec;
175 sb->st_mtim.tv_sec = ksb.st_mtime_sec;
176 sb->st_mtim.tv_nsec = ksb.st_mtime_nsec;
177 sb->st_ctim.tv_sec = ksb.st_ctime_sec;
178 sb->st_ctim.tv_nsec = ksb.st_ctime_nsec;
179 /* No st_birthtim */
180 sb->st_size = ksb.st_size;
181 sb->st_blocks = ksb.st_blocks;
182 sb->st_blksize = ksb.st_blksize;
183 /* no st_flags */
184 /* no st_get */
185
186 return (0);
187 }
188
189 static int
hostfs_readdir(struct open_file * f,struct dirent * d)190 hostfs_readdir(struct open_file *f, struct dirent *d)
191 {
192 hostfs_file *hf = f->f_fsdata;
193 int dentlen;
194 struct host_dirent64 *dent;
195
196 if (hf->hf_curdent == NULL) {
197 dentlen = host_getdents64(hf->hf_fd, hf->hf_dents, sizeof(hf->hf_dents));
198 if (dentlen <= 0)
199 return (EINVAL);
200 hf->hf_dentlen = dentlen;
201 hf->hf_curdent = hf->hf_dents;
202 }
203 dent = (struct host_dirent64 *)hf->hf_curdent;
204 d->d_fileno = dent->d_ino;
205 d->d_type = dent->d_type; /* HOST_DT_XXXX == DX_XXXX for all values */
206 strlcpy(d->d_name, dent->d_name, sizeof(d->d_name)); /* d_name is NUL terminated */
207 d->d_namlen = strlen(d->d_name);
208 hf->hf_curdent += dent->d_reclen;
209 if (hf->hf_curdent >= hf->hf_dents + hf->hf_dentlen) {
210 hf->hf_curdent = NULL;
211 hf->hf_dentlen = 0;
212 }
213
214 return (0);
215 }
216
217 struct fs_ops hostfs_fsops = {
218 .fs_name = "host",
219 .fo_open = hostfs_open,
220 .fo_close = hostfs_close,
221 .fo_read = hostfs_read,
222 .fo_write = null_write,
223 .fo_seek = hostfs_seek,
224 .fo_stat = hostfs_stat,
225 .fo_readdir = hostfs_readdir
226 };
227
228 /*
229 * Generic "null" host device. This goes hand and hand with the host fs object
230 *
231 * XXXX This and userboot for bhyve both use exactly the same code, modulo some
232 * formatting nits. Make them common. We mostly use it to 'gate' the open of the
233 * filesystem to only this device.
234 */
235
236 static int
host_dev_init(void)237 host_dev_init(void)
238 {
239 return (0);
240 }
241
242 static int
host_dev_print(int verbose)243 host_dev_print(int verbose)
244 {
245 char line[80];
246
247 printf("%s devices:", host_dev.dv_name);
248 if (pager_output("\n") != 0)
249 return (1);
250
251 snprintf(line, sizeof(line), " host%d: Host filesystem\n", 0);
252 return (pager_output(line));
253 }
254
255 /*
256 * 'Open' the host device.
257 */
258 static int
host_dev_open(struct open_file * f,...)259 host_dev_open(struct open_file *f, ...)
260 {
261 return (0);
262 }
263
264 static int
host_dev_close(struct open_file * f)265 host_dev_close(struct open_file *f)
266 {
267 return (0);
268 }
269
270 static int
host_dev_strategy(void * devdata,int rw,daddr_t dblk,size_t size,char * buf,size_t * rsize)271 host_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
272 char *buf, size_t *rsize)
273 {
274 return (ENOSYS);
275 }
276
277 struct devsw host_dev = {
278 .dv_name = "host",
279 .dv_type = DEVT_NET,
280 .dv_init = host_dev_init,
281 .dv_strategy = host_dev_strategy,
282 .dv_open = host_dev_open,
283 .dv_close = host_dev_close,
284 .dv_ioctl = noioctl,
285 .dv_print = host_dev_print,
286 .dv_cleanup = NULL
287 };
288