/* * Copyright (c) 2019 SUSE Software Solutions Germany GmbH * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; If not, see . */ #define __XEN_TOOLS__ 1 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #define BUF_SIZE 4096 struct xenhypfs_handle { xentoollog_logger *logger, *logger_tofree; unsigned int flags; xencall_handle *xcall; }; xenhypfs_handle *xenhypfs_open(xentoollog_logger *logger, unsigned open_flags) { xenhypfs_handle *fshdl = calloc(1, sizeof(*fshdl)); if (!fshdl) return NULL; fshdl->flags = open_flags; fshdl->logger = logger; fshdl->logger_tofree = NULL; if (!fshdl->logger) { fshdl->logger = fshdl->logger_tofree = (xentoollog_logger*) xtl_createlogger_stdiostream(stderr, XTL_PROGRESS, 0); if (!fshdl->logger) goto err; } fshdl->xcall = xencall_open(fshdl->logger, 0); if (!fshdl->xcall) goto err; /* No need to remember supported version, we only support V1. */ if (xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_get_version, 0, 0, 0, 0) < 0) goto err; return fshdl; err: xencall_close(fshdl->xcall); xtl_logger_destroy(fshdl->logger_tofree); free(fshdl); return NULL; } int xenhypfs_close(xenhypfs_handle *fshdl) { if (!fshdl) return 0; xencall_close(fshdl->xcall); xtl_logger_destroy(fshdl->logger_tofree); free(fshdl); return 0; } static int xenhypfs_get_pathbuf(xenhypfs_handle *fshdl, const char *path, char **path_buf) { int ret = -1; int path_sz; if (!fshdl) { errno = EBADF; goto out; } path_sz = strlen(path) + 1; if (path_sz > XEN_HYPFS_MAX_PATHLEN) { errno = ENAMETOOLONG; goto out; } *path_buf = xencall_alloc_buffer(fshdl->xcall, path_sz); if (!*path_buf) { errno = ENOMEM; goto out; } strcpy(*path_buf, path); ret = path_sz; out: return ret; } static void *xenhypfs_inflate(void *in_data, size_t *sz) { unsigned char *workbuf; void *content = NULL; unsigned int out_sz; z_stream z = { .opaque = NULL }; int ret; workbuf = malloc(BUF_SIZE); if (!workbuf) return NULL; z.next_in = in_data; z.avail_in = *sz; ret = inflateInit2(&z, MAX_WBITS + 32); /* 32 == gzip */ for (*sz = 0; ret == Z_OK; *sz += out_sz) { z.next_out = workbuf; z.avail_out = BUF_SIZE; ret = inflate(&z, Z_SYNC_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) break; out_sz = z.next_out - workbuf; content = realloc(content, *sz + out_sz); if (!content) { ret = Z_MEM_ERROR; break; } memcpy(content + *sz, workbuf, out_sz); } inflateEnd(&z); if (ret != Z_STREAM_END) { free(content); content = NULL; errno = EIO; } free(workbuf); return content; } static void xenhypfs_set_attrs(struct xen_hypfs_direntry *entry, struct xenhypfs_dirent *dirent) { dirent->size = entry->content_len; switch(entry->type) { case XEN_HYPFS_TYPE_DIR: dirent->type = xenhypfs_type_dir; break; case XEN_HYPFS_TYPE_BLOB: dirent->type = xenhypfs_type_blob; break; case XEN_HYPFS_TYPE_STRING: dirent->type = xenhypfs_type_string; break; case XEN_HYPFS_TYPE_UINT: dirent->type = xenhypfs_type_uint; break; case XEN_HYPFS_TYPE_INT: dirent->type = xenhypfs_type_int; break; case XEN_HYPFS_TYPE_BOOL: dirent->type = xenhypfs_type_bool; break; default: dirent->type = xenhypfs_type_blob; } switch (entry->encoding) { case XEN_HYPFS_ENC_PLAIN: dirent->encoding = xenhypfs_enc_plain; break; case XEN_HYPFS_ENC_GZIP: dirent->encoding = xenhypfs_enc_gzip; break; default: dirent->encoding = xenhypfs_enc_plain; dirent->type = xenhypfs_type_blob; } dirent->flags = entry->max_write_len ? XENHYPFS_FLAG_WRITABLE : 0; } void *xenhypfs_read_raw(xenhypfs_handle *fshdl, const char *path, struct xenhypfs_dirent **dirent) { void *retbuf = NULL, *content = NULL; char *path_buf = NULL; const char *name; struct xen_hypfs_direntry *entry; int ret; int sz, path_sz; *dirent = NULL; ret = xenhypfs_get_pathbuf(fshdl, path, &path_buf); if (ret < 0) goto out; path_sz = ret; for (sz = BUF_SIZE;; sz = sizeof(*entry) + entry->content_len) { if (retbuf) xencall_free_buffer(fshdl->xcall, retbuf); retbuf = xencall_alloc_buffer(fshdl->xcall, sz); if (!retbuf) { errno = ENOMEM; goto out; } entry = retbuf; ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_read, (unsigned long)path_buf, path_sz, (unsigned long)retbuf, sz); if (!ret) break; if (errno != ENOBUFS) goto out; } content = malloc(entry->content_len); if (!content) goto out; memcpy(content, entry + 1, entry->content_len); name = strrchr(path, '/'); if (!name) name = path; else { name++; if (!*name) name--; } *dirent = calloc(1, sizeof(struct xenhypfs_dirent) + strlen(name) + 1); if (!*dirent) { free(content); content = NULL; errno = ENOMEM; goto out; } (*dirent)->name = (char *)(*dirent + 1); strcpy((*dirent)->name, name); xenhypfs_set_attrs(entry, *dirent); out: ret = errno; xencall_free_buffer(fshdl->xcall, path_buf); xencall_free_buffer(fshdl->xcall, retbuf); errno = ret; return content; } char *xenhypfs_read(xenhypfs_handle *fshdl, const char *path) { char *buf, *ret_buf = NULL; struct xenhypfs_dirent *dirent; int ret; buf = xenhypfs_read_raw(fshdl, path, &dirent); if (!buf) goto out; switch (dirent->encoding) { case xenhypfs_enc_plain: break; case xenhypfs_enc_gzip: ret_buf = xenhypfs_inflate(buf, &dirent->size); if (!ret_buf) goto out; free(buf); buf = ret_buf; ret_buf = NULL; break; } switch (dirent->type) { case xenhypfs_type_dir: errno = EISDIR; break; case xenhypfs_type_blob: errno = EDOM; break; case xenhypfs_type_string: ret_buf = buf; buf = NULL; break; case xenhypfs_type_uint: case xenhypfs_type_bool: switch (dirent->size) { case 1: ret = asprintf(&ret_buf, "%"PRIu8, *(uint8_t *)buf); break; case 2: ret = asprintf(&ret_buf, "%"PRIu16, *(uint16_t *)buf); break; case 4: ret = asprintf(&ret_buf, "%"PRIu32, *(uint32_t *)buf); break; case 8: ret = asprintf(&ret_buf, "%"PRIu64, *(uint64_t *)buf); break; default: ret = -1; errno = EDOM; } if (ret < 0) ret_buf = NULL; break; case xenhypfs_type_int: switch (dirent->size) { case 1: ret = asprintf(&ret_buf, "%"PRId8, *(int8_t *)buf); break; case 2: ret = asprintf(&ret_buf, "%"PRId16, *(int16_t *)buf); break; case 4: ret = asprintf(&ret_buf, "%"PRId32, *(int32_t *)buf); break; case 8: ret = asprintf(&ret_buf, "%"PRId64, *(int64_t *)buf); break; default: ret = -1; errno = EDOM; } if (ret < 0) ret_buf = NULL; break; } out: ret = errno; free(buf); free(dirent); errno = ret; return ret_buf; } struct xenhypfs_dirent *xenhypfs_readdir(xenhypfs_handle *fshdl, const char *path, unsigned int *num_entries) { void *buf, *curr; int ret; char *names; struct xenhypfs_dirent *ret_buf = NULL, *dirent; unsigned int n = 0, name_sz = 0; struct xen_hypfs_dirlistentry *entry; buf = xenhypfs_read_raw(fshdl, path, &dirent); if (!buf) goto out; if (dirent->type != xenhypfs_type_dir || dirent->encoding != xenhypfs_enc_plain) { errno = ENOTDIR; goto out; } if (dirent->size) { curr = buf; for (n = 1;; n++) { entry = curr; name_sz += strlen(entry->name) + 1; if (!entry->off_next) break; curr += entry->off_next; } } ret_buf = malloc(n * sizeof(*ret_buf) + name_sz); if (!ret_buf) goto out; *num_entries = n; names = (char *)(ret_buf + n); curr = buf; for (n = 0; n < *num_entries; n++) { entry = curr; xenhypfs_set_attrs(&entry->e, ret_buf + n); ret_buf[n].name = names; strcpy(names, entry->name); names += strlen(entry->name) + 1; curr += entry->off_next; } out: ret = errno; free(buf); free(dirent); errno = ret; return ret_buf; } int xenhypfs_write(xenhypfs_handle *fshdl, const char *path, const char *val) { void *buf = NULL; char *path_buf = NULL, *val_end; int ret, saved_errno; int sz, path_sz; struct xen_hypfs_direntry *entry; uint64_t mask; ret = xenhypfs_get_pathbuf(fshdl, path, &path_buf); if (ret < 0) goto out; path_sz = ret; ret = -1; sz = BUF_SIZE; buf = xencall_alloc_buffer(fshdl->xcall, sz); if (!buf) { errno = ENOMEM; goto out; } ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_read, (unsigned long)path_buf, path_sz, (unsigned long)buf, sizeof(*entry)); if (ret && errno != ENOBUFS) goto out; ret = -1; entry = buf; if (!entry->max_write_len) { errno = EACCES; goto out; } if (entry->encoding != XEN_HYPFS_ENC_PLAIN) { /* Writing compressed data currently not supported. */ errno = EDOM; goto out; } switch (entry->type) { case XEN_HYPFS_TYPE_STRING: if (sz < strlen(val) + 1) { sz = strlen(val) + 1; xencall_free_buffer(fshdl->xcall, buf); buf = xencall_alloc_buffer(fshdl->xcall, sz); if (!buf) { errno = ENOMEM; goto out; } } sz = strlen(val) + 1; strcpy(buf, val); break; case XEN_HYPFS_TYPE_UINT: sz = entry->content_len; errno = 0; *(unsigned long long *)buf = strtoull(val, &val_end, 0); if (errno || !*val || *val_end) goto out; mask = ~0ULL << (8 * sz); if ((*(uint64_t *)buf & mask) && ((*(uint64_t *)buf & mask) != mask)) { errno = ERANGE; goto out; } break; case XEN_HYPFS_TYPE_INT: sz = entry->content_len; errno = 0; *(unsigned long long *)buf = strtoll(val, &val_end, 0); if (errno || !*val || *val_end) goto out; mask = (sz == 8) ? 0 : ~0ULL << (8 * sz); if ((*(uint64_t *)buf & mask) && ((*(uint64_t *)buf & mask) != mask)) { errno = ERANGE; goto out; } break; case XEN_HYPFS_TYPE_BOOL: sz = entry->content_len; *(unsigned long long *)buf = 0; if (!strcmp(val, "1") || !strcmp(val, "on") || !strcmp(val, "yes") || !strcmp(val, "true") || !strcmp(val, "enable")) *(unsigned long long *)buf = 1; else if (strcmp(val, "0") && strcmp(val, "no") && strcmp(val, "off") && strcmp(val, "false") && strcmp(val, "disable")) { errno = EDOM; goto out; } break; default: /* No support for other types (yet). */ errno = EDOM; goto out; } ret = xencall5(fshdl->xcall, __HYPERVISOR_hypfs_op, XEN_HYPFS_OP_write_contents, (unsigned long)path_buf, path_sz, (unsigned long)buf, sz); out: saved_errno = errno; xencall_free_buffer(fshdl->xcall, path_buf); xencall_free_buffer(fshdl->xcall, buf); errno = saved_errno; return ret; }