mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
955406a3ed
Also ensure that an strtoimax(3) failure in parse_disk_quantity is handled.
2853 lines
75 KiB
C
2853 lines
75 KiB
C
/*
|
|
* Copyright (c) 2015 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 prefixes[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
|
|
size_t num_prefixes = sizeof(prefixes) / sizeof(prefixes[0]);
|
|
size_t prefix_index = 0;
|
|
while ( exponent <= value && prefix_index + 1 < num_prefixes)
|
|
{
|
|
value_fraction = value % exponent;
|
|
value /= exponent;
|
|
prefix_index++;
|
|
}
|
|
char prefix_str[] = { prefixes[prefix_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, prefix_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 )
|
|
{
|
|
text(question);
|
|
if ( answer )
|
|
printf(" [%s] ", answer);
|
|
else
|
|
printf(" ");
|
|
fflush(stdout);
|
|
fgets(buffer, buffer_size, stdin);
|
|
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);
|
|
}
|