--- /dev/null
+/* This file is part of the IPFire Firewall.
+ *
+ * This program is distributed under the terms of the GNU General Public
+ * Licence. See the file COPYING for details.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <mntent.h>
+#include <time.h>
+#include <libgen.h>
+#include "setuid.h"
+
+#include "btrfsutil.h"
+#include "initreq.h"
+
+#define BTRFSPROG "/usr/bin/btrfs"
+#define SNAPSHOTDIR "/.snapshots"
+#define ROOTDIR "/"
+#define RESTOREDIR "/tmp/restore"
+#define PROCMOUNTS "/proc/mounts"
+
+#define BACKUP_PREFIX "auto-moving-to"
+
+#define VALID_NAME_CHARS "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
+#define NUMBERS "0123456789"
+#define BTRFS_FS_TREE_OBJECTID 5
+
+/*
+ * _btrfsctrl_validate_name Function
+ *
+ * Validates if a given snapshot name only contains allowed characters.
+ *
+*/
+static int _btrfsctrl_validate_name (const char *name) {
+ // Check if the given snapshot name only contains allowed characters.
+ if( strspn(name, VALID_NAME_CHARS) != strlen(name)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * _btrfsctrl_validate_number Function
+ *
+ * Validates if a given number of type char only contains digits.
+ *
+*/
+static int _btrfsctrl_validate_number(const char *number) {
+ // Check if the given id is a valid number.
+ if( strspn(number, NUMBERS) != strlen(number)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * _btrfsctrl_convert_str_to_uint64_t Function
+ *
+ * Converts a number with type char into a unsigned long intager.
+ *
+ * In case a number has been passed as argv it's type is char, but a lot of
+ * functions needs them as long int.
+ *
+*/
+static unsigned long int _btrfsctrl_convert_str_to_uint64_t(const char *number) {
+ // Convert the given number string to unsigned long int.
+ uint64_t long_uint = strtoul (number, NULL, 0);
+
+ return long_uint;
+}
+
+
+/*
+ * btrfsctrl_create_snapshot Function
+ *
+ * Requires a given name and True/False to set if a the snapshot should be read-only.
+ *
+ */
+static int btrfsctrl_create_snapshot (const char *name, bool read_only) {
+ char destination[STRING_SIZE];
+ int ro_flag = 0;
+
+ // Set read-only flag if requested.
+ if(read_only == true) {
+ ro_flag = BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY;
+ }
+
+ // Build destination directory.
+ snprintf(destination, sizeof(destination), "%s/%s", SNAPSHOTDIR, name);
+
+ // Create the snapshot in read-only mode.
+ int ret = btrfs_util_subvolume_snapshot(ROOTDIR, destination, ro_flag, NULL, NULL);
+ if (ret != BTRFS_UTIL_OK) {
+ // Get the error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "Could not create snapshot: %s\n", error);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * _btrfsctrl_create_directory Function
+ *
+ * Function to create the defined RESTOREDIR in case it does not exist yet.
+ *
+*/
+static int _btrfsctrl_create_directory(const char *directory) {
+ // Try to access the directory.
+ if (access(RESTOREDIR, X_OK) != 0) {
+ // Create the directory.
+ int ret = mkdir(directory, S_IRWXU|S_IRWXG|S_IRWXO);
+
+ // Handle error code.
+ if (ret != 0 && errno != EEXIST) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * _btrfsctrl_get_root_device Function.
+ *
+ * This function will return the device where root is currently mounted to.
+ *
+*/
+
+static const char * _btrfsctrl_get_root_device () {
+ struct mntent *ent;
+ FILE *mounts;
+
+ // Open kernel proc filesystem.
+ mounts = setmntent(PROCMOUNTS, "r");
+ if (mounts == NULL) {
+ return false;
+ }
+
+ // Loop through all known mount points.
+ while (NULL != (ent = getmntent(mounts))) {
+ // Check if the current processed mount point is the root filesystem.
+ if (strcmp(ent->mnt_dir, ROOTDIR) == 0) {
+ break;
+ }
+ }
+
+ // Close proc filesystem.
+ endmntent(mounts);
+
+ return ent->mnt_fsname;
+}
+
+/*
+ * _btrfsctrl_mount_btrfs_lvl5 Function.
+ *
+ * Function to mount the top level of a BTRFS.
+ *
+*/
+static int _btrfsctrl_mount_btrfs_lvl5(const char *device) {
+ char mount_options[STRING_SIZE];
+
+ // Assign mount options.
+ snprintf(mount_options, sizeof(mount_options), "subvolid=%d", BTRFS_FS_TREE_OBJECTID);
+
+ // Mount the top level BTRFS filesystem.
+ int ret = mount(device, RESTOREDIR, "btrfs", 0, mount_options);
+ if (ret < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * _btrfsctrl_umount_btrfs_lvl5 Function
+ *
+ * Umounts a mounted top level BTRFS from the defined restore directory.
+ *
+*/
+
+static int _btrfsctrl_umount_btrfs_lvl5() {
+ int retry = 1;
+ int counter = 0;
+
+ while(1) {
+ // Reset the retry marker
+ retry = 0;
+
+ // Try to umount the filesystem.
+ int ret = umount2(RESTOREDIR, 0);
+ if (ret) {
+ switch(errno) {
+ case EBUSY:
+ // Set marker to retry the umount.
+ retry = 1;
+
+ // Ignore if the rootfs could not be unmounted yet,
+ // because it is still used.
+ break;
+ case EINVAL:
+ // Ignore if the rootfs already has been unmounted
+ break;
+ case ENOENT:
+ // Ignore if the directory does not longer exist.
+ break;
+ default:
+ return ret;
+ }
+ }
+
+ // Abort loop if the rootfs got umounted
+ if (retry == 0) {
+ return 0;
+ }
+
+ // Abort after five failed umount attempts
+ if (counter == 5) {
+ return -1;
+ }
+
+ // Increment counter.
+ counter++;
+ }
+}
+
+/*
+ * _btrfsctrl_generate_backup_name Function
+ *
+ * Function which generates and returns a backup name for the old root
+ * when booting into or restoring a snapshot.
+ *
+*/
+
+static const char * _btrfsctrl_generate_backup_name (const char *path) {
+ time_t my_time;
+ struct tm * timeinfo;
+ char *backup_name = NULL;
+ char *pathc = NULL;
+ char *bname = NULL;
+
+ // Use basename function to get the name of the given path.
+ pathc = strdup(path);
+ bname = basename(pathc);
+
+ // Get the current time.
+ time (&my_time);
+ timeinfo = localtime (&my_time);
+
+ // Generate name for backup of the current rootdir.
+ asprintf(&backup_name, "@snapshots/%d-%02d-%02d-%02d-%02d-%s-%s", timeinfo->tm_year+1900, timeinfo->tm_mon+1, timeinfo->tm_mday,
+ timeinfo->tm_hour, timeinfo->tm_min, BACKUP_PREFIX, bname);
+
+ return backup_name;
+}
+
+/*
+ * _btrfsctrl_generate_abs_backup_path Function
+ *
+ * Function which will require a path as input and will return the absolute path from defined backupdir.
+ *
+*/
+static const char * _btrfsctrl_generate_abs_path (const char *prefix, const char *path) {
+ char *abs_path = NULL;
+
+ //Generate absolute path.
+ asprintf(&abs_path, "%s/\%s", prefix, path);
+
+ return abs_path;
+}
+
+/*
+ * _btrfsctrl_call_for_reboot Function
+ *
+ * This functions uses the sysvinit FIFO to talk to initd and request for a reboot.
+ *
+*/
+static int _btrfsctrl_call_for_reboot() {
+ struct init_request request;
+ int fd;
+
+ // Allocate some memory for the request.
+ memset(&request, 0, sizeof(request));
+
+ // Define the request to change to runlevel six (reboot)
+ request.magic = INIT_MAGIC;
+ request.cmd = INIT_CMD_RUNLVL;
+ request.runlevel = '6';
+
+ // Open the FIFO pipe to the init process and send the request.
+ if ((fd = open(INIT_FIFO, O_WRONLY)) >= 0) {
+ ssize_t p = 0;
+ size_t s = sizeof(request);
+ void *ptr = &request;
+
+ while (s > 0) {
+ p = write(fd, ptr, s);
+ if (p < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ break;
+ }
+ ptr += p;
+ s -= p;
+ }
+ close(fd);
+ }
+
+ return 0;
+
+}
+
+int main(int argc, char *argv[]) {
+ char command[STRING_SIZE];
+
+ if (!(initsetuid()))
+ exit(1);
+
+ if (argc < 2) {
+ fprintf(stderr, "\nNo argument given.\n\nbtrfsctrl (filesystem-usage|subvolume-list|snapshot-create|snapshot-delete|snapshot-restore)\n\n");
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "filesystem-usage") == 0) {
+ snprintf(command, sizeof(command), BTRFSPROG " filesystem usage -b %s", ROOTDIR);
+ safe_system(command);
+
+ // Get a list of all known subvolumes.
+ } else if (strcmp(argv[1], "subvolume-list") == 0) {
+ struct btrfs_util_subvolume_iterator* subvolume_iterator = NULL;
+ struct btrfs_util_subvolume_info subvolume_info;
+ char *path = NULL;
+ int ret;
+
+ // Create new subvolume iterator.
+ ret = btrfs_util_subvolume_iter_create(ROOTDIR, BTRFS_FS_TREE_OBJECTID, 0, &subvolume_iterator);
+ if (ret != BTRFS_UTIL_OK) {
+ // Get the error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "Could not create subvolumr iterator.\n%s\n", error);
+ exit(1);
+ }
+
+ // Init and iterate over the first subvolume.
+ ret = btrfs_util_subvolume_iter_next_info(subvolume_iterator, &path, &subvolume_info);
+ if(ret != BTRFS_UTIL_OK) {
+ // Get the error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "Could not iterate over the first subvolume.\n%s\n", error);
+
+ // Destroy the subvolume iterator.
+ btrfs_util_subvolume_iter_destroy(subvolume_iterator);
+
+ // Free the path, if set.
+ if (path != NULL) {
+ free(path);
+ }
+
+ exit(1);
+ }
+
+ // Iterate over the existing subvolumes/snapshots.
+ while(path) {
+ // Generate the line to print.
+ printf("%ld %s %ld\n", subvolume_info.id, path, subvolume_info.parent_id);
+
+ // Free the path, if set.
+ if (path != NULL) {
+ free(path);
+ }
+
+ // Iterate over the next subvolume.
+ ret = btrfs_util_subvolume_iter_next_info(subvolume_iterator, &path, &subvolume_info);
+
+ // Handle return value.
+ if (ret == BTRFS_UTIL_ERROR_STOP_ITERATION) {
+ // Nothing more to iterate.
+ break;
+
+ } else if ( ret != BTRFS_UTIL_OK) {
+ // Get the error message.
+ const char *error = btrfs_util_strerror(ret);
+
+ fprintf(stderr, "Could not iterate over the next subvolume. \n%s\n", error);
+
+ // Destroy the subvolume iterator.
+ btrfs_util_subvolume_iter_destroy(subvolume_iterator);
+
+ // Free the path if set.
+ if (path != NULL) {
+ free(path);
+ }
+ exit(1);
+ }
+ }
+
+ // Destroy the subvolume iterator.
+ btrfs_util_subvolume_iter_destroy(subvolume_iterator);
+
+ // Create a snapshot.
+ } else if (strcmp(argv[1], "snapshot-create") == 0) {
+ int r;
+
+ // Check if the given snapshot name is valid.
+ r = _btrfsctrl_validate_name(argv[2]);
+ if (r < 0) {
+ fprintf(stderr, "\nInvalid snapshot name. \nValid characters are: %s\n\n", VALID_NAME_CHARS);
+ exit(1);
+ }
+
+ // Call function and create a read-only snapshot with the given name.
+ r = btrfsctrl_create_snapshot(argv[2], true);
+ if (r < 0) {
+ exit(1);
+ }
+
+ // Delete a snapshot by it's given ID
+ } else if (strcmp(argv[1], "snapshot-delete") == 0) {
+ int ret;
+
+ // Check if the given id is a valid number.
+ ret = _btrfsctrl_validate_number(argv[2]);
+ if (ret < 0) {
+ fprintf(stderr, "\n Invalid snaphot ID: Not a numerical input.\n\n");
+ exit(1);
+ }
+
+ // Convert the given id string to unsigned long int.
+ uint64_t id = _btrfsctrl_convert_str_to_uint64_t(argv[2]);
+
+ // Open a file descriptor to the SNAPSHOTDIR.
+ int fd = open(SNAPSHOTDIR, O_DIRECTORY);
+ if (fd < 0) {
+ fprintf(stderr, "\nCould not open %s - Code: %d\n", SNAPSHOTDIR, fd);
+ exit(1);
+ }
+
+ // Delte the snapshot with the given ID.
+ ret = btrfs_util_delete_subvolume_by_id_fd(fd, id);
+ if (ret != BTRFS_UTIL_OK) {
+ // Get error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "%s\n", error);
+
+ // Close file descriptor.
+ close(fd);
+ exit(1);
+ }
+
+ // Close file desriptor.
+ close(fd);
+
+ // Restore a snapshot with a given ID.
+ } else if (strcmp(argv[1], "snapshot-restore") == 0) {
+ int ret;
+ char *path = NULL;
+ bool mounted = false;
+
+ // Check if the given id is a valid number.
+ ret = _btrfsctrl_validate_number(argv[2]);
+ if(ret < 0) {
+ fprintf(stderr, "\n Invalid snapshot ID. Not a numerical input.\n\n");
+
+ goto ERROR;
+ }
+
+ // Convert the given id string to unsigned long int.
+ uint64_t id = _btrfsctrl_convert_str_to_uint64_t(argv[2]);
+
+ // Get the path of the given snapshot ID.
+ ret = btrfs_util_subvolume_get_path(ROOTDIR, id, &path);
+ if (ret != BTRFS_UTIL_OK) {
+ // Get error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "%s\n", error);
+
+ goto ERROR;
+ }
+
+ // Create restore directory.
+ ret = _btrfsctrl_create_directory(RESTOREDIR);
+ if (ret < 0) {
+ // Get the error message.
+ const char *error = strerror(errno);
+ fprintf(stderr, "Could not create %s %s\n", RESTOREDIR, error);
+
+ goto ERROR;
+ }
+
+ // Get the device name of the mounted root partition.
+ const char *rootdev = _btrfsctrl_get_root_device();
+ if (!rootdev) {
+ fprintf(stderr, "Could not get root device.\n");
+
+ goto ERROR;
+ }
+
+ // Mount the top level of the BTRFS.
+ ret = _btrfsctrl_mount_btrfs_lvl5(rootdev);
+ if (ret < 0) {
+ // Get error message.
+ const char *error = strerror(errno);
+ fprintf(stderr, "Could not mount top level BTRFS to %s. %s\n", RESTOREDIR, error);
+
+ goto ERROR;
+ }
+
+ // Set mounted to true.
+ mounted = true;
+
+ // Generate a backup name.
+ const char *backup_name = _btrfsctrl_generate_backup_name(path);
+ if (!backup_name) {
+ fprintf(stderr, "Could not generate a backup name.\n");
+
+ goto ERROR;
+ }
+
+ // Generate absolute path values.
+ const char *oldname = _btrfsctrl_generate_abs_path(RESTOREDIR, "@");
+ const char *newname = _btrfsctrl_generate_abs_path(RESTOREDIR, backup_name);
+ if ((!oldname) || (!newname)) {
+ fprintf(stderr, "Could not generate absolute path values.\n");
+
+ goto ERROR;
+ }
+
+ // Rename / Move the current @ to backup location
+ ret = rename(oldname, newname);
+ if (ret < 0) {
+ if (errno == ENOENT) {
+ fprintf(stderr, "ENOENT\n");
+ }
+
+ // Get error message.
+ const char *error = strerror(errno);
+ fprintf(stderr, "Could not backup/move current root filesystem. %s\n", error);
+
+ goto ERROR;
+ }
+
+ /*
+ * XXX
+ *
+ * Needs some more attention. When swithing the old/current root to read-only no write
+ * operations are possible. So the RESTOREDIT could not be deleted correctly afterwards
+ * and also during init reboot the services can not write anything back to disk.
+ */
+ // Set the backup of root dir to read-only.
+ /*
+ ret = btrfs_util_subvolume_set_read_only(newname, true);
+ if (ret != BTRFS_UTIL_OK) {
+ // Get error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "Could not change the rootfs backup to read-only. %s\n", error);
+
+ goto ERROR;
+ }
+ */
+
+ // Create a snapshot of the given snapshot ID as the new root.
+ const char *restore = _btrfsctrl_generate_abs_path(RESTOREDIR, path);
+ ret = btrfs_util_subvolume_snapshot(restore, oldname, 0, NULL, NULL);
+ if (ret > 0) {
+ // Get the error message.
+ const char *error = btrfs_util_strerror(ret);
+ fprintf(stderr, "Could not restore snapshot %s: %s\n", path, error);
+
+ goto ERROR;
+ }
+
+
+
+ // Call sync to sync the disks
+ sync();
+
+ // Umount the BTRFS root filesystem from restore directory.
+ ret = _btrfsctrl_umount_btrfs_lvl5();
+ if (ret < 0) {
+ // Get error message
+ const char *error = strerror(errno);
+ fprintf(stderr, "Could not umount BTRFS root filesystem from %s. %s\n", RESTOREDIR, error);
+
+ goto ERROR;
+ }
+
+ // Set mounted to false again.
+ mounted = false;
+
+ // Remove the restore directory.
+ ret = rmdir(RESTOREDIR);
+ if(ret != 0) {
+ fprintf(stderr, "Could not remove %s. %d\n", RESTOREDIR, ret);
+
+ goto ERROR;
+ }
+
+ // Reboot the system
+ ret = _btrfsctrl_call_for_reboot();
+ if (ret < 0) {
+ fprintf(stderr, "Could not reboot the system.\n");
+ exit(1);
+ }
+
+ exit(0);
+
+ ERROR:
+ // Umount the top root BTRFS in case it is mounted.
+ if (mounted == true) {
+ ret = _btrfsctrl_umount_btrfs_lvl5();
+ if (ret < 0) {
+ // Get error message
+ const char *error = strerror(errno);
+ fprintf(stderr, "Could not umount BTRFS root filesystem during cleanup. %s\n", error);
+ }
+ }
+
+ // Free path if set.
+ if(path != NULL) {
+ free(path);
+ }
+
+ exit(1);
+ } else {
+ fprintf(stderr, "\nBad argument given.\n\nbtrfsctrl (filesystem-usage|subvolume-list|snapshot-create|snapshot-delete|snapshot-restore)\n\n");
+ exit(1);
+ }
+
+ return 0;
+}