1 /* $NetBSD: fwcfg.c,v 1.8 2026/01/03 17:08:45 nia Exp $ */ 2 3 /*- 4 * Copyright (c) 2017 Jared McNeill <jmcneill (at) invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: fwcfg.c,v 1.8 2026/01/03 17:08:45 nia Exp $"); 31 32 #define FUSE_USE_VERSION FUSE_MAKE_VERSION(2, 6) 33 34 #include <sys/ioctl.h> 35 #include <sys/param.h> 36 37 #include <err.h> 38 #include <errno.h> 39 #include <endian.h> 40 #include <fcntl.h> 41 #include <fuse.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #include <dev/ic/qemufwcfgio.h> 48 49 #include "virtdir.h" 50 51 #define _PATH_FWCFG "/dev/qemufwcfg" 52 53 struct fwcfg_file { 54 uint32_t size; 55 uint16_t select; 56 uint16_t reserved; 57 char name[56]; 58 }; 59 60 static int fwcfg_fd; 61 static mode_t fwcfg_dir_mask = 0555; 62 static mode_t fwcfg_file_mask = 0444; 63 static uid_t fwcfg_uid; 64 static gid_t fwcfg_gid; 65 66 static virtdir_t fwcfg_virtdir; 67 68 static void 69 set_index(uint16_t index) 70 { 71 if (ioctl(fwcfg_fd, FWCFGIO_SET_INDEX, &index) != 0) 72 err(EXIT_FAILURE, "failed to set index 0x%04x", index); 73 } 74 75 static void 76 read_data(void *buf, size_t buflen) 77 { 78 if (read(fwcfg_fd, buf, buflen) != (ssize_t)buflen) 79 err(EXIT_FAILURE, "failed to read data"); 80 } 81 82 static int 83 fwcfg_getattr(const char *path, struct stat *st) 84 { 85 virt_dirent_t *ep; 86 87 if (strcmp(path, "/") == 0) { 88 memset(st, 0, sizeof(*st)); 89 st->st_mode = S_IFDIR | fwcfg_dir_mask; 90 st->st_nlink = 2; 91 return 0; 92 } 93 94 if ((ep = virtdir_find(&fwcfg_virtdir, path, strlen(path))) == NULL) 95 return -ENOENT; 96 97 switch (ep->type) { 98 case 'f': 99 memcpy(st, &fwcfg_virtdir.file, sizeof(*st)); 100 st->st_size = (off_t)ep->tgtlen; 101 st->st_mode = S_IFREG | fwcfg_file_mask; 102 break; 103 case 'd': 104 memcpy(st, &fwcfg_virtdir.dir, sizeof(*st)); 105 st->st_mode = S_IFDIR | fwcfg_dir_mask; 106 break; 107 } 108 st->st_ino = (ino_t)virtdir_offset(&fwcfg_virtdir, ep) + 10; 109 110 return 0; 111 } 112 113 static int 114 fwcfg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 115 off_t offset, struct fuse_file_info *fi) 116 { 117 static VIRTDIR *dirp; 118 virt_dirent_t *dp; 119 120 if (offset == 0) { 121 if ((dirp = openvirtdir(&fwcfg_virtdir, path)) == NULL) 122 return 0; 123 filler(buf, ".", NULL, 0); 124 filler(buf, "..", NULL, 0); 125 } 126 while ((dp = readvirtdir(dirp)) != NULL) { 127 if (filler(buf, dp->d_name, NULL, 0) != 0) 128 return 0; 129 } 130 closevirtdir(dirp); 131 dirp = NULL; 132 133 return 0; 134 } 135 136 static int 137 fwcfg_open(const char *path, struct fuse_file_info *fi) 138 { 139 return 0; 140 } 141 142 static int 143 fwcfg_read(const char *path, char *buf, size_t size, off_t offset, 144 struct fuse_file_info *fi) 145 { 146 virt_dirent_t *ep; 147 uint8_t tmp[64]; 148 149 if ((ep = virtdir_find(&fwcfg_virtdir, path, strlen(path))) == NULL) 150 return -ENOENT; 151 152 if (ep->select == 0) 153 return -ENOENT; 154 155 set_index(ep->select); 156 157 /* Seek to correct offset */ 158 while (offset > 0) { 159 const size_t len = MIN(sizeof(tmp), (size_t)offset); 160 read_data(tmp, len); 161 offset -= (off_t)len; 162 } 163 164 /* Read the data */ 165 read_data(buf, size); 166 167 return (int)size; 168 } 169 170 static int 171 fwcfg_statfs(const char *path, struct statvfs *st) 172 { 173 uint32_t count; 174 175 set_index(FW_CFG_FILE_DIR); 176 read_data(&count, sizeof(count)); 177 178 memset(st, 0, sizeof(*st)); 179 st->f_files = be32toh(count); 180 181 return 0; 182 } 183 184 static struct fuse_operations fwcfg_ops = { 185 .getattr = fwcfg_getattr, 186 .readdir = fwcfg_readdir, 187 .open = fwcfg_open, 188 .read = fwcfg_read, 189 .statfs = fwcfg_statfs, 190 }; 191 192 static void 193 build_tree(virtdir_t *v) 194 { 195 char path[PATH_MAX]; 196 struct fwcfg_file f; 197 uint32_t count, n; 198 struct stat st; 199 200 memset(&st, 0, sizeof(st)); 201 st.st_uid = fwcfg_uid; 202 st.st_gid = fwcfg_gid; 203 virtdir_init(v, NULL, &st, &st, &st); 204 205 set_index(FW_CFG_FILE_DIR); 206 read_data(&count, sizeof(count)); 207 for (n = 0; n < be32toh(count); n++) { 208 read_data(&f, sizeof(f)); 209 snprintf(path, sizeof(path), "/%s", f.name); 210 virtdir_add(v, path, strlen(path), 'f', NULL, 211 be32toh(f.size), be16toh(f.select)); 212 } 213 } 214 215 #if 0 216 static __dead void 217 usage(void) 218 { 219 fprintf(stderr, "Usage: %s [-F path] [-g gid] [-M dir-mode] " 220 "[-m file-mode] [-u uid] [fuse-options]", getprogname()); 221 exit(EXIT_FAILURE); 222 } 223 #endif 224 225 int 226 main(int argc, char *argv[]) 227 { 228 const char *path = _PATH_FWCFG; 229 int ch; 230 long m; 231 char *ep; 232 233 fwcfg_uid = geteuid(); 234 fwcfg_gid = getegid(); 235 236 while ((ch = getopt(argc, argv, "F:g:m:M:u:")) != -1) { 237 switch (ch) { 238 case 'F': 239 path = optarg; 240 break; 241 case 'g': 242 fwcfg_gid = (gid_t)atoi(optarg); 243 break; 244 case 'm': 245 m = strtol(optarg, &ep, 8); 246 if (optarg == ep || *ep || m < 0) 247 errx(EXIT_FAILURE, "invalid file mode: %s", 248 optarg); 249 fwcfg_file_mask = (mode_t)m; 250 break; 251 case 'M': 252 m = strtol(optarg, &ep, 8); 253 if (optarg == ep || *ep || m < 0) 254 errx(EXIT_FAILURE, "invalid directory mode: %s", 255 optarg); 256 fwcfg_dir_mask = (mode_t)m; 257 break; 258 case 'u': 259 fwcfg_uid = (uid_t)atoi(optarg); 260 break; 261 #ifdef notyet 262 default: 263 usage(); 264 #endif 265 } 266 } 267 268 fwcfg_fd = open(path, O_RDWR); 269 if (fwcfg_fd == -1) 270 err(EXIT_FAILURE, "failed to open %s", path); 271 272 build_tree(&fwcfg_virtdir); 273 274 return fuse_main(argc, argv, &fwcfg_ops, NULL); 275 } 276