sortix-mirror/disked/disked.c

2922 lines
77 KiB
C

/*
* 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 <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <endian.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fstab.h>
#include <inttypes.h>
#include <ioleast.h>
#include <libgen.h>
#include <locale.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
#include <fsmarshall.h>
#include <mount/biosboot.h>
#include <mount/blockdevice.h>
#include <mount/devices.h>
#include <mount/ext2.h>
#include <mount/filesystem.h>
#include <mount/gpt.h>
#include <mount/harddisk.h>
#include <mount/mbr.h>
#include <mount/partition.h>
#include <mount/uuid.h>
__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 bool interactive;
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)
{
if ( !interactive )
errx(1, "Cannot ask question in non-interactive mode: %s", question);
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 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 = &current_hd->bdev;
if ( !blockdevice_probe_partition_table_type(&current_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(&current_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 = &current_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 = &current_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 = &current_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_harddisk_by_string(struct harddisk** out,
const char* argv0,
const char* name)
{
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;
*out = hds[i];
return true;
}
command_errorx("%s: No such device `%s'", argv0, name);
return false;
}
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)
{
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 = &current_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 )
{
if ( 2 <= argc )
strlcpy(answer, argv[1], sizeof(answer));
else
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 = &current_areas[i];
break;
}
break;
}
if ( argc < 2 )
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.
if ( argc < 3 )
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];
if ( 3 <= argc )
strlcpy(answer, argv[2], sizeof(answer));
else
prompt(answer, sizeof(answer),
"Free space before partition? (42%/15G/...)", "0%");
if ( !parse_disk_quantity(&start, answer, hole->length) )
{
command_errorx("%s: %s: Invalid quantity: %s",
argv[0], device_name(current_hd->path), answer);
continue;
}
if ( start == hole->length )
{
command_errorx("%s: %s: Answer was all free space, but need space "
"for the partition itself",
argv[0], device_name(current_hd->path));
continue;
}
break;
}
if ( argc < 3 )
printf("\n");
off_t max_length = hole->length - start;
off_t length;
length_str = format_bytes_amount((uintmax_t) max_length);
assert(length_str);
if ( argc < 4 )
printf("Partition size can be at most %s (100%%).\n", length_str);
free(length_str);
while ( true )
{
char answer[256];
if ( 4 <= argc )
strlcpy(answer, argv[3], sizeof(answer));
else
prompt(answer, sizeof(answer),
"Partition size? (42%/15G/...)", "100%");
if ( !parse_disk_quantity(&length, answer, max_length) )
{
command_errorx("%s: %s: Invalid quantity: %s",
argv[0], device_name(current_hd->path), answer);
continue;
}
if ( length == 0 )
{
command_errorx("%s: %s: Length was zero (or rounded down to zero)",
argv[0], device_name(current_hd->path));
continue;
}
break;
}
if ( argc < 4 )
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)";
if ( 5 <= argc )
strlcpy(fstype, argv[4], sizeof(fstype));
else
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) )
{
command_errorx("%s: %s: Invalid filesystem choice: %s",
argv[0], device_name(current_hd->path), 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 )
{
if ( 6 <= argc )
strlcpy(mountpoint, argv[5], sizeof(mountpoint));
else
prompt(mountpoint, sizeof(mountpoint),
"Where to mount partition? (mountpoint or 'no')", "no");
if ( !strcmp(mountpoint, "no") )
{
mountpoint[0] = '\0';
break;
}
if ( !verify_mountpoint(mountpoint) )
{
command_errorx("%s: %s: Invalid mountpoint: %s",
argv[0], device_name(current_hd->path), mountpoint);
continue;
}
simplify_mountpoint(mountpoint);
break;
}
if ( mountable && argc < 6 )
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, &sector_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]);
static void execute_argv(int argc, char** argv)
{
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 execute(char* cmd)
{
size_t argc = 0;
char* argv[256];
split_arguments(cmd, &argc, argv, 255);
if ( argc < 1 )
return;
argv[argc] = NULL;
execute_argv(argc, argv);
}
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, "");
bool set_interactive = false;
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 )
{
case 'i': interactive = true; set_interactive = true; break;
case 'n': interactive = false; set_interactive = true; break;
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);
if ( !set_interactive )
interactive = isatty(0) && isatty(1) && argc < 3;
if ( !devices_open_all(&hds, &hds_count) )
err(1, "iterating devices");
qsort(hds, hds_count, sizeof(struct harddisk*), harddisk_compare_path);
if ( 2 <= argc )
{
struct harddisk* hd;
if ( !lookup_harddisk_by_string(&hd, argv[0], argv[1]) )
exit(1);
switch_device(hd);
}
else if ( 1 <= hds_count )
switch_device(hds[0]);
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
if ( 3 <= argc )
execute_argv(argc - 2, argv + 2);
else 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);
return 0;
}