/* * Copyright (c) 2015, 2016, 2021, 2022 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * devices.c * Utility functions to handle devices, partitions, and filesystems. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "devices.h" struct harddisk** hds; size_t hds_count; const char* path_of_blockdevice(struct blockdevice* bdev) { return bdev->p ? bdev->p->path : bdev->hd->path; } const char* device_path_of_blockdevice(struct blockdevice* bdev) { while ( bdev->p ) bdev = bdev->p->parent_bdev; return bdev->hd->path; } void unscan_filesystem(struct blockdevice* bdev) { if ( bdev->fs ) { filesystem_release(bdev->fs); bdev->fs = NULL; } } void scan_filesystem(struct blockdevice* bdev) { enum filesystem_error fserr = blockdevice_inspect_filesystem(&bdev->fs, bdev); if ( fserr == FILESYSTEM_ERROR_ABSENT || fserr == FILESYSTEM_ERROR_UNRECOGNIZED ) return; if ( fserr != FILESYSTEM_ERROR_NONE ) return; // TODO: Perhaps print an error here? } void unscan_device(struct harddisk* hd) { if ( hd->bdev.pt ) { for ( size_t i = 0; i < hd->bdev.pt->partitions_count; i++ ) unscan_filesystem(&hd->bdev.pt->partitions[i]->bdev); partition_table_release(hd->bdev.pt); hd->bdev.pt = NULL; } if ( hd->bdev.fs ) unscan_filesystem(&hd->bdev); } void scan_device(struct harddisk* hd) { unscan_device(hd); struct blockdevice* bdev = &hd->bdev; enum partition_error parterr = blockdevice_get_partition_table(&bdev->pt, bdev); if ( parterr == PARTITION_ERROR_ABSENT || parterr == PARTITION_ERROR_UNRECOGNIZED ) { scan_filesystem(bdev); return; } else if ( parterr == PARTITION_ERROR_ERRNO || parterr != PARTITION_ERROR_NONE ) return; // TODO: Perhaps print an error here? for ( size_t i = 0; i < bdev->pt->partitions_count; i++ ) scan_filesystem(&bdev->pt->partitions[i]->bdev); } void unscan_devices(void) { for ( size_t i = 0; i < hds_count; i++ ) { unscan_device(hds[i]); harddisk_close(hds[i]); } hds_count = 0; free(hds); hds = NULL; } void scan_devices(void) { unscan_devices(); if ( !devices_open_all(&hds, &hds_count) ) { // TODO: How should callers deal with error conditions from here? warn("iterating devices"); } for ( size_t i = 0; i < hds_count; i++ ) scan_device(hds[i]); } struct filesystem* search_for_filesystem_by_uuid(const unsigned char* uuid) { for ( size_t di = 0; di < hds_count; di++ ) { struct blockdevice* dbdev = &hds[di]->bdev; if ( dbdev->fs ) { if ( (dbdev->fs->flags & FILESYSTEM_FLAG_UUID) && memcmp(dbdev->fs->uuid, uuid, 16) == 0 ) return dbdev->fs; } else if ( dbdev->pt ) { for ( size_t pi = 0; pi < dbdev->pt->partitions_count; pi++ ) { struct blockdevice* pbdev = &dbdev->pt->partitions[pi]->bdev; if ( !pbdev->fs ) continue; if ( (pbdev->fs->flags & FILESYSTEM_FLAG_UUID) && memcmp(pbdev->fs->uuid, uuid, 16) == 0 ) return pbdev->fs; } } } return NULL; } struct filesystem* search_for_filesystem_by_spec(const char* spec) { if ( strncmp(spec, "UUID=", strlen("UUID=")) == 0 ) { const char* uuid_string = spec + strlen("UUID="); if ( !uuid_validate(uuid_string) ) return NULL; unsigned char uuid[16]; uuid_from_string(uuid, uuid_string); return search_for_filesystem_by_uuid(uuid); } return NULL; } bool check_lacking_partition_table(void) { for ( size_t di = 0; di < hds_count; di++ ) { struct blockdevice* dbdev = &hds[di]->bdev; if ( dbdev->fs ) continue; if ( !dbdev->pt ) return true; } return false; } bool check_multiple_harddisks(void) { return 2 <= hds_count; } bool fsck(struct filesystem* fs) { const char* bdev_path = path_of_blockdevice(fs->bdev); printf("%s: Repairing filesystem due to inconsistency...\n", bdev_path); assert(fs->fsck); pid_t pid = fork(); if ( pid < 0 ) { warn("%s: Mandatory repair failed: fork", bdev_path); return false; } if ( pid == 0 ) { execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL); warn("%s: Failed to load filesystem checker: %s", bdev_path, fs->fsck); _Exit(127); } int code; if ( waitpid(pid, &code, 0) < 0 ) warn("waitpid"); else if ( WIFEXITED(code) && (WEXITSTATUS(code) == 0 || WEXITSTATUS(code) == 1) ) { // Successfully checked filesystem. fs->flags &= ~(FILESYSTEM_FLAG_FSCK_SHOULD | FILESYSTEM_FLAG_FSCK_MUST); return true; } else if ( WIFSIGNALED(code) ) warnx("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, strsignal(WTERMSIG(code))); else if ( !WIFEXITED(code) ) warnx("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, "Unexpected unusual termination"); else if ( WEXITSTATUS(code) == 127 ) warnx("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, "Filesystem checker is absent"); else if ( WEXITSTATUS(code) & 2 ) warnx("%s: Mandatory repair: %s: %s", bdev_path, fs->fsck, "System reboot is necessary"); else warnx("%s: Mandatory repair failed: %s: %s", bdev_path, fs->fsck, "Filesystem checker was unsuccessful"); return false; } static int sort_mountpoint(const void* a_ptr, const void* b_ptr) { const struct mountpoint* a = (const struct mountpoint*) a_ptr; const struct mountpoint* b = (const struct mountpoint*) b_ptr; return strcmp(a->entry.fs_file, b->entry.fs_file); } void free_mountpoints(struct mountpoint* mnts, size_t mnts_count) { for ( size_t i = 0; i < mnts_count; i++ ) { free(mnts[i].entry_line); free(mnts[i].absolute); } free(mnts); } bool load_mountpoints(const char* fstab_path, struct mountpoint** mountpoints_out, size_t* mountpoints_used_out) { FILE* fp = fopen(fstab_path, "r"); if ( !fp ) return false; struct mountpoint* mountpoints = malloc(sizeof(struct mountpoint)); if ( !mountpoints ) { fclose(fp); return false; } size_t mountpoints_used = 0; size_t mountpoints_length = 1; char* line = NULL; size_t line_size; ssize_t line_length; while ( 0 < (line_length = getline(&line, &line_size, fp)) ) { if ( line[line_length - 1] == '\n' ) line[--line_length] = '\0'; struct fstab fstabent; if ( !scanfsent(line, &fstabent) ) continue; if ( mountpoints_used == mountpoints_length ) { struct mountpoint* new_mountpoints = (struct mountpoint*) reallocarray(mountpoints, mountpoints_length, 2 * sizeof(struct mountpoint)); if ( !new_mountpoints ) { free_mountpoints(mountpoints, mountpoints_used); free(line); fclose(fp); return false; } mountpoints = new_mountpoints; mountpoints_length *= 2; } struct mountpoint* mountpoint = &mountpoints[mountpoints_used++]; memset(mountpoint, 0, sizeof(*mountpoint)); memcpy(&mountpoint->entry, &fstabent, sizeof(fstabent)); mountpoint->entry_line = line; mountpoint->pid = -1; if ( !(mountpoint->absolute = strdup(mountpoint->entry.fs_file)) ) { free_mountpoints(mountpoints, mountpoints_used); fclose(fp); return false; } line = NULL; line_size = 0; } bool failure = ferror(fp); free(line); fclose(fp); if ( failure ) { free_mountpoints(mountpoints, mountpoints_used); return false; } qsort(mountpoints, mountpoints_used, sizeof(struct mountpoint), sort_mountpoint); *mountpoints_out = mountpoints; *mountpoints_used_out = mountpoints_used; return true; } bool mountpoint_mount(struct mountpoint* mountpoint) { struct filesystem* fs = mountpoint->fs; // TODO: It would be ideal to get an exclusive lock so that no other // processes have currently mounted that filesystem. struct blockdevice* bdev = fs->bdev; const char* bdev_path = path_of_blockdevice(bdev); assert(bdev_path); if ( fs->flags & FILESYSTEM_FLAG_FSCK_MUST && !fsck(fs) ) { warnx("Failed to fsck %s", bdev_path); return false; } const char* pretend_where = mountpoint->entry.fs_file; const char* where = mountpoint->absolute; if ( !fs->driver ) { warnx("Failed mounting %s on %s: " "Don't know how to mount a %s filesystem", bdev_path, pretend_where, fs->fstype_name); return false; } struct stat st; if ( stat(where, &st) < 0 ) { warn("Failed mounting %s on %s: stat: %s", bdev_path, pretend_where, where); return false; } int readyfds[2]; if ( pipe(readyfds) < 0 ) { warn("Failed mounting %s on %s: pipe", bdev_path, pretend_where); return false; } if ( (mountpoint->pid = fork()) < 0 ) { warn("Failed mounting %s on %s: fork", bdev_path, pretend_where); close(readyfds[0]); close(readyfds[1]); return false; } if ( mountpoint->pid == 0 ) { close(readyfds[0]); char readyfdstr[sizeof(int) * 3]; snprintf(readyfdstr, sizeof(readyfdstr), "%d", readyfds[1]); if ( setenv("READYFD", readyfdstr, 1) < 0 ) { warn("Failed mounting %s on %s: setenv", bdev_path, pretend_where); _exit(127); } execlp(fs->driver, fs->driver, "--foreground", bdev_path, where, "--pretend-mount-path", pretend_where, (const char*) NULL); warn("Failed mount %s on %s: execvp: %s", bdev_path, pretend_where, fs->driver); _exit(127); } close(readyfds[1]); char c; struct stat newst; ssize_t amount = read(readyfds[0], &c, 1); close(readyfds[0]); if ( 0 <= amount ) { if ( !stat(where, &newst) ) { if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino ) return true; else warnx("Failed mount %s on %s: %s: " "No mounted filesystem appeared: %s", bdev_path, pretend_where, fs->driver, where); } else warn("Failed mounting %s on %s: %s, stat: %s", bdev_path, pretend_where, fs->driver, where); } else warn("Failed mounting %s on %s: %s, Failed to read readiness", bdev_path, pretend_where, fs->driver); if ( unmount(where, 0) < 0 ) { if ( errno != ENOMOUNT ) warn("Failed mounting %s on %s: unmount: %s", bdev_path, pretend_where, where); kill(mountpoint->pid, SIGQUIT); } int code; pid_t child = waitpid(mountpoint->pid, &code, 0); mountpoint->pid = -1; if ( child < 0 ) warn("Failed mounting %s on %s: %s: waitpid", bdev_path, pretend_where, fs->driver); else if ( WIFSIGNALED(code) ) warnx("Failed mounting %s on %s: %s: %s", bdev_path, pretend_where, fs->driver, strsignal(WTERMSIG(code))); else if ( !WIFEXITED(code) ) warnx("Failed mounting %s on %s: %s: Unexpected unusual termination", bdev_path, pretend_where, fs->driver); else if ( WEXITSTATUS(code) == 127 ) warnx("Failed mounting %s on %s: %s: " "Filesystem driver could not be executed", bdev_path, pretend_where, fs->driver); else if ( WEXITSTATUS(code) == 0 ) warnx("Failed mounting %s on %s: %s: Unexpected successful exit", bdev_path, pretend_where, fs->driver); else warnx("Failed mounting %s on %s: %s: Exited with status %i", bdev_path, pretend_where, fs->driver, WEXITSTATUS(code)); return false; } void mountpoint_unmount(struct mountpoint* mountpoint) { if ( mountpoint->pid < 0 ) return; if ( unmount(mountpoint->absolute, 0) < 0 && errno != ENOMOUNT ) warn("unmount: %s", mountpoint->entry.fs_file); else if ( errno == ENOMOUNT ) kill(mountpoint->pid, SIGQUIT); int code; if ( waitpid(mountpoint->pid, &code, 0) < 0 ) warn("waitpid"); mountpoint->pid = -1; }