xref: /freebsd/stand/kboot/kboot/hostfs.c (revision 1edb7116)
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 *
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
44 hostfs_free(hostfs_file *hf)
45 {
46 	free(hf);
47 }
48 
49 static int
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
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
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
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 	int 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 	offl = offset & 0xffffffff;
137 	offh = (offset >> 32) & 0xffffffff;
138 	err = host_llseek(hf->hf_fd, offh, offl, &res, whence);
139 	if (err < 0)
140 		return (err);
141 	return (res);
142 }
143 
144 static int
145 hostfs_stat(struct open_file *f, struct stat *sb)
146 {
147 	struct host_kstat ksb;
148 	hostfs_file *hf = f->f_fsdata;
149 
150 	if (host_fstat(hf->hf_fd, &ksb) < 0)
151 		return (EINVAL);
152 	/*
153 	 * Translate Linux stat info to lib stand's notion (which uses FreeBSD's
154 	 * stat structure, missing fields are zero and commented below).
155 	 */
156 	memset(sb, 0, sizeof(*sb));
157 	sb->st_dev		= ksb.st_dev;
158 	sb->st_ino		= ksb.st_ino;
159 	sb->st_nlink		= ksb.st_nlink;
160 	sb->st_mode		= ksb.st_mode;
161 	sb->st_uid		= ksb.st_uid;
162 	sb->st_gid		= ksb.st_gid;
163 	sb->st_rdev		= ksb.st_rdev;
164 	/* No st_?time_ext on i386 */
165 	sb->st_atim.tv_sec	= ksb.st_atime_sec;
166 	sb->st_atim.tv_nsec	= ksb.st_atime_nsec;
167 	sb->st_mtim.tv_sec	= ksb.st_mtime_sec;
168 	sb->st_mtim.tv_nsec	= ksb.st_mtime_nsec;
169 	sb->st_ctim.tv_sec	= ksb.st_ctime_sec;
170 	sb->st_ctim.tv_nsec	= ksb.st_ctime_nsec;
171 	/* No st_birthtim */
172 	sb->st_size		= ksb.st_size;
173 	sb->st_blocks		= ksb.st_blocks;
174 	sb->st_blksize		= ksb.st_blksize;
175 	/* no st_flags */
176 	/* no st_get */
177 
178 	return (0);
179 }
180 
181 static int
182 hostfs_readdir(struct open_file *f, struct dirent *d)
183 {
184 	hostfs_file *hf = f->f_fsdata;
185 	int dentlen;
186 	struct host_dirent64 *dent;
187 
188 	if (hf->hf_curdent == NULL) {
189 		dentlen = host_getdents64(hf->hf_fd, hf->hf_dents, sizeof(hf->hf_dents));
190 		if (dentlen <= 0)
191 			return (EINVAL);
192 		hf->hf_dentlen = dentlen;
193 		hf->hf_curdent = hf->hf_dents;
194 	}
195 	dent = (struct host_dirent64 *)hf->hf_curdent;
196 	d->d_fileno = dent->d_ino;
197 	d->d_type = dent->d_type;	/* HOST_DT_XXXX == DX_XXXX for all values */
198 	strlcpy(d->d_name, dent->d_name, sizeof(d->d_name)); /* d_name is NUL terminated */
199 	d->d_namlen = strlen(d->d_name);
200 	hf->hf_curdent += dent->d_reclen;
201 	if (hf->hf_curdent >= hf->hf_dents + hf->hf_dentlen) {
202 		hf->hf_curdent = NULL;
203 		hf->hf_dentlen = 0;
204 	}
205 
206 	return (0);
207 }
208 
209 struct fs_ops hostfs_fsops = {
210 	.fs_name = "host",
211 	.fo_open = hostfs_open,
212 	.fo_close = hostfs_close,
213 	.fo_read = hostfs_read,
214 	.fo_write = null_write,
215 	.fo_seek = hostfs_seek,
216 	.fo_stat = hostfs_stat,
217 	.fo_readdir = hostfs_readdir
218 };
219 
220 /*
221  * Generic "null" host device. This goes hand and hand with the host fs object
222  *
223  * XXXX This and userboot for bhyve both use exactly the same code, modulo some
224  * formatting nits. Make them common. We mostly use it to 'gate' the open of the
225  * filesystem to only this device.
226  */
227 
228 static int
229 host_dev_init(void)
230 {
231 	return (0);
232 }
233 
234 static int
235 host_dev_print(int verbose)
236 {
237 	char line[80];
238 
239 	printf("%s devices:", host_dev.dv_name);
240 	if (pager_output("\n") != 0)
241 		return (1);
242 
243 	snprintf(line, sizeof(line), "    host%d:   Host filesystem\n", 0);
244 	return (pager_output(line));
245 }
246 
247 /*
248  * 'Open' the host device.
249  */
250 static int
251 host_dev_open(struct open_file *f, ...)
252 {
253 	return (0);
254 }
255 
256 static int
257 host_dev_close(struct open_file *f)
258 {
259 	return (0);
260 }
261 
262 static int
263 host_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
264     char *buf, size_t *rsize)
265 {
266 	return (ENOSYS);
267 }
268 
269 struct devsw host_dev = {
270 	.dv_name = "host",
271 	.dv_type = DEVT_NET,
272 	.dv_init = host_dev_init,
273 	.dv_strategy = host_dev_strategy,
274 	.dv_open = host_dev_open,
275 	.dv_close = host_dev_close,
276 	.dv_ioctl = noioctl,
277 	.dv_print = host_dev_print,
278 	.dv_cleanup = NULL
279 };
280