1 /*
2  * Copyright (c) 2019 SUSE Software Solutions Germany GmbH
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation;
7  * version 2.1 of the License.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #define __XEN_TOOLS__ 1
19 
20 #define _GNU_SOURCE
21 
22 #include <errno.h>
23 #include <inttypes.h>
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <zlib.h>
28 
29 #include <xentoollog.h>
30 #include <xenhypfs.h>
31 #include <xencall.h>
32 #include <xentoolcore_internal.h>
33 
34 #include <xen/xen.h>
35 #include <xen/hypfs.h>
36 
37 #define BUF_SIZE 4096
38 
39 struct xenhypfs_handle {
40     xentoollog_logger *logger, *logger_tofree;
41     unsigned int flags;
42     xencall_handle *xcall;
43 };
44 
xenhypfs_open(xentoollog_logger * logger,unsigned open_flags)45 xenhypfs_handle *xenhypfs_open(xentoollog_logger *logger,
46                                unsigned open_flags)
47 {
48     xenhypfs_handle *fshdl = calloc(1, sizeof(*fshdl));
49 
50     if (!fshdl)
51         return NULL;
52 
53     fshdl->flags = open_flags;
54     fshdl->logger = logger;
55     fshdl->logger_tofree = NULL;
56 
57     if (!fshdl->logger) {
58         fshdl->logger = fshdl->logger_tofree =
59             (xentoollog_logger*)
60             xtl_createlogger_stdiostream(stderr, XTL_PROGRESS, 0);
61         if (!fshdl->logger)
62             goto err;
63     }
64 
65     fshdl->xcall = xencall_open(fshdl->logger, 0);
66     if (!fshdl->xcall)
67         goto err;
68 
69     /* No need to remember supported version, we only support V1. */
70     if (xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op,
71                  XEN_HYPFS_OP_get_version, 0, 0, 0, 0) < 0)
72         goto err;
73 
74     return fshdl;
75 
76 err:
77     xencall_close(fshdl->xcall);
78     xtl_logger_destroy(fshdl->logger_tofree);
79     free(fshdl);
80     return NULL;
81 }
82 
xenhypfs_close(xenhypfs_handle * fshdl)83 int xenhypfs_close(xenhypfs_handle *fshdl)
84 {
85     if (!fshdl)
86         return 0;
87 
88     xencall_close(fshdl->xcall);
89     xtl_logger_destroy(fshdl->logger_tofree);
90     free(fshdl);
91     return 0;
92 }
93 
xenhypfs_get_pathbuf(xenhypfs_handle * fshdl,const char * path,char ** path_buf)94 static int xenhypfs_get_pathbuf(xenhypfs_handle *fshdl, const char *path,
95                                 char **path_buf)
96 {
97     int ret = -1;
98     int path_sz;
99 
100     if (!fshdl) {
101         errno = EBADF;
102         goto out;
103     }
104 
105     path_sz = strlen(path) + 1;
106     if (path_sz > XEN_HYPFS_MAX_PATHLEN)
107     {
108         errno = ENAMETOOLONG;
109         goto out;
110     }
111 
112     *path_buf = xencall_alloc_buffer(fshdl->xcall, path_sz);
113     if (!*path_buf) {
114         errno = ENOMEM;
115         goto out;
116     }
117     strcpy(*path_buf, path);
118 
119     ret = path_sz;
120 
121  out:
122     return ret;
123 }
124 
xenhypfs_inflate(void * in_data,size_t * sz)125 static void *xenhypfs_inflate(void *in_data, size_t *sz)
126 {
127     unsigned char *workbuf;
128     void *content = NULL;
129     unsigned int out_sz;
130     z_stream z = { .opaque = NULL };
131     int ret;
132 
133     workbuf = malloc(BUF_SIZE);
134     if (!workbuf)
135         return NULL;
136 
137     z.next_in = in_data;
138     z.avail_in = *sz;
139     ret = inflateInit2(&z, MAX_WBITS + 32); /* 32 == gzip */
140 
141     for (*sz = 0; ret == Z_OK; *sz += out_sz) {
142         z.next_out = workbuf;
143         z.avail_out = BUF_SIZE;
144         ret = inflate(&z, Z_SYNC_FLUSH);
145         if (ret != Z_OK && ret != Z_STREAM_END)
146             break;
147 
148         out_sz = z.next_out - workbuf;
149         content = realloc(content, *sz + out_sz);
150         if (!content) {
151             ret = Z_MEM_ERROR;
152             break;
153         }
154         memcpy(content + *sz, workbuf, out_sz);
155     }
156 
157     inflateEnd(&z);
158     if (ret != Z_STREAM_END) {
159         free(content);
160         content = NULL;
161         errno = EIO;
162     }
163     free(workbuf);
164     return content;
165 }
166 
xenhypfs_set_attrs(struct xen_hypfs_direntry * entry,struct xenhypfs_dirent * dirent)167 static void xenhypfs_set_attrs(struct xen_hypfs_direntry *entry,
168                                struct xenhypfs_dirent *dirent)
169 {
170     dirent->size = entry->content_len;
171 
172     switch(entry->type) {
173     case XEN_HYPFS_TYPE_DIR:
174         dirent->type = xenhypfs_type_dir;
175         break;
176     case XEN_HYPFS_TYPE_BLOB:
177         dirent->type = xenhypfs_type_blob;
178         break;
179     case XEN_HYPFS_TYPE_STRING:
180         dirent->type = xenhypfs_type_string;
181         break;
182     case XEN_HYPFS_TYPE_UINT:
183         dirent->type = xenhypfs_type_uint;
184         break;
185     case XEN_HYPFS_TYPE_INT:
186         dirent->type = xenhypfs_type_int;
187         break;
188     case XEN_HYPFS_TYPE_BOOL:
189         dirent->type = xenhypfs_type_bool;
190         break;
191     default:
192         dirent->type = xenhypfs_type_blob;
193     }
194 
195     switch (entry->encoding) {
196     case XEN_HYPFS_ENC_PLAIN:
197         dirent->encoding = xenhypfs_enc_plain;
198         break;
199     case XEN_HYPFS_ENC_GZIP:
200         dirent->encoding = xenhypfs_enc_gzip;
201         break;
202     default:
203         dirent->encoding = xenhypfs_enc_plain;
204         dirent->type = xenhypfs_type_blob;
205     }
206 
207     dirent->flags = entry->max_write_len ? XENHYPFS_FLAG_WRITABLE : 0;
208 }
209 
xenhypfs_read_raw(xenhypfs_handle * fshdl,const char * path,struct xenhypfs_dirent ** dirent)210 void *xenhypfs_read_raw(xenhypfs_handle *fshdl, const char *path,
211                         struct xenhypfs_dirent **dirent)
212 {
213     void *retbuf = NULL, *content = NULL;
214     char *path_buf = NULL;
215     const char *name;
216     struct xen_hypfs_direntry *entry;
217     int ret;
218     int sz, path_sz;
219 
220     *dirent = NULL;
221     ret = xenhypfs_get_pathbuf(fshdl, path, &path_buf);
222     if (ret < 0)
223         goto out;
224 
225     path_sz = ret;
226 
227     for (sz = BUF_SIZE;; sz = sizeof(*entry) + entry->content_len) {
228         if (retbuf)
229             xencall_free_buffer(fshdl->xcall, retbuf);
230 
231         retbuf = xencall_alloc_buffer(fshdl->xcall, sz);
232         if (!retbuf) {
233             errno = ENOMEM;
234             goto out;
235         }
236         entry = retbuf;
237 
238         ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_read,
239                        (unsigned long)path_buf, path_sz,
240                        (unsigned long)retbuf, sz);
241         if (!ret)
242             break;
243 
244         if (errno != ENOBUFS)
245             goto out;
246     }
247 
248     content = malloc(entry->content_len);
249     if (!content)
250         goto out;
251     memcpy(content, entry + 1, entry->content_len);
252 
253     name = strrchr(path, '/');
254     if (!name)
255         name = path;
256     else {
257         name++;
258         if (!*name)
259             name--;
260     }
261     *dirent = calloc(1, sizeof(struct xenhypfs_dirent) + strlen(name) + 1);
262     if (!*dirent) {
263         free(content);
264         content = NULL;
265         errno = ENOMEM;
266         goto out;
267     }
268     (*dirent)->name = (char *)(*dirent + 1);
269     strcpy((*dirent)->name, name);
270     xenhypfs_set_attrs(entry, *dirent);
271 
272  out:
273     ret = errno;
274     xencall_free_buffer(fshdl->xcall, path_buf);
275     xencall_free_buffer(fshdl->xcall, retbuf);
276     errno = ret;
277 
278     return content;
279 }
280 
xenhypfs_read(xenhypfs_handle * fshdl,const char * path)281 char *xenhypfs_read(xenhypfs_handle *fshdl, const char *path)
282 {
283     char *buf, *ret_buf = NULL;
284     struct xenhypfs_dirent *dirent;
285     int ret;
286 
287     buf = xenhypfs_read_raw(fshdl, path, &dirent);
288     if (!buf)
289         goto out;
290 
291     switch (dirent->encoding) {
292     case xenhypfs_enc_plain:
293         break;
294     case xenhypfs_enc_gzip:
295         ret_buf = xenhypfs_inflate(buf, &dirent->size);
296         if (!ret_buf)
297             goto out;
298         free(buf);
299         buf = ret_buf;
300         ret_buf = NULL;
301         break;
302     }
303 
304     switch (dirent->type) {
305     case xenhypfs_type_dir:
306         errno = EISDIR;
307         break;
308     case xenhypfs_type_blob:
309         errno = EDOM;
310         break;
311     case xenhypfs_type_string:
312         ret_buf = buf;
313         buf = NULL;
314         break;
315     case xenhypfs_type_uint:
316     case xenhypfs_type_bool:
317         switch (dirent->size) {
318         case 1:
319             ret = asprintf(&ret_buf, "%"PRIu8, *(uint8_t *)buf);
320             break;
321         case 2:
322             ret = asprintf(&ret_buf, "%"PRIu16, *(uint16_t *)buf);
323             break;
324         case 4:
325             ret = asprintf(&ret_buf, "%"PRIu32, *(uint32_t *)buf);
326             break;
327         case 8:
328             ret = asprintf(&ret_buf, "%"PRIu64, *(uint64_t *)buf);
329             break;
330         default:
331             ret = -1;
332             errno = EDOM;
333         }
334         if (ret < 0)
335             ret_buf = NULL;
336         break;
337     case xenhypfs_type_int:
338         switch (dirent->size) {
339         case 1:
340             ret = asprintf(&ret_buf, "%"PRId8, *(int8_t *)buf);
341             break;
342         case 2:
343             ret = asprintf(&ret_buf, "%"PRId16, *(int16_t *)buf);
344             break;
345         case 4:
346             ret = asprintf(&ret_buf, "%"PRId32, *(int32_t *)buf);
347             break;
348         case 8:
349             ret = asprintf(&ret_buf, "%"PRId64, *(int64_t *)buf);
350             break;
351         default:
352             ret = -1;
353             errno = EDOM;
354         }
355         if (ret < 0)
356             ret_buf = NULL;
357         break;
358     }
359 
360  out:
361     ret = errno;
362     free(buf);
363     free(dirent);
364     errno = ret;
365 
366     return ret_buf;
367 }
368 
xenhypfs_readdir(xenhypfs_handle * fshdl,const char * path,unsigned int * num_entries)369 struct xenhypfs_dirent *xenhypfs_readdir(xenhypfs_handle *fshdl,
370                                          const char *path,
371                                          unsigned int *num_entries)
372 {
373     void *buf, *curr;
374     int ret;
375     char *names;
376     struct xenhypfs_dirent *ret_buf = NULL, *dirent;
377     unsigned int n = 0, name_sz = 0;
378     struct xen_hypfs_dirlistentry *entry;
379 
380     buf = xenhypfs_read_raw(fshdl, path, &dirent);
381     if (!buf)
382         goto out;
383 
384     if (dirent->type != xenhypfs_type_dir ||
385         dirent->encoding != xenhypfs_enc_plain) {
386         errno = ENOTDIR;
387         goto out;
388     }
389 
390     if (dirent->size) {
391         curr = buf;
392         for (n = 1;; n++) {
393             entry = curr;
394             name_sz += strlen(entry->name) + 1;
395             if (!entry->off_next)
396                 break;
397 
398             curr += entry->off_next;
399         }
400     }
401 
402     ret_buf = malloc(n * sizeof(*ret_buf) + name_sz);
403     if (!ret_buf)
404         goto out;
405 
406     *num_entries = n;
407     names = (char *)(ret_buf + n);
408     curr = buf;
409     for (n = 0; n < *num_entries; n++) {
410         entry = curr;
411         xenhypfs_set_attrs(&entry->e, ret_buf + n);
412         ret_buf[n].name = names;
413         strcpy(names, entry->name);
414         names += strlen(entry->name) + 1;
415         curr += entry->off_next;
416     }
417 
418  out:
419     ret = errno;
420     free(buf);
421     free(dirent);
422     errno = ret;
423 
424     return ret_buf;
425 }
426 
xenhypfs_write(xenhypfs_handle * fshdl,const char * path,const char * val)427 int xenhypfs_write(xenhypfs_handle *fshdl, const char *path, const char *val)
428 {
429     void *buf = NULL;
430     char *path_buf = NULL, *val_end;
431     int ret, saved_errno;
432     int sz, path_sz;
433     struct xen_hypfs_direntry *entry;
434     uint64_t mask;
435 
436     ret = xenhypfs_get_pathbuf(fshdl, path, &path_buf);
437     if (ret < 0)
438         goto out;
439 
440     path_sz = ret;
441     ret = -1;
442 
443     sz = BUF_SIZE;
444     buf = xencall_alloc_buffer(fshdl->xcall, sz);
445     if (!buf) {
446         errno = ENOMEM;
447         goto out;
448     }
449 
450     ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_read,
451                    (unsigned long)path_buf, path_sz,
452                    (unsigned long)buf, sizeof(*entry));
453     if (ret && errno != ENOBUFS)
454         goto out;
455     ret = -1;
456     entry = buf;
457     if (!entry->max_write_len) {
458         errno = EACCES;
459         goto out;
460     }
461     if (entry->encoding != XEN_HYPFS_ENC_PLAIN) {
462         /* Writing compressed data currently not supported. */
463         errno = EDOM;
464         goto out;
465     }
466 
467     switch (entry->type) {
468     case XEN_HYPFS_TYPE_STRING:
469         if (sz < strlen(val) + 1) {
470             sz = strlen(val) + 1;
471             xencall_free_buffer(fshdl->xcall, buf);
472             buf = xencall_alloc_buffer(fshdl->xcall, sz);
473             if (!buf) {
474                 errno = ENOMEM;
475                 goto out;
476             }
477         }
478         sz = strlen(val) + 1;
479         strcpy(buf, val);
480         break;
481     case XEN_HYPFS_TYPE_UINT:
482         sz = entry->content_len;
483         errno = 0;
484         *(unsigned long long *)buf = strtoull(val, &val_end, 0);
485         if (errno || !*val || *val_end)
486             goto out;
487         mask = ~0ULL << (8 * sz);
488         if ((*(uint64_t *)buf & mask) && ((*(uint64_t *)buf & mask) != mask)) {
489             errno = ERANGE;
490             goto out;
491         }
492         break;
493     case XEN_HYPFS_TYPE_INT:
494         sz = entry->content_len;
495         errno = 0;
496         *(unsigned long long *)buf = strtoll(val, &val_end, 0);
497         if (errno || !*val || *val_end)
498             goto out;
499         mask = (sz == 8) ? 0 : ~0ULL << (8 * sz);
500         if ((*(uint64_t *)buf & mask) && ((*(uint64_t *)buf & mask) != mask)) {
501             errno = ERANGE;
502             goto out;
503         }
504         break;
505     case XEN_HYPFS_TYPE_BOOL:
506         sz = entry->content_len;
507         *(unsigned long long *)buf = 0;
508         if (!strcmp(val, "1") || !strcmp(val, "on") || !strcmp(val, "yes") ||
509             !strcmp(val, "true") || !strcmp(val, "enable"))
510             *(unsigned long long *)buf = 1;
511         else if (strcmp(val, "0") && strcmp(val, "no") && strcmp(val, "off") &&
512                  strcmp(val, "false") && strcmp(val, "disable")) {
513             errno = EDOM;
514             goto out;
515         }
516         break;
517     default:
518         /* No support for other types (yet). */
519         errno = EDOM;
520         goto out;
521     }
522 
523     ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op,
524                    XEN_HYPFS_OP_write_contents,
525                    (unsigned long)path_buf, path_sz,
526                    (unsigned long)buf, sz);
527 
528  out:
529     saved_errno = errno;
530     xencall_free_buffer(fshdl->xcall, path_buf);
531     xencall_free_buffer(fshdl->xcall, buf);
532     errno = saved_errno;
533     return ret;
534 }
535