1 /******************************************************************************
2 *
3 * hypfs.c
4 *
5 * Simple sysfs-like file system for the hypervisor.
6 */
7
8 #include <xen/err.h>
9 #include <xen/guest_access.h>
10 #include <xen/hypercall.h>
11 #include <xen/hypfs.h>
12 #include <xen/lib.h>
13 #include <xen/param.h>
14 #include <xen/rwlock.h>
15 #include <public/hypfs.h>
16
17 #ifdef CONFIG_COMPAT
18 #include <compat/hypfs.h>
19 CHECK_hypfs_dirlistentry;
20 #endif
21
22 #define DIRENTRY_NAME_OFF offsetof(struct xen_hypfs_dirlistentry, name)
23 #define DIRENTRY_SIZE(name_len) \
24 (DIRENTRY_NAME_OFF + \
25 ROUNDUP((name_len) + 1, alignof(struct xen_hypfs_direntry)))
26
27 static DEFINE_RWLOCK(hypfs_lock);
28 enum hypfs_lock_state {
29 hypfs_unlocked,
30 hypfs_read_locked,
31 hypfs_write_locked
32 };
33 static DEFINE_PER_CPU(enum hypfs_lock_state, hypfs_locked);
34
35 HYPFS_DIR_INIT(hypfs_root, "");
36
hypfs_read_lock(void)37 static void hypfs_read_lock(void)
38 {
39 ASSERT(this_cpu(hypfs_locked) != hypfs_write_locked);
40
41 read_lock(&hypfs_lock);
42 this_cpu(hypfs_locked) = hypfs_read_locked;
43 }
44
hypfs_write_lock(void)45 static void hypfs_write_lock(void)
46 {
47 ASSERT(this_cpu(hypfs_locked) == hypfs_unlocked);
48
49 write_lock(&hypfs_lock);
50 this_cpu(hypfs_locked) = hypfs_write_locked;
51 }
52
hypfs_unlock(void)53 static void hypfs_unlock(void)
54 {
55 enum hypfs_lock_state locked = this_cpu(hypfs_locked);
56
57 this_cpu(hypfs_locked) = hypfs_unlocked;
58
59 switch ( locked )
60 {
61 case hypfs_read_locked:
62 read_unlock(&hypfs_lock);
63 break;
64 case hypfs_write_locked:
65 write_unlock(&hypfs_lock);
66 break;
67 default:
68 BUG();
69 }
70 }
71
add_entry(struct hypfs_entry_dir * parent,struct hypfs_entry * new)72 static int add_entry(struct hypfs_entry_dir *parent, struct hypfs_entry *new)
73 {
74 int ret = -ENOENT;
75 struct hypfs_entry *e;
76
77 hypfs_write_lock();
78
79 list_for_each_entry ( e, &parent->dirlist, list )
80 {
81 int cmp = strcmp(e->name, new->name);
82
83 if ( cmp > 0 )
84 {
85 ret = 0;
86 list_add_tail(&new->list, &e->list);
87 break;
88 }
89 if ( cmp == 0 )
90 {
91 ret = -EEXIST;
92 break;
93 }
94 }
95
96 if ( ret == -ENOENT )
97 {
98 ret = 0;
99 list_add_tail(&new->list, &parent->dirlist);
100 }
101
102 if ( !ret )
103 {
104 unsigned int sz = strlen(new->name);
105
106 parent->e.size += DIRENTRY_SIZE(sz);
107 }
108
109 hypfs_unlock();
110
111 return ret;
112 }
113
hypfs_add_dir(struct hypfs_entry_dir * parent,struct hypfs_entry_dir * dir,bool nofault)114 int hypfs_add_dir(struct hypfs_entry_dir *parent,
115 struct hypfs_entry_dir *dir, bool nofault)
116 {
117 int ret;
118
119 ret = add_entry(parent, &dir->e);
120 BUG_ON(nofault && ret);
121
122 return ret;
123 }
124
hypfs_add_leaf(struct hypfs_entry_dir * parent,struct hypfs_entry_leaf * leaf,bool nofault)125 int hypfs_add_leaf(struct hypfs_entry_dir *parent,
126 struct hypfs_entry_leaf *leaf, bool nofault)
127 {
128 int ret;
129
130 if ( !leaf->u.content )
131 ret = -EINVAL;
132 else
133 ret = add_entry(parent, &leaf->e);
134 BUG_ON(nofault && ret);
135
136 return ret;
137 }
138
hypfs_get_path_user(char * buf,XEN_GUEST_HANDLE_PARAM (const_char)uaddr,unsigned long ulen)139 static int hypfs_get_path_user(char *buf,
140 XEN_GUEST_HANDLE_PARAM(const_char) uaddr,
141 unsigned long ulen)
142 {
143 if ( ulen > XEN_HYPFS_MAX_PATHLEN )
144 return -EINVAL;
145
146 if ( copy_from_guest(buf, uaddr, ulen) )
147 return -EFAULT;
148
149 if ( memchr(buf, 0, ulen) != buf + ulen - 1 )
150 return -EINVAL;
151
152 return 0;
153 }
154
hypfs_get_entry_rel(struct hypfs_entry_dir * dir,const char * path)155 static struct hypfs_entry *hypfs_get_entry_rel(struct hypfs_entry_dir *dir,
156 const char *path)
157 {
158 const char *end;
159 struct hypfs_entry *entry;
160 unsigned int name_len;
161 bool again = true;
162
163 while ( again )
164 {
165 if ( dir->e.type != XEN_HYPFS_TYPE_DIR )
166 return NULL;
167
168 if ( !*path )
169 return &dir->e;
170
171 end = strchr(path, '/');
172 if ( !end )
173 end = strchr(path, '\0');
174 name_len = end - path;
175
176 again = false;
177
178 list_for_each_entry ( entry, &dir->dirlist, list )
179 {
180 int cmp = strncmp(path, entry->name, name_len);
181 struct hypfs_entry_dir *d = container_of(entry,
182 struct hypfs_entry_dir, e);
183
184 if ( cmp < 0 )
185 return NULL;
186 if ( !cmp && strlen(entry->name) == name_len )
187 {
188 if ( !*end )
189 return entry;
190
191 again = true;
192 dir = d;
193 path = end + 1;
194
195 break;
196 }
197 }
198 }
199
200 return NULL;
201 }
202
hypfs_get_entry(const char * path)203 static struct hypfs_entry *hypfs_get_entry(const char *path)
204 {
205 if ( path[0] != '/' )
206 return NULL;
207
208 return hypfs_get_entry_rel(&hypfs_root, path + 1);
209 }
210
hypfs_read_dir(const struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr)211 int hypfs_read_dir(const struct hypfs_entry *entry,
212 XEN_GUEST_HANDLE_PARAM(void) uaddr)
213 {
214 const struct hypfs_entry_dir *d;
215 const struct hypfs_entry *e;
216 unsigned int size = entry->size;
217
218 ASSERT(this_cpu(hypfs_locked) != hypfs_unlocked);
219
220 d = container_of(entry, const struct hypfs_entry_dir, e);
221
222 list_for_each_entry ( e, &d->dirlist, list )
223 {
224 struct xen_hypfs_dirlistentry direntry;
225 unsigned int e_namelen = strlen(e->name);
226 unsigned int e_len = DIRENTRY_SIZE(e_namelen);
227
228 direntry.e.pad = 0;
229 direntry.e.type = e->type;
230 direntry.e.encoding = e->encoding;
231 direntry.e.content_len = e->size;
232 direntry.e.max_write_len = e->max_size;
233 direntry.off_next = list_is_last(&e->list, &d->dirlist) ? 0 : e_len;
234 if ( copy_to_guest(uaddr, &direntry, 1) )
235 return -EFAULT;
236
237 if ( copy_to_guest_offset(uaddr, DIRENTRY_NAME_OFF,
238 e->name, e_namelen + 1) )
239 return -EFAULT;
240
241 guest_handle_add_offset(uaddr, e_len);
242
243 ASSERT(e_len <= size);
244 size -= e_len;
245 }
246
247 return 0;
248 }
249
hypfs_read_leaf(const struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr)250 int hypfs_read_leaf(const struct hypfs_entry *entry,
251 XEN_GUEST_HANDLE_PARAM(void) uaddr)
252 {
253 const struct hypfs_entry_leaf *l;
254
255 ASSERT(this_cpu(hypfs_locked) != hypfs_unlocked);
256
257 l = container_of(entry, const struct hypfs_entry_leaf, e);
258
259 return copy_to_guest(uaddr, l->u.content, entry->size) ? -EFAULT: 0;
260 }
261
hypfs_read(const struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned long ulen)262 static int hypfs_read(const struct hypfs_entry *entry,
263 XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned long ulen)
264 {
265 struct xen_hypfs_direntry e;
266 long ret = -EINVAL;
267
268 if ( ulen < sizeof(e) )
269 goto out;
270
271 e.pad = 0;
272 e.type = entry->type;
273 e.encoding = entry->encoding;
274 e.content_len = entry->size;
275 e.max_write_len = entry->max_size;
276
277 ret = -EFAULT;
278 if ( copy_to_guest(uaddr, &e, 1) )
279 goto out;
280
281 ret = -ENOBUFS;
282 if ( ulen < entry->size + sizeof(e) )
283 goto out;
284
285 guest_handle_add_offset(uaddr, sizeof(e));
286
287 ret = entry->read(entry, uaddr);
288
289 out:
290 return ret;
291 }
292
hypfs_write_leaf(struct hypfs_entry_leaf * leaf,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned int ulen)293 int hypfs_write_leaf(struct hypfs_entry_leaf *leaf,
294 XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned int ulen)
295 {
296 char *buf;
297 int ret;
298
299 ASSERT(this_cpu(hypfs_locked) == hypfs_write_locked);
300
301 if ( ulen > leaf->e.max_size )
302 return -ENOSPC;
303
304 if ( leaf->e.type != XEN_HYPFS_TYPE_STRING &&
305 leaf->e.type != XEN_HYPFS_TYPE_BLOB && ulen != leaf->e.size )
306 return -EDOM;
307
308 buf = xmalloc_array(char, ulen);
309 if ( !buf )
310 return -ENOMEM;
311
312 ret = -EFAULT;
313 if ( copy_from_guest(buf, uaddr, ulen) )
314 goto out;
315
316 ret = -EINVAL;
317 if ( leaf->e.type == XEN_HYPFS_TYPE_STRING &&
318 leaf->e.encoding == XEN_HYPFS_ENC_PLAIN &&
319 memchr(buf, 0, ulen) != (buf + ulen - 1) )
320 goto out;
321
322 ret = 0;
323 memcpy(leaf->u.write_ptr, buf, ulen);
324 leaf->e.size = ulen;
325
326 out:
327 xfree(buf);
328 return ret;
329 }
330
hypfs_write_bool(struct hypfs_entry_leaf * leaf,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned int ulen)331 int hypfs_write_bool(struct hypfs_entry_leaf *leaf,
332 XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned int ulen)
333 {
334 bool buf;
335
336 ASSERT(this_cpu(hypfs_locked) == hypfs_write_locked);
337 ASSERT(leaf->e.type == XEN_HYPFS_TYPE_BOOL &&
338 leaf->e.size == sizeof(bool) &&
339 leaf->e.max_size == sizeof(bool) );
340
341 if ( ulen != leaf->e.max_size )
342 return -EDOM;
343
344 if ( copy_from_guest(&buf, uaddr, ulen) )
345 return -EFAULT;
346
347 *(bool *)leaf->u.write_ptr = buf;
348
349 return 0;
350 }
351
hypfs_write_custom(struct hypfs_entry_leaf * leaf,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned int ulen)352 int hypfs_write_custom(struct hypfs_entry_leaf *leaf,
353 XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned int ulen)
354 {
355 struct param_hypfs *p;
356 char *buf;
357 int ret;
358
359 ASSERT(this_cpu(hypfs_locked) == hypfs_write_locked);
360
361 /* Avoid oversized buffer allocation. */
362 if ( ulen > MAX_PARAM_SIZE )
363 return -ENOSPC;
364
365 buf = xzalloc_array(char, ulen);
366 if ( !buf )
367 return -ENOMEM;
368
369 ret = -EFAULT;
370 if ( copy_from_guest(buf, uaddr, ulen) )
371 goto out;
372
373 ret = -EDOM;
374 if ( memchr(buf, 0, ulen) != (buf + ulen - 1) )
375 goto out;
376
377 p = container_of(leaf, struct param_hypfs, hypfs);
378 ret = p->func(buf);
379
380 out:
381 xfree(buf);
382 return ret;
383 }
384
hypfs_write(struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned long ulen)385 static int hypfs_write(struct hypfs_entry *entry,
386 XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned long ulen)
387 {
388 struct hypfs_entry_leaf *l;
389
390 if ( !entry->write )
391 return -EACCES;
392
393 ASSERT(entry->max_size);
394
395 l = container_of(entry, struct hypfs_entry_leaf, e);
396
397 return entry->write(l, uaddr, ulen);
398 }
399
do_hypfs_op(unsigned int cmd,XEN_GUEST_HANDLE_PARAM (const_char)arg1,unsigned long arg2,XEN_GUEST_HANDLE_PARAM (void)arg3,unsigned long arg4)400 long do_hypfs_op(unsigned int cmd,
401 XEN_GUEST_HANDLE_PARAM(const_char) arg1, unsigned long arg2,
402 XEN_GUEST_HANDLE_PARAM(void) arg3, unsigned long arg4)
403 {
404 int ret;
405 struct hypfs_entry *entry;
406 static char path[XEN_HYPFS_MAX_PATHLEN];
407
408 if ( xsm_hypfs_op(XSM_PRIV) )
409 return -EPERM;
410
411 if ( cmd == XEN_HYPFS_OP_get_version )
412 {
413 if ( !guest_handle_is_null(arg1) || arg2 ||
414 !guest_handle_is_null(arg3) || arg4 )
415 return -EINVAL;
416
417 return XEN_HYPFS_VERSION;
418 }
419
420 if ( cmd == XEN_HYPFS_OP_write_contents )
421 hypfs_write_lock();
422 else
423 hypfs_read_lock();
424
425 ret = hypfs_get_path_user(path, arg1, arg2);
426 if ( ret )
427 goto out;
428
429 entry = hypfs_get_entry(path);
430 if ( !entry )
431 {
432 ret = -ENOENT;
433 goto out;
434 }
435
436 switch ( cmd )
437 {
438 case XEN_HYPFS_OP_read:
439 ret = hypfs_read(entry, arg3, arg4);
440 break;
441
442 case XEN_HYPFS_OP_write_contents:
443 ret = hypfs_write(entry, arg3, arg4);
444 break;
445
446 default:
447 ret = -EOPNOTSUPP;
448 break;
449 }
450
451 out:
452 hypfs_unlock();
453
454 return ret;
455 }
456