/* * Copyright (c) 2015, 2016, 2017 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. * * disked.c * Disk editor. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include __attribute__((format(printf, 1, 2))) static char* print_string(const char* format, ...) { va_list ap; va_start(ap, format); char* ret; if ( vasprintf(&ret, format, ap) < 0 ) ret = NULL; va_end(ap); return ret; } static bool verify_mountpoint(const char* mountpoint) { size_t index = 0; if ( mountpoint[index++] != '/' ) return false; while ( mountpoint[index] ) { if ( mountpoint[index] == '.' ) { index++; if ( mountpoint[index] == '.' ) index++; if ( !mountpoint[index] || mountpoint[index] == '/' ) return false; } while ( mountpoint[index] && mountpoint[index] != '/' ) index++; while ( mountpoint[index] == '/' ) index++; } return true; } static void simplify_mountpoint(char* mountpoint) { bool slash_pending = false; size_t out = 0; for ( size_t in = 0; mountpoint[in]; in++ ) { if ( mountpoint[in] == '/' ) { if ( out == 0 ) { mountpoint[out++] = '/'; while ( mountpoint[in] == '/' ) in++; in--; continue; } slash_pending = true; continue; } if ( slash_pending ) { mountpoint[out++] = '/'; slash_pending = false; } mountpoint[out++] = mountpoint[in]; } mountpoint[out] = '\0'; } static char* format_bytes_amount(uintmax_t num_bytes) { uintmax_t value = num_bytes; uintmax_t value_fraction = 0; uintmax_t exponent = 1024; char suffixes[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; size_t num_suffixes = sizeof(suffixes) / sizeof(suffixes[0]); size_t suffix_index = 0; while ( exponent <= value && suffix_index + 1 < num_suffixes) { value_fraction = value % exponent; value /= exponent; suffix_index++; } char suffix_str[] = { suffixes[suffix_index], 'i', 'B', '\0' }; char value_fraction_char = '0' + (value_fraction / (1024 / 10 + 1)) % 10; char* result; if ( asprintf(&result, "%ju.%c %s", value, value_fraction_char, suffix_str) < 0 ) return NULL; return result; } static size_t string_display_length(const char* str) { size_t display_length = 0; mbstate_t ps; memset(&ps, 0, sizeof(ps)); while ( true ) { wchar_t wc; size_t amount = mbrtowc(&wc, str, SIZE_MAX, &ps); if ( amount == 0 ) break; if ( amount == (size_t) -1 || amount == (size_t) -2 ) { display_length++; str++; memset(&ps, 0, sizeof(ps)); continue; } int width = wcwidth(wc); if ( width < 0 ) width = 0; if ( SIZE_MAX - display_length < (size_t) width ) display_length = SIZE_MAX; else display_length += (size_t) width; str += amount; } return display_length; } static void split_arguments(char* cmd, size_t* argc_ptr, char** argv, size_t argc_max) { size_t argc = 0; size_t cmd_offset = 0; while ( cmd[cmd_offset] ) { while ( cmd[cmd_offset] && isspace((unsigned char) cmd[cmd_offset]) ) { cmd[cmd_offset] = '\0'; cmd_offset++; } if ( !cmd[cmd_offset] ) break; char* out = cmd + cmd_offset; size_t out_offset = 0; if ( argc < argc_max ) argv[argc++] = out; bool escape = false; bool quote_single = false; bool quote_double = false; while ( cmd[cmd_offset] ) { if ( !escape && !quote_single && cmd[cmd_offset] == '\\' ) { cmd_offset++; escape = true; } else if ( !escape && !quote_double && cmd[cmd_offset] == '\'' ) { cmd_offset++; quote_single = !quote_single; } else if ( !escape && !quote_single && cmd[cmd_offset] == '"' ) { cmd_offset++; quote_double = !quote_double; } else if ( !(escape || quote_single || quote_double) && isspace((unsigned char) cmd[cmd_offset]) ) { break; } else { out[out_offset++] = cmd[cmd_offset++]; escape = false; } } char last_c = cmd[cmd_offset]; out[out_offset] = '\0'; if ( !last_c ) break; cmd[cmd_offset++] = '\0'; } *argc_ptr = argc; } static const char* device_name(const char* name) { if ( !strncmp(name, "/dev/", strlen("/dev/")) ) return name + strlen("/dev/"); return name; } static void display_rows_columns(char* (*format)(void*, size_t, size_t), void* ctx, size_t rows, size_t columns) { size_t* widths = (size_t*) reallocarray(NULL, sizeof(size_t), columns); assert(widths); for ( size_t c = 0; c < columns; c++ ) widths[c] = 0; for ( size_t r = 0; r < rows; r++ ) { for ( size_t c = 0; c < columns; c++ ) { char* entry = format(ctx, r, c); assert(entry); size_t width = string_display_length(entry); if ( widths[c] < width ) widths[c] = width; free(entry); } } for ( size_t r = 0; r < rows; r++ ) { for ( size_t c = 0; c < columns; c++ ) { char* entry = format(ctx, r, c); assert(entry); size_t width = string_display_length(entry); printf("%s", entry); if ( c + 1 != columns ) { for ( size_t i = width; i < widths[c]; i++ ) putchar(' '); printf(" "); } free(entry); } printf("\n"); } free(widths); } static void text(const char* str) { fflush(stdout); struct winsize ws; struct wincurpos wcp; if ( tcgetwinsize(1, &ws) < 0 || tcgetwincurpos(1, &wcp) < 0 ) { printf("%s", str); return; } size_t columns = ws.ws_col; size_t column = wcp.wcp_col; bool blank = false; while ( str[0] ) { if ( str[0] == '\e' ) { size_t length = 1; while ( str[length] == '[' || str[length] == ';' || ('0' <= str[length] && str[length] <= '9') ) length++; if ( 64 <= str[length] && str[length] <= 126 ) length++; fwrite(str, 1, length, stdout); str += length; continue; } else if ( str[0] == '\n' ) { putchar('\n'); blank = false; column = 0; str++; continue; } else if ( isblank((unsigned char) str[0]) ) { blank = true; str++; continue; } size_t word_length = 0; size_t word_columns = 0; mbstate_t ps = { 0 }; while ( str[word_length] && str[word_length] != '\n' && !isblank((unsigned char) str[word_length]) ) { wchar_t wc; size_t amount = mbrtowc(&wc, str + word_length, SIZE_MAX, &ps); if ( amount == (size_t) -2 ) break; if ( amount == (size_t) -1 ) { memset(&ps, 0, sizeof(ps)); amount = 1; } if ( amount == (size_t) 0 ) break; word_length += amount; int width = wcwidth(wc); if ( width < 0 ) continue; word_columns += width; } if ( (column && blank ? 1 : 0) + word_columns <= columns - column ) { if ( column && blank ) { putchar(' '); column++; } blank = false; fwrite(str, 1, word_length, stdout); column += word_columns; if ( column == columns ) column = 0; } else { if ( column != 0 && column != columns ) putchar('\n'); column = 0; blank = false; fwrite(str, 1, word_length, stdout); column += word_columns; column %= columns; } str += word_length; } fflush(stdout); } static void textf(const char* format, ...) { va_list ap; va_start(ap, format); char* str; int len = vasprintf(&str, format, ap); va_end(ap); if ( len < 0 ) { vprintf(format, ap); return; } text(str); free(str); } static void prompt(char* buffer, size_t buffer_size, const char* question, const char* answer) { while ( true ) { printf("\e[1m"); fflush(stdout); text(question); if ( answer ) printf(" [%s] ", answer); else printf(" "); fflush(stdout); fgets(buffer, buffer_size, stdin); printf("\e[22m"); fflush(stdout); size_t buffer_length = strlen(buffer); if ( buffer_length && buffer[buffer_length-1] == '\n' ) buffer[--buffer_length] = '\0'; while ( buffer_length && buffer[buffer_length-1] == ' ' ) buffer[--buffer_length] = '\0'; if ( !strcmp(buffer, "") ) { if ( !answer ) continue; strlcpy(buffer, answer, buffer_size); } break; } } static bool remove_partition_device(const char* path) { // TODO: Refuse to do this if the partition are currently in use. if ( unmount(path, UNMOUNT_NOFOLLOW) < 0 && errno != ENOMOUNT ) { warn("unmount: %s", path); return false; } if ( unlink(path) < 0 ) { warn("unlink: %s", path); return false; } return true; } static void remove_partition_devices(const char* path) { const char* name = path; for ( size_t i = 0; path[i]; i++ ) if ( path[i] == '/' ) name = path + i + 1; size_t name_length = strlen(name); char* dir_path = strdup(path); if ( !dir_path ) { warn("%s", dir_path); return; // TODO: Error. } dirname(dir_path); DIR* dir = opendir(dir_path); struct dirent* entry; while ( (errno = 0, entry = readdir(dir)) ) { if ( strncmp(entry->d_name, name, name_length) != 0 ) continue; if ( entry->d_name[name_length] != 'p' ) continue; bool all_digits = true; for ( size_t i = name_length + 1; all_digits && entry->d_name[i]; i++ ) if ( !('0' <= entry->d_name[i] && entry->d_name[i] <= '9') ) all_digits = false; if ( !all_digits ) continue; // TODO: Refuse to do this if the partitions are currently in use. if ( unmountat(dirfd(dir), entry->d_name, UNMOUNT_NOFOLLOW) < 0 && errno != ENOMOUNT ) { warn("unmount: %s/%s", dir_path, entry->d_name); // TODO: Warn/error. } if ( unlinkat(dirfd(dir), entry->d_name, 0) < 0 ) { warn("unlink: %s/%s", dir_path, entry->d_name); // TODO: Warn/error. } rewinddir(dir); } if ( errno ) { warn("readdir: %s", dir_path); // TODO: Error. } closedir(dir); free(dir_path); } static int harddisk_compare_path(const void* a_ptr, const void* b_ptr) { struct harddisk* a = *((struct harddisk**) a_ptr); struct harddisk* b = *((struct harddisk**) b_ptr); return strcmp(a->path, b->path); } struct device_area { off_t start; off_t length; off_t extended_start; off_t ebr_off; off_t ebr_move_off; struct partition* p; char* filesystem; char* mountpoint; size_t ebr_index; size_t ebr_move_index; bool inside_extended; }; static int device_area_compare_start(const void* a_ptr, const void* b_ptr) { const struct device_area* a = (const struct device_area*) a_ptr; const struct device_area* b = (const struct device_area*) b_ptr; if ( a->start < b->start ) return -1; if ( a->start > b->start ) return 1; return 0; } static bool match_fstab_device(const char* device, struct blockdevice* bdev) { if ( strncmp(device, "UUID=", strlen("UUID=")) == 0 ) { device += strlen("UUID="); if ( !bdev->fs ) return false; if ( !(bdev->fs->flags & FILESYSTEM_FLAG_UUID) ) return false; if ( !uuid_validate(device) ) return false; unsigned char uuid[16]; uuid_from_string(uuid, device); if ( memcmp(bdev->fs->uuid, uuid, 16) != 0 ) return false; return true; } else if ( bdev->p && !strcmp(device, bdev->p->path) ) return true; else if ( !bdev->p && bdev->hd && !strcmp(device, bdev->hd->path) ) return true; return false; } struct rewrite { FILE* in; FILE* out; const char* in_path; char* out_path; }; bool rewrite_begin(struct rewrite* rewr, const char* in_path) { memset(rewr, 0, sizeof(*rewr)); rewr->in_path = in_path; if ( !(rewr->in = fopen(rewr->in_path, "r")) ) return false; if ( asprintf(&rewr->out_path, "%s.XXXXXX", in_path) < 0 ) return fclose(rewr->in), false; int out_fd = mkstemp(rewr->out_path); if ( out_fd < 0 ) return free(rewr->out_path), fclose(rewr->in), false; if ( !(rewr->out = fdopen(out_fd, "w")) ) return close(out_fd), free(rewr->out_path), fclose(rewr->in), false; return true; } void rewrite_abort(struct rewrite* rewr) { fclose(rewr->in); fclose(rewr->out); unlink(rewr->out_path); free(rewr->out_path); } bool rewrite_finish(struct rewrite* rewr) { struct stat in_st; if ( ferror(rewr->out) || fflush(rewr->out) == EOF ) return rewrite_abort(rewr), false; if ( fstat(fileno(rewr->in), &in_st) < 0 ) return rewrite_abort(rewr), false; mode_t mode = in_st.st_mode & 0777; if ( in_st.st_uid != getuid() || in_st.st_gid != getgid() ) mode &= ~getumask(); if ( fchmod(fileno(rewr->out), mode) < 0 ) return rewrite_abort(rewr), false; if ( rename(rewr->out_path, rewr->in_path) < 0 ) return rewrite_abort(rewr), false; fclose(rewr->in); fclose(rewr->out); free(rewr->out_path); return true; } static bool interactive; static bool quitting; static struct harddisk** hds; static size_t hds_count; static struct harddisk* current_hd; static enum partition_table_type current_pt_type; static struct partition_table* current_pt; static size_t current_areas_count; static struct device_area* current_areas; static const char* fstab_path = "/etc/fstab"; __attribute__((format(printf, 1, 2))) static void command_error(const char* format, ...) { va_list ap; va_start(ap, format); if ( !interactive ) verr(1, format, ap); vfprintf(stderr, format, ap); fprintf(stderr, ": %s\n", strerror(errno)); va_end(ap); } __attribute__((format(printf, 1, 2))) static void command_errorx(const char* format, ...) { va_list ap; va_start(ap, format); if ( !interactive ) verrx(1, format, ap); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); va_end(ap); } // TODO: Finish this and add decimal support. static bool parse_disk_quantity(off_t* out, const char* string, off_t max) { if ( *string && isspace((unsigned char) *string) ) string++; const char* end; bool from_end = false; errno = 0; intmax_t value = strtoimax(string, (char**) &end, 10); if ( value == INTMAX_MIN || errno ) { if ( !errno ) errno = ERANGE; command_error("Parsing `%s'", string); return false; } if ( value < 0 ) { value = -value; from_end = true; } string = end; if ( *string && isspace((unsigned char) *string) ) string++; if ( *string == '.' ) { string++; // TODO: Support this! if ( strtoimax(string, (char**) &end, 10) < 0 ) return false; string = end; } if ( *string == '%' ) { string++; if ( 100 < value ) return false; if ( value == 100 ) value = max; else value = (max * value) / 100; } else if ( *string == 'b' || *string == 'B' ) { } else if ( *string == 'k' || *string == 'K' ) { string++; if ( *string == 'i' ) string++; if ( *string == 'b' || *string == 'B' ) string++; value = value * 1024LL; } else if ( *string == 'm' || *string == 'M' || !*string || isspace((unsigned char) *string) ) { if ( *string ) string++; if ( *string == 'i' ) string++; if ( *string == 'b' || *string == 'B' ) string++; value = value * (1024LL * 1024LL); } else if ( *string == 'g' || *string == 'G' ) { string++; if ( *string == 'i' ) string++; if ( *string == 'b' || *string == 'B' ) string++; value = value * (1024LL * 1024LL * 1024LL); } else if ( *string == 't' || *string == 'T' ) { string++; if ( *string == 'i' ) string++; if ( *string == 'b' || *string == 'B' ) string++; value = value * (1024LL * 1024LL * 1024LL * 1024LL); } else if ( *string == 'p' || *string == 'P' ) { string++; if ( *string == 'i' ) string++; if ( *string == 'b' || *string == 'B' ) string++; value = value * (1024LL * 1024LL * 1024LL * 1024LL * 1024LL); } if ( *string && isspace((unsigned char) *string) ) string++; if ( *string ) return false; if ( max < value ) return false; if ( from_end ) value = max - value; uintmax_t uvalue = value; uintmax_t mask = ~(UINTMAX_C(1048576) - 1); uvalue = -(-value & mask); value = (off_t) uvalue; return *out = value, true; } static bool lookup_fstab_by_blockdevice(struct fstab* out_fsent, char** out_storage, struct blockdevice* bdev) { FILE* fstab_fp = fopen(fstab_path, "r"); if ( !fstab_fp ) return false; char* line = NULL; size_t line_size = 0; ssize_t line_length; while ( 0 < (line_length = getline(&line, &line_size, fstab_fp)) ) { if ( line[line_length - 1] == '\n' ) line[--line_length] = '\0'; if ( !scanfsent(line, out_fsent) ) continue; if ( match_fstab_device(out_fsent->fs_spec, bdev) ) { *out_storage = line; fclose(fstab_fp); return true; } } free(line); fclose(fstab_fp); return false; } static bool remove_blockdevice_from_fstab(struct blockdevice* bdev) { struct rewrite rewr; if ( !rewrite_begin(&rewr, fstab_path) ) { if ( errno == ENOENT ) return true; return false; } char* line = NULL; size_t line_size = 0; ssize_t line_length; while ( 0 < (line_length = getline(&line, &line_size, rewr.in)) ) { if ( line[line_length - 1] == '\n' ) line[--line_length] = '\0'; char* dup = strdup(line); if ( !dup ) return rewrite_abort(&rewr), false; struct fstab fsent; if ( !scanfsent(dup, &fsent) || !match_fstab_device(fsent.fs_spec, bdev) ) fprintf(rewr.out, "%s\n", line); free(dup); } free(line); if ( ferror(rewr.in) ) return rewrite_abort(&rewr), false; return rewrite_finish(&rewr); } static void print_blockdevice_fsent(FILE* fp, struct blockdevice* bdev, const char* mountpoint) { char uuid[5 + UUID_STRING_LENGTH + 1]; const char* spec = bdev->p ? bdev->p->path : bdev->hd->path; if ( bdev->fs->flags & FILESYSTEM_FLAG_UUID ) { strcpy(uuid, "UUID="); uuid_to_string(bdev->fs->uuid, uuid + 5); spec = uuid; } fprintf(fp, "%s %s %s %s %i %i\n", spec, mountpoint, bdev->fs->fstype_name, "rw", 1, !strcmp(mountpoint, "/") ? 1 : 2); } static bool add_blockdevice_to_fstab(struct blockdevice* bdev, const char* mountpoint) { assert(bdev->fs); struct rewrite rewr; if ( !rewrite_begin(&rewr, fstab_path) ) { if ( errno == ENOENT ) { FILE* fp = fopen(fstab_path, "w"); if ( !fp ) return false; print_blockdevice_fsent(fp, bdev, mountpoint); if ( ferror(fp) || fflush(fp) == EOF ) return fclose(fp), false; fclose(fp); return true; } return false; } char* line = NULL; size_t line_size = 0; ssize_t line_length; bool found = false; while ( 0 < (line_length = getline(&line, &line_size, rewr.in)) ) { if ( line[line_length - 1] == '\n' ) line[--line_length] = '\0'; char* dup = strdup(line); if ( !dup ) return rewrite_abort(&rewr), false; struct fstab fsent; if ( !scanfsent(dup, &fsent) ) { fprintf(rewr.out, "%s\n", line); } else if ( match_fstab_device(fsent.fs_spec, bdev) ) { fprintf(rewr.out, "%s %s %s %s %i %i\n", fsent.fs_spec, mountpoint, fsent.fs_vfstype, fsent.fs_mntops, fsent.fs_freq, fsent.fs_passno); found = true; } else if ( !strcmp(fsent.fs_file, mountpoint) ) { // Remove conflicting mountpoint. } else { fprintf(rewr.out, "%s\n", line); } free(dup); } free(line); if ( ferror(rewr.in) ) return rewrite_abort(&rewr), false; if ( !found ) print_blockdevice_fsent(rewr.out, bdev, mountpoint); return rewrite_finish(&rewr); } static void unscan_partition(struct partition* p) { struct blockdevice* bdev = &p->bdev; filesystem_release(bdev->fs); bdev->fs_error = FILESYSTEM_ERROR_NONE; } static void unscan_device(void) { if ( !current_hd ) return; for ( size_t i = 0; i < current_areas_count; i++ ) { free(current_areas[i].filesystem); free(current_areas[i].mountpoint); } current_areas_count = 0; free(current_areas); current_areas = NULL; if ( current_pt ) { for ( size_t i = 0; i < current_pt->partitions_count; i++ ) unscan_partition(current_pt->partitions[i]); partition_table_release(current_pt); } current_pt = NULL; current_pt_type = PARTITION_TABLE_TYPE_UNKNOWN; current_hd->bdev.pt_error = PARTITION_ERROR_NONE; } static void scan_partition(struct partition* p) { unscan_partition(p); struct blockdevice* bdev = &p->bdev; bdev->fs_error = blockdevice_inspect_filesystem(&bdev->fs, bdev); if ( bdev->fs_error == FILESYSTEM_ERROR_ABSENT || bdev->fs_error == FILESYSTEM_ERROR_UNRECOGNIZED ) return; if ( bdev->fs_error != FILESYSTEM_ERROR_NONE ) return command_errorx("Scanning `%s': %s", device_name(p->path), filesystem_error_string(bdev->fs_error)); } static void scan_device(void) { if ( !current_hd ) return; unscan_device(); struct blockdevice* bdev = ¤t_hd->bdev; if ( !blockdevice_probe_partition_table_type(¤t_pt_type, bdev) ) { // TODO: Try probe for a filesystem here to see if one covers the whole // device. command_error("Scanning `%s'", device_name(current_hd->path)); current_hd = NULL; current_pt_type = PARTITION_TABLE_TYPE_UNKNOWN; return; } bdev->pt_error = blockdevice_get_partition_table(¤t_pt, bdev); if ( bdev->pt_error != PARTITION_ERROR_NONE ) { if ( bdev->pt_error != PARTITION_ERROR_ABSENT && bdev->pt_error != PARTITION_ERROR_UNRECOGNIZED ) command_errorx("Scanning `%s': %s", device_name(current_hd->path), partition_error_string(bdev->pt_error)); partition_table_release(current_pt); current_pt = NULL; return; } current_pt_type = current_pt->type; // TODO: In case of GPT, verify the header is supported for write (version // check, just using it blindly is safe for read compatibility, but // we need to refuse updating the GPT if uses extensions). Then after // deciding this, check this condition in all commands that modify // the partition table. for ( size_t i = 0; i < current_pt->partitions_count; i++ ) scan_partition(current_pt->partitions[i]); size_t areas_length; size_t partitions_count = current_pt->partitions_count; if ( __builtin_mul_overflow(2, partitions_count, &areas_length) || __builtin_add_overflow(1, areas_length, &areas_length ) ) { errno = EOVERFLOW; command_error("Scanning `%s'", device_name(current_hd->path)); return; } if ( current_pt_type == PARTITION_TABLE_TYPE_MBR ) { struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; areas_length += mbrpt->ebr_chain_count * 2; } current_areas = (struct device_area*) reallocarray(NULL, sizeof(struct device_area), areas_length); if ( !current_areas ) { command_error("malloc"); partition_table_release(current_pt); current_pt = NULL; return; } struct device_area* sort_areas = current_areas + areas_length - current_pt->partitions_count; for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { struct device_area* area = &sort_areas[i]; struct partition* p = current_pt->partitions[i]; memset(area, 0, sizeof(*area)); area->start = p->start; area->length = p->length; area->p = p; area->inside_extended = p->type == PARTITION_TYPE_LOGICAL; } qsort(sort_areas, current_pt->partitions_count, sizeof(struct device_area), device_area_compare_start); off_t last_end = current_pt->usable_start; current_areas_count = 0; for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { struct device_area* area = &sort_areas[i]; if ( area->p->type == PARTITION_TYPE_LOGICAL ) continue; if ( last_end < area->start ) { struct device_area* hole = ¤t_areas[current_areas_count++]; memset(hole, 0, sizeof(*hole)); hole->start = last_end; hole->length = area->start - last_end; } current_areas[current_areas_count++] = *area; last_end = area->start + area->length; if ( area->p->type == PARTITION_TYPE_EXTENDED && current_pt_type == PARTITION_TABLE_TYPE_MBR ) { off_t extended_start = area->start; off_t extended_end = area->start + area->length; struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; assert(1 <= mbrpt->ebr_chain_count); size_t ebr_i = 0; size_t larea_i = i + 1; size_t new_part_ebr_i = 0; off_t offset = extended_start; while ( true ) { struct mbr_ebr_link* ebr = NULL; if ( ebr_i < mbrpt->ebr_chain_count ) ebr = &mbrpt->ebr_chain[ebr_i]; struct device_area* larea = NULL; if ( larea_i < current_pt->partitions_count && sort_areas[larea_i].p->type == PARTITION_TYPE_LOGICAL ) larea = &sort_areas[larea_i]; off_t ebr_start = ebr ? ebr->offset : extended_end; off_t larea_start = larea ? larea->start : extended_end; off_t dist_ebr = ebr_start - offset; off_t dist_larea = larea_start - offset; off_t dist = dist_larea < dist_ebr ? dist_larea : dist_ebr; bool next_is_larea = larea && dist_larea < dist_ebr; bool next_is_ebr = ebr && dist_ebr < dist_larea; if ( next_is_ebr ) { struct mbr_partition p; memcpy(&p, ebr->ebr.partitions[0], sizeof(p)); mbr_partition_decode(&p); new_part_ebr_i = ebr_i; ebr_i++; continue; } if ( current_hd->logical_block_size < dist ) { assert(ebr_i); struct device_area* hole = ¤t_areas[current_areas_count]; memset(hole, 0, sizeof(*hole)); hole->ebr_index = new_part_ebr_i; hole->ebr_off = offset; hole->start = offset + current_hd->logical_block_size; hole->length = offset + dist - hole->start; hole->extended_start = extended_start; if ( next_is_larea ) { hole->length -= current_hd->logical_block_size; hole->ebr_move_off = hole->start + hole->length; assert(ebr_i); hole->ebr_move_index = ebr_i - 1; } hole->inside_extended = true; if ( 0 <= hole->length ) current_areas_count++; } if ( next_is_larea ) { current_areas[current_areas_count++] = *larea; offset = larea->start + larea->length; larea_i++; new_part_ebr_i++; } else break; } i = larea_i - 1; } } if ( last_end < current_pt->usable_end ) { struct device_area* hole = ¤t_areas[current_areas_count++]; memset(hole, 0, sizeof(*hole)); hole->start = last_end; hole->length = current_pt->usable_end - last_end; } size_t new_areas_count = 0; for ( size_t i = 0; i < current_areas_count; i++ ) { if ( !current_areas[i].p ) { uintmax_t mask = ~(UINTMAX_C(1048576) - 1); off_t start = current_areas[i].start; uintmax_t aligned = (uintmax_t) start; aligned = -(-aligned & mask); off_t start_aligned = (off_t) aligned; if ( current_areas[i].length < start_aligned - start ) continue; current_areas[i].start = start_aligned; current_areas[i].length -= start_aligned - start; current_areas[i].length &= mask; if ( current_areas[i].length == 0 ) continue; } if ( new_areas_count != i ) current_areas[new_areas_count] = current_areas[i]; new_areas_count++; } current_areas_count = new_areas_count; } static void switch_device(struct harddisk* hd) { if ( current_hd ) { unscan_device(); current_hd = NULL; } if ( !(current_hd = hd) ) return; scan_device(); } static bool lookup_partition_by_string(struct partition** out, const char* argv0, const char* numstr) { char* end; unsigned long part_index = strtoul(numstr, &end, 10); if ( *end ) { command_errorx("%s: Invalid partition number `%s'", argv0, numstr); return false; } struct partition* part = NULL; for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->index == part_index ) { part = current_pt->partitions[i]; break; } } if ( !part) { command_errorx("%s: No such partition `%sp%lu'", argv0, device_name(current_hd->path), part_index); return false; } return *out = part, true; } bool gpt_update(const char* argv0, struct gpt_partition_table* gptpt) { size_t header_size; blksize_t logical_block_size = current_hd->logical_block_size; struct gpt pri_gpt; memcpy(&pri_gpt, &gptpt->gpt, sizeof(pri_gpt)); gpt_decode(&pri_gpt); size_t rpt_size = (size_t) pri_gpt.number_of_partition_entries * (size_t) pri_gpt.size_of_partition_entry; uint32_t rpt_checksum = gpt_crc32(gptpt->rpt, rpt_size); uint64_t pri_gpt_lba = 1; off_t pri_gpt_off = (off_t) logical_block_size * (off_t) pri_gpt_lba; uint64_t alt_gpt_lba = pri_gpt.alternate_lba; off_t alt_gpt_off = (off_t) logical_block_size * (off_t) alt_gpt_lba; struct gpt alt_gpt; if ( preadall(current_hd->fd, &alt_gpt, sizeof(alt_gpt), alt_gpt_off) < sizeof(alt_gpt) ) { command_error("%s: %s: read", argv0, device_name(current_hd->path)); return false; } gpt_decode(&alt_gpt); // TODO: Validate the alternate gpt. uint64_t alt_rpt_lba = alt_gpt.partition_entry_lba; off_t alt_rpt_off = (off_t) logical_block_size * (off_t) alt_rpt_lba; if ( pwriteall(current_hd->fd, gptpt->rpt, rpt_size, alt_rpt_off) < rpt_size ) { command_error("%s: %s: write", argv0, device_name(current_hd->path)); scan_device(); return false; } memcpy(&alt_gpt, &gptpt->gpt, sizeof(alt_gpt)); gpt_decode(&alt_gpt); alt_gpt.header_crc32 = 0; alt_gpt.my_lba = alt_gpt_lba; alt_gpt.alternate_lba = pri_gpt_lba; alt_gpt.partition_entry_lba = alt_rpt_lba; alt_gpt.partition_entry_array_crc32 = rpt_checksum; header_size = alt_gpt.header_size; gpt_encode(&alt_gpt); alt_gpt.header_crc32 = htole32(gpt_crc32(&alt_gpt, header_size)); if ( pwriteall(current_hd->fd, &alt_gpt, sizeof(alt_gpt), alt_gpt_off) < sizeof(alt_gpt) ) { command_error("%s: %s: write", argv0, device_name(current_hd->path)); scan_device(); return false; } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv0, device_name(current_hd->path)); scan_device(); return false; } uint64_t pri_rpt_lba = pri_gpt.partition_entry_lba; off_t pri_rpt_off = (off_t) logical_block_size * (off_t) pri_rpt_lba; if ( pwriteall(current_hd->fd, gptpt->rpt, rpt_size, pri_rpt_off) < rpt_size ) { command_error("%s: %s: write", argv0, device_name(current_hd->path)); scan_device(); return false; } memcpy(&pri_gpt, &gptpt->gpt, sizeof(pri_gpt)); gpt_decode(&pri_gpt); pri_gpt.header_crc32 = 0; pri_gpt.my_lba = pri_gpt_lba; pri_gpt.alternate_lba = alt_gpt_lba; pri_gpt.partition_entry_lba = pri_rpt_lba; pri_gpt.partition_entry_array_crc32 = rpt_checksum; header_size = pri_gpt.header_size; gpt_encode(&pri_gpt); pri_gpt.header_crc32 = htole32(gpt_crc32(&pri_gpt, header_size)); if ( pwriteall(current_hd->fd, &pri_gpt, sizeof(pri_gpt), pri_gpt_off) < sizeof(pri_gpt) ) { command_error("%s: %s: write", argv0, device_name(current_hd->path)); scan_device(); return false; } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv0, device_name(current_hd->path)); scan_device(); return false; } return true; } bool create_partition_device(const char* argv0, const struct partition* p) { int mountfd = open(p->path, O_RDONLY | O_CREAT | O_EXCL); if ( mountfd < 0 ) { command_error("%s: %s", argv0, p->path); return false; } int partfd = mkpartition(current_hd->fd, p->start, p->length); if ( partfd < 0 ) { close(mountfd); command_error("%s: mkpartition: %s", argv0, p->path); return false; } if ( fsm_fsbind(partfd, mountfd, 0) < 0 ) { command_error("%s: fsbind: %s", argv0, p->path); return false; } close(partfd); close(mountfd); return true; } struct command { const char* name; void (*function)(size_t argc, char** argv); const char* flags; }; static const struct command commands[]; static const size_t commands_count; static void on_device(size_t argc, char** argv) { if ( argc < 2 ) { printf("%s\n", current_hd ? device_name(current_hd->path) : "none"); return; } const char* name = argv[1]; if ( 2 < argc ) { command_errorx("%s: extra operand `%s'", argv[0], argv[2]); return; } if ( !strcmp(name, "none") ) { current_hd = NULL; return; } for ( size_t i = 0; i < hds_count; i++ ) { char buf[sizeof(i) * 3]; snprintf(buf, sizeof(buf), "%zu", i); if ( strcmp(name, hds[i]->path) != 0 && strcmp(name, device_name(hds[i]->path)) != 0 && strcmp(name, buf) != 0 ) continue; switch_device(hds[i]); return; } command_errorx("%s: No such device `%s'", argv[0], name); } static char* display_harddisk_format(void* ctx, size_t row, size_t column) { if ( row == 0 ) { switch ( column ) { case 0: return strdup("#"); case 1: return strdup("DEVICE"); case 2: return strdup("SIZE"); case 3: return strdup("MODEL"); case 4: return strdup("SERIAL"); default: return NULL; } } struct harddisk** hds = (struct harddisk**) ctx; struct harddisk* hd = hds[row-1]; switch ( column ) { case 0: return print_string("%zu", row - 1); case 1: return strdup(device_name(hd->path)); case 2: return format_bytes_amount((uintmax_t) hd->st.st_size); case 3: return strdup(hd->model); case 4: return strdup(hd->serial); default: return NULL; } } static void on_devices(size_t argc, char** argv) { (void) argc; (void) argv; qsort(hds, hds_count, sizeof(struct harddisk*), harddisk_compare_path); display_rows_columns(display_harddisk_format, hds, 1 + hds_count, 5); } static void on_fsck(size_t argc, char** argv) { if ( argc < 2 ) { command_errorx("%s: No partition specified", argv[0]); return; } struct partition* p; if ( !lookup_partition_by_string(&p, argv[0], argv[1]) ) return; if ( !p->bdev.fs ) { command_errorx("%s: %s: No filesystem recognized", argv[0], device_name(p->path)); return; } struct filesystem* fs = p->bdev.fs; if ( !fs->fsck ) { command_errorx("%s: %s: fsck is not supported for %s", argv[0], device_name(p->path), fs->fstype_name); return; } bool interactive_fsck = false; // TODO: Run this in its own foreground process group so it can be ^C'd. pid_t child_pid; retry_interactive_fsck: if ( (child_pid = fork()) < 0 ) { command_error("%s: fork", argv[0]); return; } if ( child_pid == 0 ) { if ( interactive_fsck ) execlp(fs->fsck, fs->fsck, "--", p->path, (const char*) NULL); else execlp(fs->fsck, fs->fsck, "-p", "--", p->path, (const char*) NULL); warn("%s: Failed to load filesystem checker: %s", argv[0], fs->fsck); _Exit(127); } int code; waitpid(child_pid, &code, 0); if ( WIFSIGNALED(code) ) command_errorx("%s: %s: Filesystem check failed: %s: %s", argv[0], p->path, fs->fsck, strsignal(WTERMSIG(code))); else if ( !WIFEXITED(code) ) command_errorx("%s: %s: Filesystem check failed: %s: %s", argv[0], p->path, fs->fsck, "Unexpected unusual termination"); else if ( WEXITSTATUS(code) == 127 ) command_errorx("%s: %s: Filesystem check failed: %s: %s", argv[0], p->path, fs->fsck, "Filesystem checker is not installed"); else if ( WEXITSTATUS(code) & 4 && !interactive_fsck ) { interactive_fsck = true; goto retry_interactive_fsck; } else if ( WEXITSTATUS(code) != 0 && WEXITSTATUS(code) != 1 ) command_errorx("%s: %s: Filesystem check failed: %s: %s", argv[0], p->path, fs->fsck, "Filesystem checker was unsuccessful"); } static void on_help(size_t argc, char** argv) { (void) argc; (void) argv; // TODO: Show help for a particular command if an argument is given. // Perhaps advertise the man page? const char* prefix = ""; for ( size_t i = 0; i < commands_count; i++ ) { if ( strchr(commands[i].flags, 'a') ) continue; printf("%s%s", prefix, commands[i].name); prefix = " "; } printf("\n"); } static char* display_area_format(void* ctx, size_t row, size_t column) { if ( row == 0 ) { switch ( column ) { case 0: return strdup("PARTITION"); case 1: return strdup("SIZE"); case 2: return strdup("FILESYSTEM"); case 3: return strdup("MOUNTPOINT"); // TODO: LABEL default: return NULL; } } struct device_area* areas = (struct device_area*) ctx; struct device_area* area = &areas[row - 1]; if ( !area->p ) { switch ( column ) { case 0: return strdup(area->inside_extended ? " (unused)" : "(unused)"); case 1: return format_bytes_amount((uintmax_t) area->length); case 2: return strdup("-"); case 3: return strdup("-"); default: return NULL; } } switch ( column ) { case 0: if ( area->inside_extended ) return print_string(" %s", device_name(area->p->path)); else return strdup(device_name(area->p->path)); case 1: return format_bytes_amount((uintmax_t) area->length); case 2: if ( area->p->bdev.fs ) return strdup(area->p->bdev.fs->fstype_name); switch ( area->p->bdev.fs_error ) { case FILESYSTEM_ERROR_NONE: return strdup("(no error)"); case FILESYSTEM_ERROR_ABSENT: return strdup("none"); case FILESYSTEM_ERROR_UNRECOGNIZED: return strdup("unrecognized"); case FILESYSTEM_ERROR_ERRNO: return strdup("(error)"); } return strdup("(unknown error)"); case 3: { struct blockdevice* bdev = &area->p->bdev; char* storage; struct fstab fsent; if ( lookup_fstab_by_blockdevice(&fsent, &storage, bdev) ) { char* result = strdup(fsent.fs_file); free(storage); return result; } return strdup("-"); } default: return NULL; } } static void on_ls(size_t argc, char** argv) { (void) argc; (void) argv; display_rows_columns(display_area_format, current_areas, 1 + current_areas_count, 4); } static void on_man(size_t argc, char** argv) { (void) argc; sigset_t oldset, sigttou; sigemptyset(&sigttou); sigaddset(&sigttou, SIGTTOU); pid_t child_pid = fork(); if ( child_pid < 0 ) { command_error("%s: fork", argv[0]); return; } if ( child_pid == 0 ) { setpgid(0, 0); sigprocmask(SIG_BLOCK, &sigttou, &oldset); tcsetpgrp(0, getpgid(0)); sigprocmask(SIG_SETMASK, &oldset, NULL); const char* defargv[] = { "man", "8", "disked", NULL }; char** subargv = argc == 1 ? (char**) defargv : argv; execvp(subargv[0], (char* const*) subargv); warn("%s", subargv[0]); _exit(127); } int code; waitpid(child_pid, &code, 0); sigprocmask(SIG_BLOCK, &sigttou, &oldset); tcsetpgrp(0, getpgid(0)); sigprocmask(SIG_SETMASK, &oldset, NULL); } static char* display_hole_format(void* ctx, size_t row, size_t column) { if ( row == 0 ) { switch ( column ) { case 0: return strdup("HOLE"); case 1: return strdup("START"); case 2: return strdup("LENGTH"); case 3: return strdup("TYPE"); default: return NULL; } } struct device_area* areas = (struct device_area*) ctx; struct device_area* hole = NULL; size_t num_hole = 0; for ( size_t i = 0; i < current_areas_count; i++ ) { if ( areas[i].p ) continue; if ( num_hole == row - 1 ) { hole = &areas[i]; break; } num_hole++; } switch ( column ) { case 0: return print_string("%zu", row); case 1: return format_bytes_amount((uintmax_t) hole->start); case 2: return format_bytes_amount((uintmax_t) hole->length); case 3: return strdup(hole->inside_extended ? "logical" : "primary"); default: return NULL; } } static void on_mkpart(size_t argc, char** argv) { (void) argc; (void) argv; size_t num_holes = 0; struct device_area* hole = NULL; for ( size_t i = 0; i < current_areas_count; i++ ) { if ( current_areas[i].p ) continue; num_holes++; hole = ¤t_areas[i]; } if ( num_holes == 0 ) { command_errorx("%s: %s: Device has no unused areas left", argv[0], device_name(current_hd->path)); return; } else if ( 2 <= num_holes ) { bool type_column = current_pt_type == PARTITION_TABLE_TYPE_MBR; display_rows_columns(display_hole_format, current_areas, 1 + num_holes, type_column ? 4 : 3); char answer[sizeof(size_t) * 3]; while ( true ) { prompt(answer, sizeof(answer), "Which hole to create the partition inside?", "1"); char* end; unsigned long num = strtoul(answer, &end, 10); if ( *end || num_holes < num ) { command_errorx("%s: Invalid hole `%s'", argv[0], answer); continue; } for ( size_t i = 0; i < current_areas_count; i++ ) { if ( current_areas[i].p ) continue; if ( --num != 0 ) continue; hole = ¤t_areas[i]; break; } break; } printf("\n"); } unsigned int slot = 0; if ( current_pt_type == PARTITION_TABLE_TYPE_MBR && hole->inside_extended ) { slot = 5 + hole->ebr_index; } else if ( current_pt_type == PARTITION_TABLE_TYPE_MBR ) { struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; for ( unsigned int i = 0; i < 4; i++ ) { struct mbr_partition p; memcpy(&p, &mbrpt->mbr.partitions[i], sizeof(p)); mbr_partition_decode(&p); if ( mbr_is_partition_used(&p) ) continue; slot = 1 + i; break; } } else if ( current_pt_type == PARTITION_TABLE_TYPE_GPT ) { struct gpt_partition_table* gptpt = (struct gpt_partition_table*) current_pt->raw_partition_table; struct gpt gpt; memcpy(&gpt, &gptpt->gpt, sizeof(gpt)); gpt_decode(&gpt); for ( uint32_t i = 0; i < gpt.number_of_partition_entries; i++ ) { size_t poff = i * (size_t) gpt.size_of_partition_entry; struct gpt_partition p; memcpy(&p, gptpt->rpt + poff, sizeof(p)); bool unused = true; for ( size_t n = 0; n < 16; n++ ) if ( p.partition_type_guid[n] ) unused = false; if ( !unused ) continue; slot = 1 + i; break; } } else { command_errorx("%s: %s: Partition scheme not supported", argv[0], device_name(current_hd->path)); return; } if ( slot == 0 ) { command_errorx("%s: %s: Cannot add partition because the table is full", argv[0], device_name(current_hd->path)); return; } char* start_str = format_bytes_amount((uintmax_t) hole->start); assert(start_str); // TODO: Error handling. char* length_str = format_bytes_amount((uintmax_t) hole->length); assert(length_str); // TODO: Error handling. printf("Creating partition inside hole at %s of length %s (100%%)\n", start_str, length_str); free(start_str); free(length_str); off_t start; while ( true ) { char answer[256]; prompt(answer, sizeof(answer), "Free space before partition? (42%/15G/...)", "0%"); if ( !parse_disk_quantity(&start, answer, hole->length) ) { fprintf(stderr, "Invalid quantity `%s'.\n", answer); continue; } if ( start == hole->length ) { fprintf(stderr, "Answer was all free space, but need space for the " "partition itself.\n"); continue; } break; } printf("\n"); off_t max_length = hole->length - start; off_t length; length_str = format_bytes_amount((uintmax_t) max_length); assert(length_str); printf("Partition size can be at most %s (100%%).\n", length_str); free(length_str); while ( true ) { char answer[256]; prompt(answer, sizeof(answer), "Partition size? (42%/15G/...)", "100%"); if ( !parse_disk_quantity(&length, answer, max_length) ) { fprintf(stderr, "Invalid quantity `%s'.\n", answer); continue; } if ( length == 0 ) { fprintf(stderr, "Answer was zero (or rounded down to zero).\n"); continue; } break; } printf("\n"); char fstype[256]; while ( true ) { bool is_mbr = current_pt_type == PARTITION_TABLE_TYPE_MBR; bool is_gpt = current_pt_type == PARTITION_TABLE_TYPE_GPT; const char* question = "Format a filesystem? (no/ext2)"; if ( is_mbr ) question = "Format a filesystem? (no/ext2/extended)"; else if ( is_gpt ) question = "Format a filesystem? (no/ext2/biosboot)"; prompt(fstype, sizeof(fstype), question, "ext2"); if ( strcmp(fstype, "no") != 0 && strcmp(fstype, "ext2") != 0 && (!is_mbr || strcmp(fstype, "extended") != 0) && (!is_gpt || strcmp(fstype, "biosboot") != 0) ) { fprintf(stderr, "Invalid filesystem choice `%s'.\n", fstype); continue; } if ( !strcmp(fstype, "extended") ) { for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->type != PARTITION_TYPE_EXTENDED ) continue; command_errorx("%s: %s: Device already has an extended partition", argv[0], device_name(current_hd->path)); return; } } break; } char mountpoint[256] = ""; bool mountable = !strcmp(fstype, "ext2"); while ( mountable ) { prompt(mountpoint, sizeof(mountpoint), "Where to mount partition? (mountpoint or 'no')", "no"); if ( !strcmp(mountpoint, "no") ) { mountpoint[0] = '\0'; break; } if ( !strcmp(mountpoint, "mountpoint") ) { printf("Then answer which mountpoint.\n"); continue; } if ( !verify_mountpoint(mountpoint) ) { fprintf(stderr, "Invalid mountpoint `%s'.\n", fstype); continue; } simplify_mountpoint(mountpoint); break; } printf("\n"); size_t renumbered_partitions = 0; if ( current_pt_type == PARTITION_TABLE_TYPE_MBR && hole->inside_extended ) { struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; struct mbr ebr; struct mbr_partition p; off_t next_ebr_off = 0; uint32_t next_ebr_full_sectors = 0; if ( hole->ebr_index + 1 < mbrpt->ebr_chain_count ) { memcpy(&ebr, &mbrpt->ebr_chain[hole->ebr_index + 1].ebr, sizeof(ebr)); memcpy(&p, &ebr.partitions[0], sizeof(p)); mbr_partition_decode(&p); next_ebr_full_sectors = p.start_sector + p.total_sectors; next_ebr_off = mbrpt->ebr_chain[hole->ebr_index + 1].offset; } if ( hole->ebr_move_off ) { renumbered_partitions = 5 + hole->ebr_move_index; memcpy(&ebr, &mbrpt->ebr_chain[hole->ebr_move_index].ebr, sizeof(ebr)); memcpy(&p, &ebr.partitions[0], sizeof(p)); mbr_partition_decode(&p); // TODO: Update CHS information? p.start_sector = 1; next_ebr_full_sectors = p.start_sector + p.total_sectors; mbr_partition_encode(&p); memcpy(&ebr.partitions[0], &p, sizeof(p)); memcpy(&p, &ebr.partitions[1], sizeof(p)); mbr_partition_decode(&p); // TODO: Update CHS information? p.start_sector = 0; if ( next_ebr_off ) { assert(hole->ebr_move_off < next_ebr_off); off_t dist = next_ebr_off - hole->extended_start; assert(dist); p.start_sector = dist / current_hd->logical_block_size; } mbr_partition_encode(&p); memcpy(&ebr.partitions[1], &p, sizeof(p)); if ( pwriteall(current_hd->fd, &ebr, sizeof(ebr), hole->ebr_move_off) < sizeof(ebr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } next_ebr_off = hole->ebr_move_off; } off_t ebr_off = hole->ebr_off; memset(&ebr, 0, sizeof(ebr)); ebr.signature[0] = 0x55; ebr.signature[1] = 0xAA; memset(&p, 0, sizeof(p)); off_t p_start = hole->start + start - ebr_off; p.flags = 0; p.start_head = 1; // TODO: This. p.start_sector_cylinder = 1; // TODO: This. if ( !strcmp(fstype, "ext2") ) p.system_id = 0x83; else if ( !strcmp(fstype, "extended") ) p.system_id = 0x05; else p.system_id = 0x83; p.end_head = 2; // TODO: This. p.end_sector_cylinder = 2; // TODO: This. p.start_sector = p_start / current_hd->logical_block_size; p.total_sectors = length / current_hd->logical_block_size; mbr_partition_encode(&p); memcpy(&ebr.partitions[0], &p, sizeof(p)); memset(&p, 0, sizeof(p)); p.system_id = 0x00; p.start_sector = 0; p.total_sectors = 0; if ( next_ebr_off ) { p.system_id = 0x05; assert(ebr_off < next_ebr_off); off_t dist = next_ebr_off - hole->extended_start; assert(dist); p.start_sector = dist / current_hd->logical_block_size; p.total_sectors = next_ebr_full_sectors; } mbr_partition_encode(&p); memcpy(&ebr.partitions[1], &p, sizeof(p)); if ( pwriteall(current_hd->fd, &ebr, sizeof(ebr), ebr_off) < sizeof(ebr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } if ( 0 < hole->ebr_index ) { size_t prev_ebr_index = hole->ebr_index - 1; off_t prev_ebr_off = mbrpt->ebr_chain[prev_ebr_index].offset; memcpy(&ebr, &mbrpt->ebr_chain[prev_ebr_index].ebr, sizeof(ebr)); memcpy(&p, &ebr.partitions[1], sizeof(p)); mbr_partition_decode(&p); // TODO: Update CHS information? p.system_id = 0x05; assert(prev_ebr_off < ebr_off); off_t dist = ebr_off - hole->extended_start; assert(dist); p.start_sector = dist / current_hd->logical_block_size; off_t dist_total = hole->start + start + length - ebr_off; assert(dist_total); p.total_sectors = dist_total / current_hd->logical_block_size; mbr_partition_encode(&p); memcpy(&ebr.partitions[1], &p, sizeof(p)); if ( pwriteall(current_hd->fd, &ebr, sizeof(ebr), prev_ebr_off) < sizeof(ebr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv[0], device_name(current_hd->path)); scan_device(); return; } } else if ( current_pt_type == PARTITION_TABLE_TYPE_MBR ) { struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; struct mbr mbr; memcpy(&mbr, &mbrpt->mbr, sizeof(mbr)); struct mbr_partition p; memset(&p, 0, sizeof(p)); p.flags = 0; p.start_head = 0; // TODO: This. p.start_sector_cylinder = 0; // TODO: This. if ( !strcmp(fstype, "ext2") ) p.system_id = 0x83; else if ( !strcmp(fstype, "extended") ) p.system_id = 0x05; else p.system_id = 0x83; p.end_head = 0; // TODO: This. p.end_sector_cylinder = 0; // TODO: This. p.start_sector = (hole->start + start) / current_hd->logical_block_size; p.total_sectors = length / current_hd->logical_block_size; mbr_partition_encode(&p); memcpy(&mbr.partitions[slot - 1], &p, sizeof(p)); if ( pwriteall(current_hd->fd, &mbr, sizeof(mbr), 0) < sizeof(mbr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv[0], device_name(current_hd->path)); scan_device(); return; } } else if ( current_pt_type == PARTITION_TABLE_TYPE_GPT ) { struct gpt_partition_table* gptpt = (struct gpt_partition_table*) current_pt->raw_partition_table; struct gpt gpt; memcpy(&gpt, &gptpt->gpt, sizeof(gpt)); gpt_decode(&gpt); size_t poff = (slot - 1) * (size_t) gpt.size_of_partition_entry; struct gpt_partition p; memset(&p, 0, sizeof(p)); // TODO: This string might need to have some bytes swapped. // TODO: Perhaps just to hell with Linux guids and allocate our own // Sortix values that denote particular filesystems. const char* type_uuid_str = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"; if ( !strcmp(fstype, "biosboot") ) type_uuid_str = BIOSBOOT_GPT_TYPE_UUID; uuid_from_string(p.partition_type_guid, type_uuid_str); arc4random_buf(p.unique_partition_guid, sizeof(p.unique_partition_guid)); off_t pstart = hole->start + start; off_t pend = hole->start + start + length; p.starting_lba = pstart / current_hd->logical_block_size; p.ending_lba = pend / current_hd->logical_block_size - 1; p.attributes = 0; // TODO: Partition name. gpt_partition_encode(&p); memcpy(gptpt->rpt + poff, &p, sizeof(p)); if ( !gpt_update(argv[0], gptpt) ) return; } else { command_errorx("%s: %s: Partition scheme not supported", argv[0], device_name(current_hd->path)); return; } off_t search_target_offset = hole->start + start; if ( current_pt_type == PARTITION_TABLE_TYPE_MBR && !strcmp(fstype, "extended") ) { struct mbr ebr; memset(&ebr, 0, sizeof(ebr)); ebr.signature[0] = 0x55; ebr.signature[1] = 0xAA; if ( pwriteall(current_hd->fd, &ebr, sizeof(ebr), search_target_offset) < sizeof(ebr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } } if ( renumbered_partitions ) { for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->index < renumbered_partitions ) continue; remove_partition_device(current_pt->partitions[i]->path); break; } } scan_device(); if ( !current_pt ) // TODO: Assumes scan went well. { command_errorx("%s: %s: Rescan failed", argv[0], device_name(current_hd->path)); return; } if ( renumbered_partitions ) { for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->index < renumbered_partitions ) continue; if ( current_pt->partitions[i]->start == search_target_offset ) continue; struct partition* p = current_pt->partitions[i]; if ( !create_partition_device(argv[0], p) ) return; } } struct partition* p = NULL; for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->start != search_target_offset ) continue; p = current_pt->partitions[i]; } if ( !p ) { command_errorx("%s: %s: Failed to locate expected %sp%u partition", argv[0], device_name(current_hd->path), device_name(current_hd->path), slot); return; // TODO: Something went wrong. } if ( !create_partition_device(argv[0], p) ) return; printf("(Made %s)\n", device_name(p->path)); if ( !strcmp(fstype, "ext2") ) { printf("(Formatting %s as ext2...)\n", device_name(p->path)); struct ext2_superblock zero_sb; memset(&zero_sb, 0, sizeof(zero_sb)); // TODO: Add a blockdevice_pwriteall to libmount and use it. if ( pwriteall(current_hd->fd, &zero_sb, sizeof(zero_sb), p->start + 1024) < sizeof(zero_sb) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_partition(p); return; } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv[0], device_name(current_hd->path)); scan_partition(p); return; } // TODO: Run this in its own foreground process group so ^C works. pid_t child_pid = fork(); if ( child_pid < 0 ) { command_error("%s: fork", argv[0]); return; } const char* mkfs_argv[] = { "mkfs.ext2", "-q", mountpoint[0] ? "-M" : p->path, mountpoint[0] ? mountpoint : NULL, p->path, NULL }; if ( child_pid == 0 ) { execvp(mkfs_argv[0], (char* const*) mkfs_argv); warn("%s", mkfs_argv[0]); _exit(127); } int status; waitpid(child_pid, &status, 0); if ( WIFEXITED(status) && WEXITSTATUS(status) == 127 ) { command_errorx("%s: Failed to format filesystem (%s is not installed)", argv[0], mkfs_argv[0]); return; } else if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 ) { command_errorx("%s: Failed to format filesystem", argv[0]); return; } else if ( WIFSIGNALED(status) ) { command_errorx("%s: Failed to format filesystem (%s)", argv[0], strsignal(WTERMSIG(status))); return; } printf("(Formatted %s as ext2)\n", device_name(p->path)); scan_partition(p); if ( !p->bdev.fs || !(p->bdev.fs->flags & FILESYSTEM_FLAG_UUID) ) { command_errorx("%s: %s: Failed to scan expected ext2 filesystem", argv[0], device_name(p->path)); return; } if ( mountpoint[0] ) { if ( !add_blockdevice_to_fstab(&p->bdev, mountpoint) ) { command_error("%s: %s: Failed to add partition", argv[0], fstab_path); return; } } } } static void on_mktable(size_t argc, char** argv) { (void) argc; (void) argv; if ( current_pt_type != PARTITION_TABLE_TYPE_NONE ) { const char* name = device_name(current_hd->path); if ( interactive ) fprintf(stderr, "Device `%s' already has a partition table.\n", name); else command_errorx("Device `%s' already has a partition table", name); return; } char type_answer[32]; const char* type = NULL; if ( 2 <= argc ) type = argv[1]; if ( !type ) { prompt(type_answer, sizeof(type_answer), "Which partition table type? (mbr/gpt)", "gpt"); type = type_answer; } remove_partition_devices(current_hd->path); int fd = current_hd->fd; const char* name = device_name(current_hd->path); size_t logical_block_size = current_hd->logical_block_size; if ( !strcasecmp(type, "mbr") ) { struct mbr mbr; memset(&mbr, 0, sizeof(mbr)); mbr.signature[0] = 0x55; mbr.signature[1] = 0xAA; if ( pwriteall(fd, &mbr, sizeof(mbr), 0) < sizeof(mbr) ) { command_error("%s: %s: write", argv[0], name); scan_device(); return; } } else if ( !strcasecmp(type, "gpt") ) { size_t header_size; uint64_t sector_count = current_hd->st.st_size / logical_block_size; size_t partition_table_size = 16384; if ( partition_table_size < logical_block_size ) partition_table_size = logical_block_size; size_t partition_table_length = partition_table_size / sizeof(struct gpt_partition); uint64_t partition_table_sectors = partition_table_size / logical_block_size; uint64_t minimum_leading = 1 + 1 + partition_table_sectors; uint64_t minimum_trailing = partition_table_sectors + 1; uint64_t minimum_sector_count = minimum_leading + minimum_trailing; if ( sector_count <= minimum_sector_count ) { command_errorx("Device `%s' is too small for GPT", name); return; } uint64_t last_lba = sector_count - 1; off_t gpt_off = logical_block_size; off_t alt_off = logical_block_size * last_lba; unsigned char* partition_table = (unsigned char*) calloc(1, partition_table_size); if ( !partition_table ) { command_error("%s: %s: malloc", argv[0], name); return; } uint32_t partition_table_checksum = gpt_crc32(partition_table, partition_table_size); uint64_t pt_prim_lba = 2; off_t pt_prim_lba_off = pt_prim_lba * logical_block_size; if ( pwriteall(fd, partition_table, partition_table_size, pt_prim_lba_off) < partition_table_size ) { command_error("%s: %s: write", argv[0], name); free(partition_table); scan_device(); return; } uint64_t pt_alt_lba = last_lba - partition_table_sectors; off_t pt_alt_lba_off = pt_alt_lba * logical_block_size; if ( pwriteall(fd, partition_table, partition_table_size, pt_alt_lba_off) < partition_table_size ) { command_error("%s: %s: write", argv[0], name); free(partition_table); scan_device(); return; } free(partition_table); struct gpt gpt; memset(&gpt, 0, sizeof(gpt)); memcpy(gpt.signature, "EFI PART", 8); gpt.revision = 0x00010000; gpt.header_size = sizeof(gpt) - sizeof(gpt.reserved1); gpt.header_crc32 = 0; gpt.reserved0 = 0; gpt.my_lba = 1; gpt.alternate_lba = last_lba; gpt.first_usable_lba = pt_prim_lba + partition_table_sectors; gpt.last_usable_lba = pt_alt_lba - 1; arc4random_buf(&gpt.disk_guid, sizeof(gpt.disk_guid)); gpt.partition_entry_lba = pt_prim_lba; gpt.number_of_partition_entries = partition_table_length; gpt.size_of_partition_entry = sizeof(struct gpt_partition); gpt.partition_entry_array_crc32 = partition_table_checksum; header_size = gpt.header_size; gpt_encode(&gpt); gpt.header_crc32 = htole32(gpt_crc32(&gpt, header_size)); if ( pwriteall(fd, &gpt, sizeof(gpt), gpt_off) < sizeof(gpt) ) { command_error("%s: %s: write", argv[0], name); scan_device(); return; } gpt_decode(&gpt); gpt.header_crc32 = 0; gpt.reserved0 = 0; gpt.my_lba = last_lba; gpt.alternate_lba = 1; gpt.partition_entry_lba = pt_alt_lba; header_size = gpt.header_size; gpt_encode(&gpt); gpt.header_crc32 = htole32(gpt_crc32(&gpt, header_size)); if ( pwriteall(fd, &gpt, sizeof(gpt), alt_off) < sizeof(gpt) ) { command_error("%s: %s: write", argv[0], name); scan_device(); return; } if ( UINT32_MAX < sector_count ) sector_count = UINT32_MAX; struct mbr mbr; memset(&mbr, 0, sizeof(mbr)); mbr.signature[0] = 0x55; mbr.signature[1] = 0xAA; struct mbr_partition p; memset(&p, 0, sizeof(p)); p.flags = 0; p.start_head = 0; // TODO: This. p.start_sector_cylinder = 0; // TODO: This. p.system_id = 0xEE; p.end_head = 0; // TODO: This. p.end_sector_cylinder = 0; // TODO: This. p.start_sector = 1; p.total_sectors = sector_count - p.start_sector; mbr_partition_encode(&p); memcpy(&mbr.partitions[0], &p, sizeof(p)); if ( pwriteall(fd, &mbr, sizeof(mbr), 0) < sizeof(mbr) ) { command_error("%s: %s: write", argv[0], name); scan_device(); return; } } else { command_errorx("%s: Unrecognized partition table type `%s'", argv[0], type); return; } if ( fsync(fd) < 0 ) { command_error("%s: %s: sync", argv[0], name); scan_device(); return; } // TODO: Rescan this device. switch_device(current_hd); } static void on_mount(size_t argc, char** argv) { if ( argc < 2 ) { command_errorx("%s: No partition specified", argv[0]); return; } struct partition* part; if ( !lookup_partition_by_string(&part, argv[0], argv[1]) ) return; if ( argc < 3 ) { command_errorx("%s: Mountpoint or 'no' wasn't specified", argv[0]); return; } char* mountpoint = argv[2]; if ( !strcmp(mountpoint, "no") ) { if ( !remove_blockdevice_from_fstab(&part->bdev) ) { command_error("%s: %s: Failed to remove partition", argv[0], fstab_path); return; } } else { if ( !verify_mountpoint(mountpoint) ) { command_errorx("%s: Invalid mountpoint `%s'.", argv[0], mountpoint); return; } simplify_mountpoint(mountpoint); if ( !part->bdev.fs || !part->bdev.fs->driver ) { const char* name = device_name(part->path); printf("Warning: `%s' is not a mountable filesystem.\n", name); } if ( !add_blockdevice_to_fstab(&part->bdev, mountpoint) ) { command_error("%s: %s: Failed to remove partition", argv[0], fstab_path); return; } } } static void on_quit(size_t argc, char** argv) { (void) argc; (void) argv; quitting = true; } static void on_rmpart(size_t argc, char** argv) { if ( argc < 2 ) { command_errorx("%s: No partition specified", argv[0]); return; } struct partition* part; if ( !lookup_partition_by_string(&part, argv[0], argv[1]) ) return; bool ok = false; if ( part->type == PARTITION_TYPE_EXTENDED ) { bool has_logical = false; for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->type != PARTITION_TYPE_LOGICAL ) continue; has_logical = true; break; } ok = !has_logical; } if ( interactive && !ok ) { const char* name = device_name(part->path); // TODO: Use the name and mount point of the partition if such! if ( part->type == PARTITION_TYPE_EXTENDED ) { textf("WARNING: This will \e[91mERASE ALL PARTITIONS\e[m on the " "extended partition \e[93m%s\e[m!\n", name); for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { struct partition* logic = current_pt->partitions[i]; if ( logic->type != PARTITION_TYPE_LOGICAL ) continue; const char* logic_name = device_name(logic->path); textf("WARNING: This will \e[91mERASE ALL DATA\e[m on the " "partition \e[93m%s\e[m!\n", logic_name); } } else textf("WARNING: This will \e[91mERASE ALL DATA\e[m on the " "partition \e[93m%s\e[m!\n", name); // TODO: Warn if GPT require attribute is set. while ( true ) { char answer[32]; prompt(answer, sizeof(answer), "Confirm partition deletion? (yes/no)", "no"); if ( strcmp(answer, "no") == 0 ) { printf("(Aborted partition deletion)\n"); return; } if ( strcmp(answer, "yes") == 0 ) break; } } // TODO: Ensure the partition is not in use! if ( part->type == PARTITION_TYPE_EXTENDED ) { for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { struct partition* logic = current_pt->partitions[i]; if ( logic->type != PARTITION_TYPE_LOGICAL ) continue; if ( !remove_partition_device(logic->path) ) { command_error("%s: Failed to remove partition device: %s", argv[0], logic->path); return; } if ( !remove_blockdevice_from_fstab(&logic->bdev) ) { command_error("%s: %s: Failed to remove partition", argv[0], fstab_path); return; } } } if ( !remove_partition_device(part->path) ) { command_error("%s: Failed to remove partition device: %s", argv[0], part->path); return; } if ( !remove_blockdevice_from_fstab(&part->bdev) ) { command_error("%s: %s: Failed to remove partition", argv[0], fstab_path); // TODO: Recreate the partition. return; } unsigned int part_index = part->index; size_t renumbered_partitions = 0; if ( current_pt_type == PARTITION_TABLE_TYPE_MBR && part->type == PARTITION_TYPE_LOGICAL ) { assert(5 <= part_index); renumbered_partitions = part_index; for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->index <= part_index ) continue; remove_partition_device(current_pt->partitions[i]->path); } struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; size_t ebr_i = part_index - 5; off_t ebr_off = 0; struct mbr ebr; struct mbr_partition p; if ( ebr_i == 0 && mbrpt->ebr_chain_count == 1 ) { ebr_off = mbrpt->ebr_chain[0].offset; memset(&ebr, 0, sizeof(ebr)); ebr.signature[0] = 0x55; ebr.signature[1] = 0xAA; } else if ( ebr_i == 0 ) { ebr_off = mbrpt->ebr_chain[0].offset; off_t dist = mbrpt->ebr_chain[1].offset - mbrpt->ebr_chain[0].offset; memcpy(&ebr, &mbrpt->ebr_chain[1].ebr, sizeof(ebr)); memcpy(&p, ebr.partitions[0], sizeof(p)); mbr_partition_decode(&p); p.start_sector += dist / current_hd->logical_block_size; mbr_partition_encode(&p); memcpy(&ebr.partitions[0], &p, sizeof(p)); } else { memcpy(&ebr, &mbrpt->ebr_chain[ebr_i].ebr, sizeof(ebr)); memcpy(&p, ebr.partitions[1], sizeof(p)); ebr_off = mbrpt->ebr_chain[ebr_i - 1].offset; memcpy(&ebr, &mbrpt->ebr_chain[ebr_i - 1].ebr, sizeof(ebr)); memcpy(&ebr.partitions[1], &p, sizeof(p)); } // TODO: Partitions may be reordered. if ( pwriteall(current_hd->fd, &ebr, sizeof(ebr), ebr_off) < sizeof(ebr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv[0], device_name(current_hd->path)); scan_device(); return; } } else if ( current_pt_type == PARTITION_TABLE_TYPE_MBR ) { assert(0 < part_index); assert(part_index <= 4); struct mbr_partition_table* mbrpt = (struct mbr_partition_table*) current_pt->raw_partition_table; struct mbr mbr; memcpy(&mbr, &mbrpt->mbr, sizeof(mbr)); memset(&mbr.partitions[part_index - 1], 0, sizeof(struct mbr_partition)); if ( pwriteall(current_hd->fd, &mbr, sizeof(mbr), 0) < sizeof(mbr) ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); scan_device(); return; } if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv[0], device_name(current_hd->path)); scan_device(); return; } } else if ( current_pt_type == PARTITION_TABLE_TYPE_GPT ) { struct gpt_partition_table* gptpt = (struct gpt_partition_table*) current_pt->raw_partition_table; struct gpt gpt; memcpy(&gpt, &gptpt->gpt, sizeof(gpt)); gpt_decode(&gpt); size_t poff = (part_index - 1) * (size_t) gpt.size_of_partition_entry; memset(gptpt->rpt + poff, 0, sizeof(struct gpt_partition)); if ( !gpt_update(argv[0], gptpt) ) return; } else { command_errorx("%s: %s: Partition scheme not supported", argv[0], device_name(current_hd->path)); return; } scan_device(); printf("(Deleted %sp%u)\n", device_name(current_hd->path), part_index); if ( current_pt && renumbered_partitions ) { for ( size_t i = 0; i < current_pt->partitions_count; i++ ) { if ( current_pt->partitions[i]->index < renumbered_partitions ) continue; struct partition* p = current_pt->partitions[i]; if ( !create_partition_device(argv[0], p) ) return; } } } static void on_rmtable(size_t argc, char** argv) { (void) argc; (void) argv; // TODO: We shouldn't do this either if we recognize a filesystem on the // raw device itself. if ( current_pt_type != PARTITION_TABLE_TYPE_NONE && interactive ) { const char* name = device_name(current_hd->path); textf("WARNING: This will \e[91mERASE ALL DATA\e[m on the device " "\e[93m%s\e[m!\n", name); // TODO: List all the partitions? // TODO: Use the name and mount point of the partition if such! if ( current_pt && 0 < current_pt->partitions_count ) textf("WARNING: Device \e[93m%s\e[m \e[91mHAS PARTITIONS\e[m!\n", name); while ( true ) { char answer[32]; prompt(answer, sizeof(answer), "Confirm erase partition table? (yes/no)", "no"); if ( strcmp(answer, "no") == 0 ) { printf("(Aborted partition table erase)\n"); return; } if ( strcmp(answer, "yes") == 0 ) break; } } for ( size_t i = 0; current_pt && i < current_pt->partitions_count; i++ ) { struct partition* part = current_pt->partitions[i]; if ( !remove_partition_device(part->path) ) { command_error("%s: Failed to remove partition device: %s", argv[0], part->path); return; } if ( !remove_blockdevice_from_fstab(&part->bdev) ) { command_error("%s: %s: Failed to remove partitions", argv[0], fstab_path); // TODO: Recreate the partition. return; } } remove_partition_devices(current_hd->path); // TODO: Assert logical_block_size fits in size_t. size_t block_size = current_hd->logical_block_size; unsigned char* zeroes = (unsigned char*) calloc(1, block_size); if ( !zeroes ) { command_error("malloc"); return; } off_t sector_0 = 0; // MBR & GPT off_t sector_1 = block_size; // GPT off_t sector_m1; if ( __builtin_sub_overflow(current_hd->st.st_size, block_size, §or_m1) ) { errno = EOVERFLOW; command_error("Removing partition table"); return; } if ( pwriteall(current_hd->fd, zeroes, block_size, sector_0) < block_size || pwriteall(current_hd->fd, zeroes, block_size, sector_1) < block_size || pwriteall(current_hd->fd, zeroes, block_size, sector_m1) < block_size ) { command_error("%s: %s: write", argv[0], device_name(current_hd->path)); free(zeroes); return; } free(zeroes); if ( fsync(current_hd->fd) < 0 ) { command_error("%s: %s: sync", argv[0], device_name(current_hd->path)); return; } scan_device(); } static void on_sh(size_t argc, char** argv) { (void) argc; sigset_t oldset, sigttou; sigemptyset(&sigttou); sigaddset(&sigttou, SIGTTOU); pid_t child_pid = fork(); if ( child_pid < 0 ) { command_error("%s: fork", argv[0]); return; } if ( child_pid == 0 ) { setpgid(0, 0); sigprocmask(SIG_BLOCK, &sigttou, &oldset); tcsetpgrp(0, getpgid(0)); sigprocmask(SIG_SETMASK, &oldset, NULL); const char* subargv[] = { "sh", NULL }; execvp(subargv[0], (char* const*) subargv); warn("%s", subargv[0]); _exit(127); } int code; waitpid(child_pid, &code, 0); sigprocmask(SIG_BLOCK, &sigttou, &oldset); tcsetpgrp(0, getpgid(0)); sigprocmask(SIG_SETMASK, &oldset, NULL); scan_device(); } // TODO: mkfs command. static const struct command commands[] = { { "!", on_sh, "as" }, { "!man", on_man, "as" }, { "?", on_help, "a" }, { "device", on_device, "" }, { "devices", on_devices, "" }, { "d", on_device, "a" }, { "ds", on_devices, "a" }, { "e", on_quit, "a" }, { "exit", on_quit, "" }, { "fsck", on_fsck, "dtp" }, { "help", on_help, "" }, { "ls", on_ls, "dt" }, { "man", on_man, "s" }, { "mkpart", on_mkpart, "dt" }, { "mktable", on_mktable, "d" }, { "mount", on_mount, "dtp" }, { "q", on_quit, "a" }, { "quit", on_quit, "a" }, { "rmpart", on_rmpart, "dtp" }, { "rmtable", on_rmtable, "d" }, { "sh", on_sh, "s" }, }; static const size_t commands_count = sizeof(commands) / sizeof(commands[0]); void execute(char* cmd) { size_t argc = 0; char* argv[256]; split_arguments(cmd, &argc, argv, 255); if ( argc < 1 ) return; argv[argc] = NULL; for ( size_t i = 0; i < commands_count; i++ ) { if ( strcmp(argv[0], commands[i].name) != 0 ) continue; if ( strchr(commands[i].flags, 'd') && !current_hd ) { if ( !interactive ) errx(1, "No device specified"); fprintf(stderr, "Use the `device' command first to specify a device.\n"); fprintf(stderr, "You can list devices with the `devices' command.\n"); return; } if ( strchr(commands[i].flags, 't') && !current_pt ) { if ( current_pt_type == PARTITION_TABLE_TYPE_NONE ) printf("%s: No partition table found\n", device_name(current_hd->path)); else if ( current_pt_type == PARTITION_TABLE_TYPE_UNKNOWN ) printf("%s: No partition table recognized\n", device_name(current_hd->path)); else command_errorx("%s: %s: Partition table not loaded", argv[0], device_name(current_hd->path)); return; } commands[i].function(argc, argv); return; } if ( !interactive ) errx(1, "unrecognized command `%s'", argv[0]); fprintf(stderr, "Unrecognized command `%s'. " "Try `help' for more information.\n", argv[0]); } static void compact_arguments(int* argc, char*** argv) { for ( int i = 0; i < *argc; i++ ) { while ( i < *argc && !(*argv)[i] ) { for ( int n = i; n < *argc; n++ ) (*argv)[n] = (*argv)[n+1]; (*argc)--; } } } static void help(FILE* fp, const char* argv0) { fprintf(fp, "Usage: %s [OPTION]...\n", argv0); fprintf(fp, "Edit disk partition tables\n"); } static void version(FILE* fp, const char* argv0) { fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); } int main(int argc, char* argv[]) { setlocale(LC_ALL, ""); const char* argv0 = argv[0]; for ( int i = 1; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { default: fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); help(stderr, argv0); exit(1); } } else if ( !strcmp(arg, "--help") ) help(stdout, argv0), exit(0); else if ( !strcmp(arg, "--version") ) version(stdout, argv0), exit(0); else if ( !strncmp(arg, "--fstab=", strlen("--fstab=")) ) fstab_path = arg + strlen("--fstab="); else if ( !strcmp(arg, "--fstab") ) { if ( i + 1 == argc ) { warn( "option '--fstab' requires an argument"); fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); exit(125); } fstab_path = argv[i+1]; argv[++i] = NULL; } else { fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); help(stderr, argv0); exit(1); } } compact_arguments(&argc, &argv); interactive = isatty(0) && isatty(1); if ( !devices_open_all(&hds, &hds_count) ) err(1, "iterating devices"); qsort(hds, hds_count, sizeof(struct harddisk*), harddisk_compare_path); if ( 1 <= hds_count ) switch_device(hds[0]); char* line = NULL; size_t line_size = 0; ssize_t line_length; while ( !quitting ) { if ( interactive ) { printf("\e[93m(%s)\e[m ", current_hd ? device_name(current_hd->path) : "disked"); fflush(stdout); } if ( (line_length = getline(&line, &line_size, stdin)) < 0 ) { if ( interactive ) printf("\n"); break; } if ( line[line_length-1] == '\n' ) line[--line_length] = '\0'; execute(line); } free(line); if ( ferror(stdin) ) err(1, "getline"); for ( size_t i = 0; i < hds_count; i++ ) harddisk_close(hds[i]); free(hds); }