diff --git a/Makefile b/Makefile
index 5e771c9b..2b9d0057 100644
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@ dispd \
libmount \
bench \
carray \
+disked \
editor \
ext \
games \
diff --git a/disked/.gitignore b/disked/.gitignore
new file mode 100644
index 00000000..76f242d6
--- /dev/null
+++ b/disked/.gitignore
@@ -0,0 +1 @@
+disked
diff --git a/disked/Makefile b/disked/Makefile
new file mode 100644
index 00000000..97d3638a
--- /dev/null
+++ b/disked/Makefile
@@ -0,0 +1,29 @@
+SOFTWARE_MEANT_FOR_SORTIX=1
+include ../build-aux/platform.mak
+include ../build-aux/compiler.mak
+include ../build-aux/version.mak
+include ../build-aux/dirs.mak
+
+OPTLEVEL?=$(DEFAULT_OPTLEVEL)
+CFLAGS?=$(OPTLEVEL)
+
+CFLAGS:=$(CFLAGS) -Wall -Wextra
+CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
+
+BINARY:=disked
+
+all: $(BINARY)
+
+.PHONY: all install clean
+
+install: all
+ mkdir -p $(DESTDIR)$(SBINDIR)
+ install $(BINARY) $(DESTDIR)$(SBINDIR)
+ mkdir -p $(DESTDIR)$(MANDIR)/man8
+ cp disked.8 $(DESTDIR)$(MANDIR)/man8
+
+$(BINARY): $(BINARY).c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -std=gnu11 $< -o $@ -lmount
+
+clean:
+ rm -f $(BINARY)
diff --git a/disked/disked.8 b/disked/disked.8
new file mode 100644
index 00000000..74c25a5f
--- /dev/null
+++ b/disked/disked.8
@@ -0,0 +1,189 @@
+.Dd $Mdocdate: October 11 2015 $
+.Dt DISKED 8
+.Os
+.Sh NAME
+.Nm disked
+.Nd disk editor
+.Sh SYNOPSIS
+.Nm disked
+.Op Fl \-fstab Ns "=" Ns Ar path
+.Sh DESCRIPTION
+.Nm
+is an interactive program that manages partition tables. It can create and
+destroy partition tables on block devices. It can create partitions and destroy
+them. It can format filesystems on partitions and configure mountpoints in
+.Xr fstab 5 .
+.Nm
+supports the Master Boot Record and GUID Partition Table partitioning schemes.
+.Pp
+.Nm
+provides an interactive command line. Its prompt shows the currently selected
+device (defaulting to the first device alphabetically) or
+.Li (disked)
+if none is selected. Commands perform their actions when run rather
+than waiting for the user to write out changes.
+.Nm
+only creates partitions aligned to 1 MiB boundaries whose size is a multiple of
+1 MiB. Unused regions are aligned and those smaller than the alignment are not
+shown.
+.Pp
+The options are as follows:
+.Bl -tag -width "12345678"
+.It Fl \-fstab Ns "=" Ns Ar path
+Use
+.Ar path
+instead of
+.Pa /etc/fstab
+as
+.Xr fstab 5 .
+.El
+.Pp
+The following commands are supported:
+.Bl -tag -width "12345678"
+.It Sy device Ar device-index
+Switch to the device
+.Ar device-index
+as numbered by the
+.Sy devices
+command. If no index is specified, show the name of the current device.
+Alternatively you can write the absolute path to the device such as
+.Pa /dev/ahci0
+or just its name
+.Pa ahci0 .
+.It Sy devices
+List every available block device and show their indexes, device names (as found
+in
+.Pa /dev ) ,
+model names and serial numbers. Devices are counted from 0.
+.It Sy exit
+Exit
+.Nm .
+.It Sy fsck Ar partition-index
+Perform a
+.Xr fsck 8
+filesystem check of the partition
+.Ar partition-index
+on the current device.
+.It Sy help
+List available commands.
+.It Sy ls
+Display the partition table of the current device. Partitions are counted from
+1.
+.It Sy man Oo ... Oc
+Display this manual page if no operands are given, otherwise run
+.Xr man 1
+with the given command line.
+.It Sy mkpart
+Create a partition on the current device. If the partition table has multiple
+unused regions
+.Pq holes ,
+.Nm
+asks you which one to use. You need to specify the offset into the hole where
+the partition is created and then the length of the partition. See
+.Sx QUANTITIES
+below on the possible partition offset and length values. You will be asked if
+you want to format a filesystem:
+.Bl -tag -width "12345678"
+.It Sy biosdata
+(gpt only) Format a BIOS boot partition, which is required for booting with GRUB
+from a root filesystem on a GPT partition. 1 MiB is sufficient for this kind of
+partition.
+.It Sy extended
+(mbr only) Create an extended partition, which can contain an arbitrary amount
+logical partitions. You can only have a single extended partition.
+.It Sy ext2
+Format an ext2 filesystem.
+.It Sy no
+Use the existing disk data.
+.El
+.Pp
+If you format a mountable filesystem, then you will be asked if you want to
+create a mountpoint for the partition, which will be added to
+.Xr fstab 5 .
+.It Sy mktable Oo mbr "|" gpt Oc
+Create a partition table on the current device of the specified type.
+.It Sy mount Ar partition-index Oo Ar mountpoint Li "|" Sy no Oc
+Mount the partition
+.Ar partition-index
+of the current device on
+.Ar mountpoint
+in
+.Xr fstab 5 ,
+or if
+.Sy no
+then remove any existing mountpoints. Conflicting mountpoints are removed.
+.It Sy quit
+Exit
+.Nm .
+.It Sy rmpart Ar partition-index
+Delete the partition
+.Ar partition-index
+on the current device. The partition data is rendered inaccessible but is not
+actually erased. The partition can technically be recovered using
+.Sy mkpart .
+The partition data no longer has the protections of being in a partition and
+looks like regular unused space and can easily be overwritten. You should not
+delete a partition unless you want its contents gone. Deleting an extended
+partition deletes all the partitions it contains.
+.It Sy rmtable
+Delete the partition table on the current device. The existing partitions are
+rendered inaccessible but are not actually erased. The partitions can
+technically be recovered using
+.Sy mktable
+and
+.Sy mkpart .
+The disk data no longer has the protections of being partitioned and looks like
+regular unused space and can easily be overwritten. You should not delete the
+partition table unless you want all the data on the disk gone.
+.It Sy sh
+Run an interactive shell.
+.El
+.Sh QUANTITIES
+.Nm
+allows useful expressions when describing disk offsets and lengths. Every
+question needs an answer between 0 and a maximum. You can answer in percent
+where 100% is the maximum. You can answer an integer value followed by a suffix
+such as B, K, M, G, T, or P to signify bytes, KiB, MiB, GiB, TiB, and PiB
+respectively. The value is in MiB by default if there is no suffix. The answer
+is rounded to the 1 MiB alignment. If the expression is a negative value, then
+the answer is the maximum minus the absolute value. For instance:
+.Bl -tag -width "12345678"
+.It 42%
+Use 42% of the maximum.
+.It 13m
+Use 13 MiB.
+.It 37
+Use 37 MiB.
+.It 9001 GiB
+Use 9001 GiB.
+.It -100M
+Leave 100 MiB at the end.
+.It -10%
+Use 90% of the maximum.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/fstab" -compact
+.It Pa /etc/fstab
+filesystem table (see
+.Xr fstab 5 )
+.El
+.Sh EXAMPLES
+.Bd -literal
+(ahci0) devices # list devices
+(ahci0) device 1 # select device 1
+(ahci1) mktable gpt # create partition table
+(ahci1) mkpart # create partition
+0% # no free space preceding it
+50% # use half the disk
+ext2 # format an ext2 filesystem
+/home/user # use as /home/user filesystem
+(ahci1) ls # inspect partition table
+(ahci1) mount 1 /home # change partition 1 mountpoint to /home
+(ahci1) exit # done
+.Ed
+.Sh SEE ALSO
+.Xr fstab 5 ,
+.Xr gpt 7 ,
+.Xr mbr 7 ,
+.Xr fsck 8 ,
+.Xr init 8
diff --git a/disked/disked.c b/disked/disked.c
new file mode 100644
index 00000000..92a548a8
--- /dev/null
+++ b/disked/disked.c
@@ -0,0 +1,2842 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ disked.c
+ Disk editor.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+__attribute__((format(printf, 1, 2)))
+static char* print_string(const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ char* ret;
+ if ( vasprintf(&ret, format, ap) < 0 )
+ ret = NULL;
+ va_end(ap);
+ return ret;
+}
+
+static bool verify_mountpoint(const char* mountpoint)
+{
+ size_t index = 0;
+ if ( mountpoint[index++] != '/' )
+ return false;
+ while ( mountpoint[index] )
+ {
+ if ( mountpoint[index] == '.' )
+ {
+ index++;
+ if ( mountpoint[index] == '.' )
+ index++;
+ if ( !mountpoint[index] || mountpoint[index] == '/' )
+ return false;
+ }
+ while ( mountpoint[index] != '/' )
+ 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;
+}
+
+// TODO: Finish this, add decimal support and protect against overflow.
+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;
+ intmax_t value = strtoimax(string, (char**) &end, 10);
+ if ( value < 0 )
+ {
+ // TODO: If the least possible value, overflow.
+ 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 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";
+
+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 <= (errno = 0, line_length = getline(&line, &line_size, fstab_fp)) )
+ {
+ if ( line_length && 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);
+ if ( errno )
+ return false;
+ 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 <= (errno = 0, line_length = getline(&line, &line_size, rewr.in)) )
+ {
+ if ( line_length && 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 ( errno )
+ 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 <= (errno = 0, line_length = getline(&line, &line_size, rewr.in)) )
+ {
+ if ( line_length && 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 ( errno )
+ return rewrite_abort(&rewr), false;
+ if ( !found )
+ print_blockdevice_fsent(rewr.out, bdev, mountpoint);
+ return rewrite_finish(&rewr);
+}
+
+__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);
+}
+
+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()
+{
+ 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()
+{
+ 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]);
+ // TODO: Check for overflow here.
+ size_t areas_length = 2 * current_pt->partitions_count + 1;
+ 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
+ // TODO: Overflow: Ensure underflow is not possible.
+ off_t sector_m1 = current_hd->st.st_size - block_size; // GPT
+ 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);
+ fprintf(fp, "License GPLv3+: GNU GPL version 3 or later .\n");
+ fprintf(fp, "This is free software: you are free to change and redistribute it.\n");
+ fprintf(fp, "There is NO WARRANTY, to the extent permitted by law.\n");
+}
+
+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_length && 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);
+}