Add virtual filesystem for QEMU Firmware Configuration interface.

This commit is contained in:
jmcneill 2017-11-25 23:23:39 +00:00
parent 40017b1be4
commit c91601382a
5 changed files with 666 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# $NetBSD: Makefile,v 1.1 2017/11/25 23:23:39 jmcneill Exp $
PROG= mount_qemufwcfg
SRCS= fwcfg.c virtdir.c
DPADD+= ${LIBREFUSE}
LDADD= -lrefuse
NOMAN= # defined
WARNS= 3
CPPFLAGS+= -D_KERNTYPES
.include <bsd.prog.mk>

View File

@ -0,0 +1,88 @@
/* $NetBSD: defs.h,v 1.1 2017/11/25 23:23:39 jmcneill Exp $ */
/*
* Copyright (c) 1999-2005 Alistair Crooks. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef DEFS_H_
#define DEFS_H_
#include <sys/types.h>
#include <sys/param.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NEWARRAY(type,ptr,size,where,action) do { \
if ((ptr = (type *) calloc(sizeof(type), (unsigned)(size))) == NULL) { \
(void) fprintf(stderr, "%s: can't allocate %lu bytes\n", \
where, (unsigned long)(size * sizeof(type))); \
action; \
} \
} while( /* CONSTCOND */ 0)
#define RENEW(type,ptr,size,where,action) do { \
type *_newptr; \
if ((_newptr = (type *) realloc(ptr, sizeof(type) * (size))) == NULL) { \
(void) fprintf(stderr, "%s: can't realloc %lu bytes\n", \
where, (unsigned long)(size * sizeof(type))); \
action; \
} else { \
ptr = _newptr; \
} \
} while( /* CONSTCOND */ 0)
#define NEW(type, ptr, where, action) NEWARRAY(type, ptr, 1, where, action)
#define FREE(ptr) (void) free(ptr)
#define ALLOC(type, v, size, c, init, incr, where, action) do { \
uint32_t _newsize = size; \
if (size == 0) { \
_newsize = init; \
NEWARRAY(type, v, _newsize, where ": new", action); \
} else if (c == size) { \
_newsize = size + incr; \
RENEW(type, v, _newsize, where ": renew", action); \
} \
size = _newsize; \
} while( /* CONSTCOND */ 0)
/* (void) memset(&v[size], 0x0, sizeof(type) * incr); \*/
#define DEFINE_ARRAY(name, type) \
typedef struct name { \
uint32_t c; \
uint32_t size; \
type *v; \
} name
#endif /* !DEFS_H_ */

View File

@ -0,0 +1,259 @@
/* $NetBSD: fwcfg.c,v 1.1 2017/11/25 23:23:39 jmcneill Exp $ */
/*-
* Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: fwcfg.c,v 1.1 2017/11/25 23:23:39 jmcneill Exp $");
#include <sys/ioctl.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dev/ic/qemufwcfgio.h>
#include "virtdir.h"
#define _PATH_FWCFG "/dev/qemufwcfg"
struct fwcfg_file {
uint32_t size;
uint16_t select;
uint16_t reserved;
char name[56];
};
static int fwcfg_fd;
static mode_t fwcfg_dir_mask = 0555;
static mode_t fwcfg_file_mask = 0444;
static uid_t fwcfg_uid;
static gid_t fwcfg_gid;
static virtdir_t fwcfg_virtdir;
static void
set_index(uint16_t index)
{
if (ioctl(fwcfg_fd, FWCFGIO_SET_INDEX, &index) != 0)
err(EXIT_FAILURE, "failed to set index 0x%04x", index);
}
static void
read_data(void *buf, size_t buflen)
{
if (read(fwcfg_fd, buf, buflen) != (ssize_t)buflen)
err(EXIT_FAILURE, "failed to read data");
}
static int
fwcfg_getattr(const char *path, struct stat *st)
{
virt_dirent_t *ep;
if (strcmp(path, "/") == 0) {
memset(st, 0, sizeof(*st));
st->st_mode = S_IFDIR | fwcfg_dir_mask;
st->st_nlink = 2;
return 0;
}
if ((ep = virtdir_find(&fwcfg_virtdir, path, strlen(path))) == NULL)
return -ENOENT;
switch (ep->type) {
case 'f':
memcpy(st, &fwcfg_virtdir.file, sizeof(*st));
st->st_size = ep->tgtlen;
st->st_mode = S_IFREG | fwcfg_file_mask;
break;
case 'd':
memcpy(st, &fwcfg_virtdir.dir, sizeof(*st));
st->st_mode = S_IFDIR | fwcfg_dir_mask;
break;
}
st->st_ino = virtdir_offset(&fwcfg_virtdir, ep) + 10;
return 0;
}
static int
fwcfg_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
static VIRTDIR *dirp;
virt_dirent_t *dp;
if (offset == 0) {
if ((dirp = openvirtdir(&fwcfg_virtdir, path)) == NULL)
return 0;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
}
while ((dp = readvirtdir(dirp)) != NULL) {
if (filler(buf, dp->d_name, NULL, 0) != 0)
return 0;
}
closevirtdir(dirp);
dirp = NULL;
return 0;
}
static int
fwcfg_open(const char *path, struct fuse_file_info *fi)
{
return 0;
}
static int
fwcfg_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
virt_dirent_t *ep;
uint8_t tmp[64];
if ((ep = virtdir_find(&fwcfg_virtdir, path, strlen(path))) == NULL)
return -ENOENT;
if (ep->select == 0)
return -ENOENT;
set_index(ep->select);
/* Seek to correct offset */
while (offset > 0) {
const int len = MIN(sizeof(tmp), (size_t)offset);
read_data(tmp, len);
offset -= len;
}
/* Read the data */
read_data(buf, size);
return size;
}
static int
fwcfg_statfs(const char *path, struct statvfs *st)
{
uint32_t count;
set_index(FW_CFG_FILE_DIR);
read_data(&count, sizeof(count));
memset(st, 0, sizeof(*st));
st->f_files = be32toh(count);
return 0;
}
static struct fuse_operations fwcfg_ops = {
.getattr = fwcfg_getattr,
.readdir = fwcfg_readdir,
.open = fwcfg_open,
.read = fwcfg_read,
.statfs = fwcfg_statfs,
};
static void
build_tree(virtdir_t *v)
{
char path[PATH_MAX];
struct fwcfg_file f;
uint32_t count, n;
struct stat st;
memset(&st, 0, sizeof(st));
st.st_uid = fwcfg_uid;
st.st_gid = fwcfg_gid;
virtdir_init(v, NULL, &st, &st, &st);
printf("init with uid = %d, gid = %d\n", st.st_uid, st.st_gid);
set_index(FW_CFG_FILE_DIR);
read_data(&count, sizeof(count));
for (n = 0; n < be32toh(count); n++) {
read_data(&f, sizeof(f));
snprintf(path, sizeof(path), "/%s", f.name);
virtdir_add(v, path, strlen(path), 'f', NULL,
be32toh(f.size), be16toh(f.select));
}
}
int
main(int argc, char *argv[])
{
const char *path = _PATH_FWCFG;
int ch, m;
char *ep;
fwcfg_uid = geteuid();
fwcfg_gid = getegid();
while ((ch = getopt(argc, argv, "F:g:m:M:u:")) != -1) {
switch (ch) {
case 'F':
path = optarg;
break;
case 'g':
fwcfg_gid = atoi(optarg);
break;
case 'm':
m = strtol(optarg, &ep, 8);
if (optarg == ep || *ep || m < 0)
errx(1, "invalid file mode: %s", optarg);
fwcfg_file_mask = m;
break;
case 'M':
m = strtol(optarg, &ep, 8);
if (optarg == ep || *ep || m < 0)
errx(1, "invalid file mode: %s", optarg);
fwcfg_dir_mask = m;
break;
case 'u':
fwcfg_uid = atoi(optarg);
break;
}
}
fwcfg_fd = open(path, O_RDWR);
if (fwcfg_fd == -1)
err(EXIT_FAILURE, "failed to open %s", path);
build_tree(&fwcfg_virtdir);
for (int i = 0; i < argc; i++)
printf("argv[%d] = \"%s\"\n", i, argv[i]);
return fuse_main(argc, argv, &fwcfg_ops, NULL);
}

View File

@ -0,0 +1,226 @@
/* $NetBSD: virtdir.c,v 1.1 2017/11/25 23:23:39 jmcneill Exp $ */
/*
* Copyright © 2007 Alistair Crooks. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "virtdir.h"
#include "defs.h"
/* utility comparison routine for sorting and searching */
static int
compare(const void *vp1, const void *vp2)
{
const virt_dirent_t *tp1 = (const virt_dirent_t *) vp1;
const virt_dirent_t *tp2 = (const virt_dirent_t *) vp2;
return strcmp(tp1->name, tp2->name);
}
/* save `n' chars of `s' in allocated storage */
static char *
strnsave(const char *s, int n)
{
char *cp;
if (n < 0) {
n = strlen(s);
}
NEWARRAY(char, cp, n + 1, "strnsave", return NULL);
(void) memcpy(cp, s, n);
cp[n] = 0x0;
return cp;
}
/* ensure intermediate directories exist */
static void
mkdirs(virtdir_t *tp, const char *path, size_t size)
{
virt_dirent_t *ep;
char name[MAXPATHLEN];
char *slash;
(void) strlcpy(name, path, sizeof(name));
for (slash = name + 1 ; (slash = strchr(slash + 1, '/')) != NULL ; ) {
*slash = 0x0;
if ((ep = virtdir_find(tp, name, strlen(name))) == NULL) {
virtdir_add(tp, name, strlen(name), 'd', NULL, 0, 0);
}
*slash = '/';
}
}
/* get rid of multiple slashes in input */
static int
normalise(const char *name, size_t namelen, char *path, size_t pathsize)
{
const char *np;
char *pp;
int done;
for (pp = path, np = name, done = 0 ; !done && (int)(pp - path) < pathsize - 1 && (int)(np - name) <= namelen ; ) {
switch(*np) {
case '/':
if (pp == path || *(pp - 1) != '/') {
*pp++ = *np;
}
np += 1;
break;
case 0x0:
done = 1;
break;
default:
*pp++ = *np++;
break;
}
}
/* XXX - trailing slash? */
*pp = 0x0;
return (int)(pp - path);
}
/* initialise the tree */
int
virtdir_init(virtdir_t *tp, const char *rootdir, struct stat *d, struct stat *f, struct stat *l)
{
(void) memcpy(&tp->dir, d, sizeof(tp->dir));
tp->dir.st_mode = S_IFDIR | 0755;
tp->dir.st_nlink = 2;
(void) memcpy(&tp->file, f, sizeof(tp->file));
tp->file.st_mode = S_IFREG | 0644;
tp->file.st_nlink = 1;
(void) memcpy(&tp->lnk, l, sizeof(tp->lnk));
tp->lnk.st_mode = S_IFLNK | 0644;
tp->lnk.st_nlink = 1;
if (rootdir != NULL) {
tp->rootdir = strdup(rootdir);
}
return 1;
}
/* add an entry to the tree */
int
virtdir_add(virtdir_t *tp, const char *name, size_t size, uint8_t type, const char *tgt, size_t tgtlen, uint16_t select)
{
char path[MAXPATHLEN];
int pathlen;
pathlen = normalise(name, size, path, sizeof(path));
if (virtdir_find(tp, path, pathlen) != NULL) {
/* attempt to add a duplicate directory entry */
return 0;
}
ALLOC(virt_dirent_t, tp->v, tp->size, tp->c, 10, 10, "virtdir_add",
return 0);
tp->v[tp->c].namelen = pathlen;
if ((tp->v[tp->c].name = strnsave(path, pathlen)) == NULL) {
return 0;
}
tp->v[tp->c].d_name = strrchr(tp->v[tp->c].name, '/') + 1;
tp->v[tp->c].type = type;
tp->v[tp->c].ino = (ino_t) random() & 0xfffff;
tp->v[tp->c].tgtlen = tgtlen;
if (tgt != NULL) {
tp->v[tp->c].tgt = strnsave(tgt, tgtlen);
}
tp->v[tp->c].select = select;
tp->c += 1;
qsort(tp->v, tp->c, sizeof(tp->v[0]), compare);
mkdirs(tp, path, pathlen);
return 1;
}
/* find an entry in the tree */
virt_dirent_t *
virtdir_find(virtdir_t *tp, const char *name, size_t namelen)
{
virt_dirent_t e;
char path[MAXPATHLEN];
(void) memset(&e, 0x0, sizeof(e));
e.namelen = normalise(name, namelen, path, sizeof(path));
e.name = path;
return bsearch(&e, tp->v, tp->c, sizeof(tp->v[0]), compare);
}
/* return the virtual offset in the tree */
int
virtdir_offset(virtdir_t *tp, virt_dirent_t *dp)
{
return (int)(dp - tp->v);
}
/* analogous to opendir(3) - open a directory, save information, and
* return a pointer to the dynamically allocated structure */
VIRTDIR *
openvirtdir(virtdir_t *tp, const char *d)
{
VIRTDIR *dirp;
NEW(VIRTDIR, dirp, "openvirtdir", exit(EXIT_FAILURE));
dirp->dirname = strdup(d);
dirp->dirnamelen = strlen(d);
dirp->tp = tp;
dirp->i = 0;
return dirp;
}
/* analogous to readdir(3) - read the next entry in the directory that
* was opened, and return a pointer to it */
virt_dirent_t *
readvirtdir(VIRTDIR *dirp)
{
char *from;
for ( ; dirp->i < dirp->tp->c ; dirp->i++) {
from = (strcmp(dirp->dirname, "/") == 0) ?
&dirp->tp->v[dirp->i].name[1] :
&dirp->tp->v[dirp->i].name[dirp->dirnamelen + 1];
if (strncmp(dirp->tp->v[dirp->i].name, dirp->dirname,
dirp->dirnamelen) == 0 &&
*from != 0x0 &&
strchr(from, '/') == NULL) {
return &dirp->tp->v[dirp->i++];
}
}
return NULL;
}
/* free the storage associated with the virtual directory structure */
void
closevirtdir(VIRTDIR *dirp)
{
free(dirp->dirname);
FREE(dirp);
}

View File

@ -0,0 +1,81 @@
/* $NetBSD: virtdir.h,v 1.1 2017/11/25 23:23:39 jmcneill Exp $ */
/*
* Copyright © 2007 Alistair Crooks. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef VIRTDIR_H_
#define VIRTDIR_H_ 20070405
#include <sys/types.h>
#include <sys/stat.h>
#include "defs.h"
/* this struct keeps a note of all the info related to a virtual directory entry */
typedef struct virt_dirent_t {
char *name; /* entry name - used as key */
size_t namelen; /* length of name */
char *d_name; /* component in this directory */
char *tgt; /* any symlink target */
size_t tgtlen; /* length of symlink target */
uint8_t type; /* entry type - file, dir, lnk */
ino_t ino; /* inode number */
uint16_t select; /* selector */
} virt_dirent_t;
/* this defines the list of virtual directory entries,
sorted in name alpha order */
typedef struct virtdir_t {
uint32_t c; /* count of entries */
uint32_t size; /* size of allocated list */
virt_dirent_t *v; /* list */
char *rootdir; /* root directory of virtual fs */
struct stat file; /* stat struct for file entries */
struct stat dir; /* stat struct for dir entries */
struct stat lnk; /* stat struct for symlinks */
} virtdir_t;
/* this struct is used to walk through directories */
typedef struct VIRTDIR {
char *dirname; /* directory name */
int dirnamelen; /* length of directory name */
virtdir_t *tp; /* the directory tree */
int i; /* current offset in dir tree */
} VIRTDIR;
int virtdir_init(virtdir_t *, const char *, struct stat *, struct stat *, struct stat *);
int virtdir_add(virtdir_t *, const char *, size_t, uint8_t, const char *, size_t, uint16_t);
virt_dirent_t *virtdir_find(virtdir_t *, const char *, size_t);
VIRTDIR *openvirtdir(virtdir_t *, const char *);
virt_dirent_t *readvirtdir(VIRTDIR *);
void closevirtdir(VIRTDIR *);
int virtdir_offset(virtdir_t *, virt_dirent_t *);
#endif