1/*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source.  A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12/*
13 * Copyright (c) 2015 Joyent, Inc.  All rights reserved.
14 */
15
16/*
17 * This file takes care of reading the boot time modules and constructing them
18 * into the appropriate series of vnodes.
19 */
20
21#include <sys/conf.h>
22#include <sys/ddi.h>
23#include <sys/sunddi.h>
24#include <sys/vfs.h>
25#include <sys/sysmacros.h>
26#include <sys/stat.h>
27
28#include <sys/fs/bootfs_impl.h>
29
30kmem_cache_t *bootfs_node_cache;
31
32static const vattr_t bootfs_vattr_dir = {
33	AT_ALL,					/* va_mask */
34	VDIR,					/* va_type */
35	S_IFDIR | 0555,				/* va_mode */
36	0,					/* va_uid */
37	0,					/* va_gid */
38	0,					/* va_fsid */
39	0,					/* va_nodeid */
40	1,					/* va_nlink */
41	0,					/* va_size */
42	0,					/* va_atime */
43	0,					/* va_mtime */
44	0,					/* va_ctime */
45	0,					/* va_rdev */
46	0,					/* va_blksize */
47	0,					/* va_nblocks */
48	0					/* va_seq */
49};
50
51static const vattr_t bootfs_vattr_reg = {
52	AT_ALL,					/* va_mask */
53	VREG,					/* va_type */
54	S_IFREG | 0555,				/* va_mode */
55	0,					/* va_uid */
56	0,					/* va_gid */
57	0,					/* va_fsid */
58	0,					/* va_nodeid */
59	1,					/* va_nlink */
60	0,					/* va_size */
61	0,					/* va_atime */
62	0,					/* va_mtime */
63	0,					/* va_ctime */
64	0,					/* va_rdev */
65	0,					/* va_blksize */
66	0,					/* va_nblocks */
67	0					/* va_seq */
68};
69
70/*ARGSUSED*/
71int
72bootfs_node_constructor(void *buf, void *arg, int kmflags)
73{
74	bootfs_node_t *bnp = buf;
75
76	bnp->bvn_vnp = vn_alloc(kmflags);
77	if (bnp->bvn_vnp == NULL)
78		return (-1);
79
80	return (0);
81}
82
83/*ARGSUSED*/
84void
85bootfs_node_destructor(void *buf, void *arg)
86{
87	bootfs_node_t *bnp = buf;
88
89	vn_free(bnp->bvn_vnp);
90}
91
92static int
93bootfs_comparator(const void *a, const void *b)
94{
95	const bootfs_node_t *lfs, *rfs;
96	int ret;
97
98	lfs = a;
99	rfs = b;
100
101	ret = strcmp(lfs->bvn_name, rfs->bvn_name);
102	if (ret > 0)
103		ret = 1;
104	if (ret < 0)
105		ret = -1;
106	return (ret);
107}
108
109static void
110bootfs_node_init(bootfs_t *bfs, bootfs_node_t *bnp, const struct vattr *vap,
111    const char *name, size_t namelen)
112{
113	timestruc_t now;
114
115	vn_reinit(bnp->bvn_vnp);
116
117	bnp->bvn_vnp->v_flag |= VNOSWAP;
118	bnp->bvn_vnp->v_type = vap->va_type;
119	bnp->bvn_vnp->v_vfsp = bfs->bfs_vfsp;
120	bnp->bvn_vnp->v_rdev = 0;
121	bnp->bvn_vnp->v_data = (caddr_t)bnp;
122	vn_setops(bnp->bvn_vnp, bootfs_vnodeops);
123
124	bnp->bvn_name = kmem_alloc(namelen + 1, KM_SLEEP);
125	bcopy(name, bnp->bvn_name, namelen);
126	bnp->bvn_name[namelen] = '\0';
127	if (vap->va_type == VDIR) {
128		avl_create(&bnp->bvn_dir, bootfs_comparator,
129		    sizeof (bootfs_node_t),
130		    offsetof(bootfs_node_t, bvn_link));
131	}
132	bzero(&bnp->bvn_link, sizeof (avl_node_t));
133	bcopy(vap, &bnp->bvn_attr, sizeof (vattr_t));
134
135	gethrestime(&now);
136	bnp->bvn_attr.va_atime = now;
137	bnp->bvn_attr.va_ctime = now;
138	bnp->bvn_attr.va_mtime = now;
139	bnp->bvn_attr.va_fsid = makedevice(bootfs_major, bfs->bfs_minor);
140	bnp->bvn_attr.va_nodeid = bfs->bfs_ninode;
141	bnp->bvn_attr.va_blksize = PAGESIZE;
142	bfs->bfs_ninode++;
143	list_insert_tail(&bfs->bfs_nodes, bnp);
144}
145
146static void
147bootfs_mkroot(bootfs_t *bfs)
148{
149	bootfs_node_t *bnp;
150
151	bnp = kmem_cache_alloc(bootfs_node_cache, KM_SLEEP);
152	bootfs_node_init(bfs, bnp, &bootfs_vattr_dir, "/", 1);
153	bnp->bvn_vnp->v_flag |= VROOT;
154	bnp->bvn_parent = bnp;
155	bfs->bfs_rootvn = bnp;
156	bfs->bfs_stat.bfss_ndirs.value.ui32++;
157	vn_exists(bnp->bvn_vnp);
158}
159
160static int
161bootfs_mknode(bootfs_t *bfs, bootfs_node_t *parent, bootfs_node_t **outp,
162    const char *name, size_t namelen, const vattr_t *vap, uintptr_t addr,
163    uint64_t size)
164{
165	bootfs_node_t *bnp;
166	bootfs_node_t sn;
167	avl_index_t where;
168	char *buf;
169
170	ASSERT(parent->bvn_attr.va_type == VDIR);
171	buf = kmem_alloc(namelen + 1, KM_SLEEP);
172	bcopy(name, buf, namelen);
173	buf[namelen] = '\0';
174	sn.bvn_name = buf;
175	if ((bnp = avl_find(&parent->bvn_dir, &sn, &where)) != NULL) {
176		kmem_free(buf, namelen + 1);
177		/* Directories can collide, files cannot */
178		if (vap->va_type == VDIR) {
179			*outp = bnp;
180			return (0);
181		}
182		return (EEXIST);
183	}
184	kmem_free(buf, namelen + 1);
185
186	bnp = kmem_cache_alloc(bootfs_node_cache, KM_SLEEP);
187	bootfs_node_init(bfs, bnp, vap, name, namelen);
188	bnp->bvn_parent = parent;
189	avl_add(&parent->bvn_dir, bnp);
190	*outp = bnp;
191
192	if (vap->va_type == VDIR) {
193		parent->bvn_attr.va_size++;
194		parent->bvn_attr.va_nlink++;
195		bfs->bfs_stat.bfss_ndirs.value.ui32++;
196	} else {
197		bnp->bvn_addr = addr;
198		bnp->bvn_size = size;
199		bfs->bfs_stat.bfss_nfiles.value.ui32++;
200		bfs->bfs_stat.bfss_nbytes.value.ui64 += size;
201		bnp->bvn_attr.va_nblocks = P2ROUNDUP(size, 512) >> 9;
202		bnp->bvn_attr.va_size = size;
203	}
204
205	vn_exists(bnp->bvn_vnp);
206
207	return (0);
208}
209
210/*
211 * Given the address, size, and path a boot-time module would like, go through
212 * and create all of the directory entries that are required and then the file
213 * itself. If someone has passed in a module that has the same name as another
214 * one, we honor the first one.
215 */
216static int
217bootfs_construct_entry(bootfs_t *bfs, uintptr_t addr, uint64_t size,
218    const char *mname)
219{
220	char *sp;
221	size_t nlen;
222	int ret;
223	bootfs_node_t *nbnp;
224
225	const char *p = mname;
226	bootfs_node_t *bnp = bfs->bfs_rootvn;
227
228	if (*p == '\0')
229		return (EINVAL);
230
231	for (;;) {
232		/* First eliminate all leading / characters. */
233		while (*p == '/')
234			p++;
235
236		/* A name with all slashes or ending in a / */
237		if (*p == '\0')
238			return (EINVAL);
239
240		sp = strchr(p, '/');
241		if (sp == NULL)
242			break;
243		nlen = (ptrdiff_t)sp - (ptrdiff_t)p;
244		if (strncmp(p, ".", nlen) == 0) {
245			p = sp + 1;
246			continue;
247		}
248
249		if (strncmp(p, "..", nlen) == 0) {
250			bnp = bnp->bvn_parent;
251			p = sp + 1;
252			continue;
253		}
254
255		VERIFY(bootfs_mknode(bfs, bnp, &nbnp, p, nlen,
256		    &bootfs_vattr_dir, addr, size) == 0);
257		p = sp + 1;
258		bnp = nbnp;
259	}
260
261	nlen = strlen(p);
262	ret = bootfs_mknode(bfs, bnp, &nbnp, p, nlen, &bootfs_vattr_reg,
263	    addr, size);
264	if (ret != 0)
265		return (ret);
266
267	return (0);
268}
269
270/*
271 * We're going to go through every boot time module and construct the
272 * appropriate vnodes for them now. Because there are very few of these that
273 * exist, generally on the order of a handful, we're going to create them all
274 * when the file system is initialized and then tear them all down when the
275 * module gets unloaded.
276 *
277 * The information about the modules is contained in properties on the root of
278 * the devinfo tree. Specifically there are three properties per module:
279 *
280 *   - module-size-%d	int64_t size, in bytes, of the boot time module.
281 *   - module-addr-%d	The address of the boot time module
282 *   - module-name-%d	The string name of the boot time module
283 *
284 * Note that the module-size and module-addr fields are always 64-bit values
285 * regardless of being on a 32-bit or 64-bit kernel. module-name is a string
286 * property.
287 *
288 * There is no property that indicates the total number of such modules. Modules
289 * start at 0 and work their way up incrementally. The first time we can't find
290 * a module or a property, then we stop.
291 */
292void
293bootfs_construct(bootfs_t *bfs)
294{
295	uint_t id = 0;
296	char paddr[64], psize[64], pname[64], *mname;
297	dev_info_t *root;
298	uint64_t size, addr;
299	int ret;
300
301	bootfs_mkroot(bfs);
302	root = ddi_root_node();
303
304	for (;;) {
305		if (id == UINT32_MAX)
306			break;
307
308		if (snprintf(paddr, sizeof (paddr), "module-addr-%d", id) >
309		    sizeof (paddr))
310			break;
311
312		if (snprintf(psize, sizeof (paddr), "module-size-%d", id) >
313		    sizeof (paddr))
314			break;
315
316		if (snprintf(pname, sizeof (paddr), "module-name-%d", id) >
317		    sizeof (paddr))
318			break;
319
320		addr = (uint64_t)ddi_prop_get_int64(DDI_DEV_T_ANY, root,
321		    DDI_PROP_DONTPASS, paddr, 0);
322		if (addr == 0 || addr == DDI_PROP_NOT_FOUND)
323			break;
324
325		size = (uint64_t)ddi_prop_get_int64(DDI_DEV_T_ANY, root,
326		    DDI_PROP_DONTPASS, psize, 0);
327		if (size == 0 || size == DDI_PROP_NOT_FOUND)
328			break;
329
330		if (ddi_prop_lookup_string(DDI_DEV_T_ANY, root,
331		    DDI_PROP_DONTPASS, pname, &mname) != DDI_PROP_SUCCESS)
332			break;
333
334		ret = bootfs_construct_entry(bfs, addr, size, mname);
335		if (ret == EINVAL)
336			bfs->bfs_stat.bfss_ndiscards.value.ui32++;
337		if (ret == EEXIST)
338			bfs->bfs_stat.bfss_ndups.value.ui32++;
339		ddi_prop_free(mname);
340
341		id++;
342	}
343}
344
345void
346bootfs_destruct(bootfs_t *bfs)
347{
348	bootfs_node_t *bnp;
349
350	while ((bnp = list_remove_head(&bfs->bfs_nodes)) != NULL) {
351		ASSERT(bnp->bvn_vnp->v_count == 1);
352		VN_RELE(bnp->bvn_vnp);
353		kmem_free(bnp->bvn_name, strlen(bnp->bvn_name) + 1);
354		kmem_cache_free(bootfs_node_cache, bnp);
355	}
356}
357