Greg Hartman | 76d05dc | 2016-11-23 15:51:27 -0800 | [diff] [blame] | 1 | #include <sys/file.h> |
| 2 | #include <stdio.h> |
| 3 | #include <stdbool.h> |
| 4 | #include <string.h> |
| 5 | #include <unistd.h> |
| 6 | #include <fcntl.h> |
| 7 | #include <dprintf.h> |
| 8 | #include <syslinux/sysappend.h> |
| 9 | #include "core.h" |
| 10 | #include "dev.h" |
| 11 | #include "fs.h" |
| 12 | #include "cache.h" |
| 13 | |
| 14 | /* The currently mounted filesystem */ |
| 15 | __export struct fs_info *this_fs = NULL; /* Root filesystem */ |
| 16 | |
| 17 | /* Actual file structures (we don't have malloc yet...) */ |
| 18 | __export struct file files[MAX_OPEN]; |
| 19 | |
| 20 | /* Symlink hard limits */ |
| 21 | #define MAX_SYMLINK_CNT 20 |
| 22 | #define MAX_SYMLINK_BUF 4096 |
| 23 | |
| 24 | /* |
| 25 | * Get a new inode structure |
| 26 | */ |
| 27 | struct inode *alloc_inode(struct fs_info *fs, uint32_t ino, size_t data) |
| 28 | { |
| 29 | struct inode *inode = zalloc(sizeof(struct inode) + data); |
| 30 | if (inode) { |
| 31 | inode->fs = fs; |
| 32 | inode->ino = ino; |
| 33 | inode->refcnt = 1; |
| 34 | } |
| 35 | return inode; |
| 36 | } |
| 37 | |
| 38 | /* |
| 39 | * Free a refcounted inode |
| 40 | */ |
| 41 | void put_inode(struct inode *inode) |
| 42 | { |
| 43 | while (inode) { |
| 44 | struct inode *dead = inode; |
| 45 | int refcnt = --(dead->refcnt); |
| 46 | dprintf("put_inode %p name %s refcnt %u\n", dead, dead->name, refcnt); |
| 47 | if (refcnt) |
| 48 | break; /* We still have references */ |
| 49 | inode = dead->parent; |
| 50 | if (dead->name) |
| 51 | free((char *)dead->name); |
| 52 | free(dead); |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | /* |
| 57 | * Get an empty file structure |
| 58 | */ |
| 59 | static struct file *alloc_file(void) |
| 60 | { |
| 61 | int i; |
| 62 | struct file *file = files; |
| 63 | |
| 64 | for (i = 0; i < MAX_OPEN; i++) { |
| 65 | if (!file->fs) |
| 66 | return file; |
| 67 | file++; |
| 68 | } |
| 69 | |
| 70 | return NULL; |
| 71 | } |
| 72 | |
| 73 | /* |
| 74 | * Close and free a file structure |
| 75 | */ |
| 76 | static inline void free_file(struct file *file) |
| 77 | { |
| 78 | memset(file, 0, sizeof *file); |
| 79 | } |
| 80 | |
| 81 | __export void _close_file(struct file *file) |
| 82 | { |
| 83 | if (file->fs) |
| 84 | file->fs->fs_ops->close_file(file); |
| 85 | free_file(file); |
| 86 | } |
| 87 | |
| 88 | /* |
| 89 | * Find and open the configuration file |
| 90 | */ |
| 91 | __export int open_config(void) |
| 92 | { |
| 93 | int fd, handle; |
| 94 | struct file_info *fp; |
| 95 | |
| 96 | fd = opendev(&__file_dev, NULL, O_RDONLY); |
| 97 | if (fd < 0) |
| 98 | return -1; |
| 99 | |
| 100 | fp = &__file_info[fd]; |
| 101 | |
| 102 | handle = this_fs->fs_ops->open_config(&fp->i.fd); |
| 103 | if (handle < 0) { |
| 104 | close(fd); |
| 105 | errno = ENOENT; |
| 106 | return -1; |
| 107 | } |
| 108 | |
| 109 | fp->i.offset = 0; |
| 110 | fp->i.nbytes = 0; |
| 111 | |
| 112 | return fd; |
| 113 | } |
| 114 | |
| 115 | __export void mangle_name(char *dst, const char *src) |
| 116 | { |
| 117 | this_fs->fs_ops->mangle_name(dst, src); |
| 118 | } |
| 119 | |
| 120 | size_t pmapi_read_file(uint16_t *handle, void *buf, size_t sectors) |
| 121 | { |
| 122 | bool have_more; |
| 123 | size_t bytes_read; |
| 124 | struct file *file; |
| 125 | |
| 126 | file = handle_to_file(*handle); |
| 127 | bytes_read = file->fs->fs_ops->getfssec(file, buf, sectors, &have_more); |
| 128 | |
| 129 | /* |
| 130 | * If we reach EOF, the filesystem driver will have already closed |
| 131 | * the underlying file... this really should be cleaner. |
| 132 | */ |
| 133 | if (!have_more) { |
| 134 | _close_file(file); |
| 135 | *handle = 0; |
| 136 | } |
| 137 | |
| 138 | return bytes_read; |
| 139 | } |
| 140 | |
| 141 | int searchdir(const char *name, int flags) |
| 142 | { |
| 143 | static char root_name[] = "/"; |
| 144 | struct file *file; |
| 145 | char *path, *inode_name, *next_inode_name; |
| 146 | struct inode *tmp, *inode = NULL; |
| 147 | int symlink_count = MAX_SYMLINK_CNT; |
| 148 | |
| 149 | dprintf("searchdir: %s root: %p cwd: %p\n", |
| 150 | name, this_fs->root, this_fs->cwd); |
| 151 | |
| 152 | if (!(file = alloc_file())) |
| 153 | goto err_no_close; |
| 154 | file->fs = this_fs; |
| 155 | |
| 156 | /* if we have ->searchdir method, call it */ |
| 157 | if (file->fs->fs_ops->searchdir) { |
| 158 | file->fs->fs_ops->searchdir(name, flags, file); |
| 159 | |
| 160 | if (file->inode) |
| 161 | return file_to_handle(file); |
| 162 | else |
| 163 | goto err; |
| 164 | } |
| 165 | |
| 166 | /* else, try the generic-path-lookup method */ |
| 167 | |
| 168 | /* Copy the path */ |
| 169 | path = strdup(name); |
| 170 | if (!path) { |
| 171 | dprintf("searchdir: Couldn't copy path\n"); |
| 172 | goto err_path; |
| 173 | } |
| 174 | |
| 175 | /* Work with the current directory, by default */ |
| 176 | inode = get_inode(this_fs->cwd); |
| 177 | if (!inode) { |
| 178 | dprintf("searchdir: Couldn't use current directory\n"); |
| 179 | goto err_curdir; |
| 180 | } |
| 181 | |
| 182 | for (inode_name = path; inode_name; inode_name = next_inode_name) { |
| 183 | /* Root directory? */ |
| 184 | if (inode_name[0] == '/') { |
| 185 | next_inode_name = inode_name + 1; |
| 186 | inode_name = root_name; |
| 187 | } else { |
| 188 | /* Find the next inode name */ |
| 189 | next_inode_name = strchr(inode_name + 1, '/'); |
| 190 | if (next_inode_name) { |
| 191 | /* Terminate the current inode name and point to next */ |
| 192 | *next_inode_name++ = '\0'; |
| 193 | } |
| 194 | } |
| 195 | if (next_inode_name) { |
| 196 | /* Advance beyond redundant slashes */ |
| 197 | while (*next_inode_name == '/') |
| 198 | next_inode_name++; |
| 199 | |
| 200 | /* Check if we're at the end */ |
| 201 | if (*next_inode_name == '\0') |
| 202 | next_inode_name = NULL; |
| 203 | } |
| 204 | dprintf("searchdir: inode_name: %s\n", inode_name); |
| 205 | if (next_inode_name) |
| 206 | dprintf("searchdir: Remaining: %s\n", next_inode_name); |
| 207 | |
| 208 | /* Root directory? */ |
| 209 | if (inode_name[0] == '/') { |
| 210 | /* Release any chain that's already been established */ |
| 211 | put_inode(inode); |
| 212 | inode = get_inode(this_fs->root); |
| 213 | continue; |
| 214 | } |
| 215 | |
| 216 | /* Current directory? */ |
| 217 | if (!strncmp(inode_name, ".", sizeof ".")) |
| 218 | continue; |
| 219 | |
| 220 | /* Parent directory? */ |
| 221 | if (!strncmp(inode_name, "..", sizeof "..")) { |
| 222 | /* If there is no parent, just ignore it */ |
| 223 | if (!inode->parent) |
| 224 | continue; |
| 225 | |
| 226 | /* Add a reference to the parent so we can release the child */ |
| 227 | tmp = get_inode(inode->parent); |
| 228 | |
| 229 | /* Releasing the child will drop the parent back down to 1 */ |
| 230 | put_inode(inode); |
| 231 | |
| 232 | inode = tmp; |
| 233 | continue; |
| 234 | } |
| 235 | |
| 236 | /* Anything else */ |
| 237 | tmp = inode; |
| 238 | inode = this_fs->fs_ops->iget(inode_name, inode); |
| 239 | if (!inode) { |
| 240 | /* Failure. Release the chain */ |
| 241 | put_inode(tmp); |
| 242 | break; |
| 243 | } |
| 244 | |
| 245 | /* Sanity-check */ |
| 246 | if (inode->parent && inode->parent != tmp) { |
| 247 | dprintf("searchdir: iget returned a different parent\n"); |
| 248 | put_inode(inode); |
| 249 | inode = NULL; |
| 250 | put_inode(tmp); |
| 251 | break; |
| 252 | } |
| 253 | inode->parent = tmp; |
| 254 | inode->name = strdup(inode_name); |
| 255 | dprintf("searchdir: path component: %s\n", inode->name); |
| 256 | |
| 257 | /* Symlink handling */ |
| 258 | if (inode->mode == DT_LNK) { |
| 259 | char *new_path; |
| 260 | int new_len, copied; |
| 261 | |
| 262 | /* target path + NUL */ |
| 263 | new_len = inode->size + 1; |
| 264 | |
| 265 | if (next_inode_name) { |
| 266 | /* target path + slash + remaining + NUL */ |
| 267 | new_len += strlen(next_inode_name) + 1; |
| 268 | } |
| 269 | |
| 270 | if (!this_fs->fs_ops->readlink || |
| 271 | /* limit checks */ |
| 272 | --symlink_count == 0 || |
| 273 | new_len > MAX_SYMLINK_BUF) |
| 274 | goto err_new_len; |
| 275 | |
| 276 | new_path = malloc(new_len); |
| 277 | if (!new_path) |
| 278 | goto err_new_path; |
| 279 | |
| 280 | copied = this_fs->fs_ops->readlink(inode, new_path); |
| 281 | if (copied <= 0) |
| 282 | goto err_copied; |
| 283 | new_path[copied] = '\0'; |
| 284 | dprintf("searchdir: Symlink: %s\n", new_path); |
| 285 | |
| 286 | if (next_inode_name) { |
| 287 | new_path[copied] = '/'; |
| 288 | strcpy(new_path + copied + 1, next_inode_name); |
| 289 | dprintf("searchdir: New path: %s\n", new_path); |
| 290 | } |
| 291 | |
| 292 | free(path); |
| 293 | path = next_inode_name = new_path; |
| 294 | |
| 295 | /* Add a reference to the parent so we can release the child */ |
| 296 | tmp = get_inode(inode->parent); |
| 297 | |
| 298 | /* Releasing the child will drop the parent back down to 1 */ |
| 299 | put_inode(inode); |
| 300 | |
| 301 | inode = tmp; |
| 302 | continue; |
| 303 | err_copied: |
| 304 | free(new_path); |
| 305 | err_new_path: |
| 306 | err_new_len: |
| 307 | put_inode(inode); |
| 308 | inode = NULL; |
| 309 | break; |
| 310 | } |
| 311 | |
| 312 | /* If there's more to process, this should be a directory */ |
| 313 | if (next_inode_name && inode->mode != DT_DIR) { |
| 314 | dprintf("searchdir: Expected a directory\n"); |
| 315 | put_inode(inode); |
| 316 | inode = NULL; |
| 317 | break; |
| 318 | } |
| 319 | } |
| 320 | err_curdir: |
| 321 | free(path); |
| 322 | err_path: |
| 323 | if (!inode) { |
| 324 | dprintf("searchdir: Not found\n"); |
| 325 | goto err; |
| 326 | } |
| 327 | |
| 328 | file->inode = inode; |
| 329 | file->offset = 0; |
| 330 | |
| 331 | return file_to_handle(file); |
| 332 | |
| 333 | err: |
| 334 | dprintf("serachdir: error seraching file %s\n", name); |
| 335 | _close_file(file); |
| 336 | err_no_close: |
| 337 | return -1; |
| 338 | } |
| 339 | |
| 340 | __export int open_file(const char *name, int flags, struct com32_filedata *filedata) |
| 341 | { |
| 342 | int rv; |
| 343 | struct file *file; |
| 344 | char mangled_name[FILENAME_MAX]; |
| 345 | |
| 346 | dprintf("open_file %s\n", name); |
| 347 | |
| 348 | mangle_name(mangled_name, name); |
| 349 | rv = searchdir(mangled_name, flags); |
| 350 | |
| 351 | if (rv < 0) |
| 352 | return rv; |
| 353 | |
| 354 | file = handle_to_file(rv); |
| 355 | |
| 356 | if (file->inode->mode != DT_REG) { |
| 357 | _close_file(file); |
| 358 | return -1; |
| 359 | } |
| 360 | |
| 361 | filedata->size = file->inode->size; |
| 362 | filedata->blocklg2 = SECTOR_SHIFT(file->fs); |
| 363 | filedata->handle = rv; |
| 364 | |
| 365 | return rv; |
| 366 | } |
| 367 | |
| 368 | __export void close_file(uint16_t handle) |
| 369 | { |
| 370 | struct file *file; |
| 371 | |
| 372 | if (handle) { |
| 373 | file = handle_to_file(handle); |
| 374 | _close_file(file); |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | __export char *fs_uuid(void) |
| 379 | { |
| 380 | if (!this_fs || !this_fs->fs_ops || !this_fs->fs_ops->fs_uuid) |
| 381 | return NULL; |
| 382 | return this_fs->fs_ops->fs_uuid(this_fs); |
| 383 | } |
| 384 | |
| 385 | /* |
| 386 | * it will do: |
| 387 | * initialize the memory management function; |
| 388 | * set up the vfs fs structure; |
| 389 | * initialize the device structure; |
| 390 | * invoke the fs-specific init function; |
| 391 | * initialize the cache if we need one; |
| 392 | * finally, get the current inode for relative path looking. |
| 393 | * |
| 394 | * ops is a ptr list for several fs_ops |
| 395 | */ |
| 396 | __bss16 uint16_t SectorSize, SectorShift; |
| 397 | |
| 398 | void fs_init(const struct fs_ops **ops, void *priv) |
| 399 | { |
| 400 | static struct fs_info fs; /* The actual filesystem buffer */ |
| 401 | int blk_shift = -1; |
| 402 | struct device *dev = NULL; |
| 403 | |
| 404 | /* Default name for the root directory */ |
| 405 | fs.cwd_name[0] = '/'; |
| 406 | |
| 407 | while ((blk_shift < 0) && *ops) { |
| 408 | /* set up the fs stucture */ |
| 409 | fs.fs_ops = *ops; |
| 410 | |
| 411 | /* |
| 412 | * This boldly assumes that we don't mix FS_NODEV filesystems |
| 413 | * with FS_DEV filesystems... |
| 414 | */ |
| 415 | if (fs.fs_ops->fs_flags & FS_NODEV) { |
| 416 | fs.fs_dev = NULL; |
| 417 | } else { |
| 418 | if (!dev) |
| 419 | dev = device_init(priv); |
| 420 | fs.fs_dev = dev; |
| 421 | } |
| 422 | /* invoke the fs-specific init code */ |
| 423 | blk_shift = fs.fs_ops->fs_init(&fs); |
| 424 | ops++; |
| 425 | } |
| 426 | if (blk_shift < 0) { |
| 427 | printf("No valid file system found!\n"); |
| 428 | while (1) |
| 429 | ; |
| 430 | } |
| 431 | this_fs = &fs; |
| 432 | |
| 433 | /* initialize the cache only if it wasn't already initialized |
| 434 | * by the fs driver */ |
| 435 | if (fs.fs_dev && fs.fs_dev->cache_data && !fs.fs_dev->cache_init) |
| 436 | cache_init(fs.fs_dev, blk_shift); |
| 437 | |
| 438 | /* start out in the root directory */ |
| 439 | if (fs.fs_ops->iget_root) { |
| 440 | fs.root = fs.fs_ops->iget_root(&fs); |
| 441 | fs.cwd = get_inode(fs.root); |
| 442 | dprintf("init: root inode %p, cwd inode %p\n", fs.root, fs.cwd); |
| 443 | } |
| 444 | |
| 445 | if (fs.fs_ops->chdir_start) { |
| 446 | if (fs.fs_ops->chdir_start() < 0) |
| 447 | printf("Failed to chdir to start directory\n"); |
| 448 | } |
| 449 | |
| 450 | SectorShift = fs.sector_shift; |
| 451 | SectorSize = fs.sector_size; |
| 452 | |
| 453 | /* Add FSUUID=... string to cmdline */ |
| 454 | sysappend_set_fs_uuid(); |
| 455 | |
| 456 | } |