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 + 1);
150         if (!content) {
151             ret = Z_MEM_ERROR;
152             break;
153         }
154         memcpy(content + *sz, workbuf, out_sz);
155         *(char *)(content + *sz + out_sz) = 0;
156     }
157 
158     inflateEnd(&z);
159     if (ret != Z_STREAM_END) {
160         free(content);
161         content = NULL;
162         errno = EIO;
163     }
164     free(workbuf);
165     return content;
166 }
167 
xenhypfs_set_attrs(struct xen_hypfs_direntry * entry,struct xenhypfs_dirent * dirent)168 static void xenhypfs_set_attrs(struct xen_hypfs_direntry *entry,
169                                struct xenhypfs_dirent *dirent)
170 {
171     dirent->size = entry->content_len;
172 
173     switch(entry->type) {
174     case XEN_HYPFS_TYPE_DIR:
175         dirent->type = xenhypfs_type_dir;
176         break;
177     case XEN_HYPFS_TYPE_BLOB:
178         dirent->type = xenhypfs_type_blob;
179         break;
180     case XEN_HYPFS_TYPE_STRING:
181         dirent->type = xenhypfs_type_string;
182         break;
183     case XEN_HYPFS_TYPE_UINT:
184         dirent->type = xenhypfs_type_uint;
185         break;
186     case XEN_HYPFS_TYPE_INT:
187         dirent->type = xenhypfs_type_int;
188         break;
189     case XEN_HYPFS_TYPE_BOOL:
190         dirent->type = xenhypfs_type_bool;
191         break;
192     default:
193         dirent->type = xenhypfs_type_blob;
194     }
195 
196     switch (entry->encoding) {
197     case XEN_HYPFS_ENC_PLAIN:
198         dirent->encoding = xenhypfs_enc_plain;
199         break;
200     case XEN_HYPFS_ENC_GZIP:
201         dirent->encoding = xenhypfs_enc_gzip;
202         break;
203     default:
204         dirent->encoding = xenhypfs_enc_plain;
205         dirent->type = xenhypfs_type_blob;
206     }
207 
208     dirent->flags = entry->max_write_len ? XENHYPFS_FLAG_WRITABLE : 0;
209 }
210 
xenhypfs_read_raw(xenhypfs_handle * fshdl,const char * path,struct xenhypfs_dirent ** dirent)211 void *xenhypfs_read_raw(xenhypfs_handle *fshdl, const char *path,
212                         struct xenhypfs_dirent **dirent)
213 {
214     void *retbuf = NULL, *content = NULL;
215     char *path_buf = NULL;
216     const char *name;
217     struct xen_hypfs_direntry *entry;
218     int ret;
219     int sz, path_sz;
220 
221     *dirent = NULL;
222     ret = xenhypfs_get_pathbuf(fshdl, path, &path_buf);
223     if (ret < 0)
224         goto out;
225 
226     path_sz = ret;
227 
228     for (sz = BUF_SIZE;; sz = sizeof(*entry) + entry->content_len) {
229         if (retbuf)
230             xencall_free_buffer(fshdl->xcall, retbuf);
231 
232         retbuf = xencall_alloc_buffer(fshdl->xcall, sz);
233         if (!retbuf) {
234             errno = ENOMEM;
235             goto out;
236         }
237         entry = retbuf;
238 
239         ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_read,
240                        (unsigned long)path_buf, path_sz,
241                        (unsigned long)retbuf, sz);
242         if (!ret)
243             break;
244 
245         if (errno != ENOBUFS)
246             goto out;
247     }
248 
249     content = malloc(entry->content_len);
250     if (!content)
251         goto out;
252     memcpy(content, entry + 1, entry->content_len);
253 
254     name = strrchr(path, '/');
255     if (!name)
256         name = path;
257     else {
258         name++;
259         if (!*name)
260             name--;
261     }
262     *dirent = calloc(1, sizeof(struct xenhypfs_dirent) + strlen(name) + 1);
263     if (!*dirent) {
264         free(content);
265         content = NULL;
266         errno = ENOMEM;
267         goto out;
268     }
269     (*dirent)->name = (char *)(*dirent + 1);
270     strcpy((*dirent)->name, name);
271     xenhypfs_set_attrs(entry, *dirent);
272 
273  out:
274     ret = errno;
275     xencall_free_buffer(fshdl->xcall, path_buf);
276     xencall_free_buffer(fshdl->xcall, retbuf);
277     errno = ret;
278 
279     return content;
280 }
281 
xenhypfs_read(xenhypfs_handle * fshdl,const char * path)282 char *xenhypfs_read(xenhypfs_handle *fshdl, const char *path)
283 {
284     char *buf, *ret_buf = NULL;
285     struct xenhypfs_dirent *dirent;
286     int ret;
287 
288     buf = xenhypfs_read_raw(fshdl, path, &dirent);
289     if (!buf)
290         goto out;
291 
292     switch (dirent->encoding) {
293     case xenhypfs_enc_plain:
294         break;
295     case xenhypfs_enc_gzip:
296         ret_buf = xenhypfs_inflate(buf, &dirent->size);
297         if (!ret_buf)
298             goto out;
299         free(buf);
300         buf = ret_buf;
301         ret_buf = NULL;
302         break;
303     }
304 
305     switch (dirent->type) {
306     case xenhypfs_type_dir:
307         errno = EISDIR;
308         break;
309     case xenhypfs_type_blob:
310         errno = EDOM;
311         break;
312     case xenhypfs_type_string:
313         ret_buf = buf;
314         buf = NULL;
315         break;
316     case xenhypfs_type_uint:
317     case xenhypfs_type_bool:
318         switch (dirent->size) {
319         case 1:
320             ret = asprintf(&ret_buf, "%"PRIu8, *(uint8_t *)buf);
321             break;
322         case 2:
323             ret = asprintf(&ret_buf, "%"PRIu16, *(uint16_t *)buf);
324             break;
325         case 4:
326             ret = asprintf(&ret_buf, "%"PRIu32, *(uint32_t *)buf);
327             break;
328         case 8:
329             ret = asprintf(&ret_buf, "%"PRIu64, *(uint64_t *)buf);
330             break;
331         default:
332             ret = -1;
333             errno = EDOM;
334         }
335         if (ret < 0)
336             ret_buf = NULL;
337         break;
338     case xenhypfs_type_int:
339         switch (dirent->size) {
340         case 1:
341             ret = asprintf(&ret_buf, "%"PRId8, *(int8_t *)buf);
342             break;
343         case 2:
344             ret = asprintf(&ret_buf, "%"PRId16, *(int16_t *)buf);
345             break;
346         case 4:
347             ret = asprintf(&ret_buf, "%"PRId32, *(int32_t *)buf);
348             break;
349         case 8:
350             ret = asprintf(&ret_buf, "%"PRId64, *(int64_t *)buf);
351             break;
352         default:
353             ret = -1;
354             errno = EDOM;
355         }
356         if (ret < 0)
357             ret_buf = NULL;
358         break;
359     }
360 
361  out:
362     ret = errno;
363     free(buf);
364     free(dirent);
365     errno = ret;
366 
367     return ret_buf;
368 }
369 
xenhypfs_readdir(xenhypfs_handle * fshdl,const char * path,unsigned int * num_entries)370 struct xenhypfs_dirent *xenhypfs_readdir(xenhypfs_handle *fshdl,
371                                          const char *path,
372                                          unsigned int *num_entries)
373 {
374     void *buf, *curr;
375     int ret;
376     char *names;
377     struct xenhypfs_dirent *ret_buf = NULL, *dirent;
378     unsigned int n = 0, name_sz = 0;
379     struct xen_hypfs_dirlistentry *entry;
380 
381     buf = xenhypfs_read_raw(fshdl, path, &dirent);
382     if (!buf)
383         goto out;
384 
385     if (dirent->type != xenhypfs_type_dir ||
386         dirent->encoding != xenhypfs_enc_plain) {
387         errno = ENOTDIR;
388         goto out;
389     }
390 
391     if (dirent->size) {
392         curr = buf;
393         for (n = 1;; n++) {
394             entry = curr;
395             name_sz += strlen(entry->name) + 1;
396             if (!entry->off_next)
397                 break;
398 
399             curr += entry->off_next;
400         }
401     }
402 
403     ret_buf = malloc(n * sizeof(*ret_buf) + name_sz);
404     if (!ret_buf)
405         goto out;
406 
407     *num_entries = n;
408     names = (char *)(ret_buf + n);
409     curr = buf;
410     for (n = 0; n < *num_entries; n++) {
411         entry = curr;
412         xenhypfs_set_attrs(&entry->e, ret_buf + n);
413         ret_buf[n].name = names;
414         strcpy(names, entry->name);
415         names += strlen(entry->name) + 1;
416         curr += entry->off_next;
417     }
418 
419  out:
420     ret = errno;
421     free(buf);
422     free(dirent);
423     errno = ret;
424 
425     return ret_buf;
426 }
427 
xenhypfs_write(xenhypfs_handle * fshdl,const char * path,const char * val)428 int xenhypfs_write(xenhypfs_handle *fshdl, const char *path, const char *val)
429 {
430     void *buf = NULL;
431     char *path_buf = NULL, *val_end;
432     int ret, saved_errno;
433     int sz, path_sz;
434     struct xen_hypfs_direntry *entry;
435     uint64_t mask;
436 
437     ret = xenhypfs_get_pathbuf(fshdl, path, &path_buf);
438     if (ret < 0)
439         goto out;
440 
441     path_sz = ret;
442     ret = -1;
443 
444     sz = BUF_SIZE;
445     buf = xencall_alloc_buffer(fshdl->xcall, sz);
446     if (!buf) {
447         errno = ENOMEM;
448         goto out;
449     }
450 
451     ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_read,
452                    (unsigned long)path_buf, path_sz,
453                    (unsigned long)buf, sizeof(*entry));
454     if (ret && errno != ENOBUFS)
455         goto out;
456     ret = -1;
457     entry = buf;
458     if (!entry->max_write_len) {
459         errno = EACCES;
460         goto out;
461     }
462     if (entry->encoding != XEN_HYPFS_ENC_PLAIN) {
463         /* Writing compressed data currently not supported. */
464         errno = EDOM;
465         goto out;
466     }
467 
468     switch (entry->type) {
469     case XEN_HYPFS_TYPE_STRING:
470         if (sz < strlen(val) + 1) {
471             sz = strlen(val) + 1;
472             xencall_free_buffer(fshdl->xcall, buf);
473             buf = xencall_alloc_buffer(fshdl->xcall, sz);
474             if (!buf) {
475                 errno = ENOMEM;
476                 goto out;
477             }
478         }
479         sz = strlen(val) + 1;
480         strcpy(buf, val);
481         break;
482     case XEN_HYPFS_TYPE_UINT:
483         sz = entry->content_len;
484         errno = 0;
485         *(unsigned long long *)buf = strtoull(val, &val_end, 0);
486         if (errno || !*val || *val_end)
487             goto out;
488         mask = ~0ULL << (8 * sz);
489         if ((*(uint64_t *)buf & mask) && ((*(uint64_t *)buf & mask) != mask)) {
490             errno = ERANGE;
491             goto out;
492         }
493         break;
494     case XEN_HYPFS_TYPE_INT:
495         sz = entry->content_len;
496         errno = 0;
497         *(unsigned long long *)buf = strtoll(val, &val_end, 0);
498         if (errno || !*val || *val_end)
499             goto out;
500         mask = (sz == 8) ? 0 : ~0ULL << (8 * sz);
501         if ((*(uint64_t *)buf & mask) && ((*(uint64_t *)buf & mask) != mask)) {
502             errno = ERANGE;
503             goto out;
504         }
505         break;
506     case XEN_HYPFS_TYPE_BOOL:
507         sz = entry->content_len;
508         *(unsigned long long *)buf = 0;
509         if (!strcmp(val, "1") || !strcmp(val, "on") || !strcmp(val, "yes") ||
510             !strcmp(val, "true") || !strcmp(val, "enable"))
511             *(unsigned long long *)buf = 1;
512         else if (strcmp(val, "0") && strcmp(val, "no") && strcmp(val, "off") &&
513                  strcmp(val, "false") && strcmp(val, "disable")) {
514             errno = EDOM;
515             goto out;
516         }
517         break;
518     default:
519         /* No support for other types (yet). */
520         errno = EDOM;
521         goto out;
522     }
523 
524     ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op,
525                    XEN_HYPFS_OP_write_contents,
526                    (unsigned long)path_buf, path_sz,
527                    (unsigned long)buf, sz);
528 
529  out:
530     saved_errno = errno;
531     xencall_free_buffer(fshdl->xcall, path_buf);
532     xencall_free_buffer(fshdl->xcall, buf);
533     errno = saved_errno;
534     return ret;
535 }
536