@@ -45,7 +45,7 @@ MAKEOPTS = --no-print-directory Q=$(Q)
progs = mkfs.btrfs btrfs-debug-tree btrfsck \
btrfs btrfs-map-logical btrfs-image btrfs-zero-log btrfs-convert \
- btrfs-find-root btrfstune btrfs-show-super
+ btrfs-find-root btrfstune btrfs-show-super mount.btrfs
# external libs required by various binaries; for btrfs-foo,
# specify btrfs_foo_libs = <list of libs>; see $($(subst...)) rules below
@@ -171,6 +171,10 @@ ioctl-test: $(objects) $(libs) ioctl-test.o
@echo " [LD] $@"
$(Q)$(CC) $(CFLAGS) -o ioctl-test $(objects) ioctl-test.o $(LDFLAGS) $(LIBS)
+mount.btrfs: btrfs-mount.o
+ @echo " [LD] $@"
+ $(Q)$(CC) $(CFLAGS) -o mount.btrfs -lmount -lblkid btrfs-mount.o $(LDFLAGS)
+
send-test: $(objects) $(libs) send-test.o
@echo " [LD] $@"
$(Q)$(CC) $(CFLAGS) -o send-test $(objects) send-test.o $(LDFLAGS) $(LIBS) -lpthread
new file mode 100644
@@ -0,0 +1,617 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <blkid/blkid.h>
+#include <libmount/libmount.h>
+
+#define MOUNT_FLAG_FAKE_MOUNT 1
+#define MOUNT_FLAG_VERBOSE 2
+#define MOUNT_FLAG_NOT_WRITIING_MTAB 4
+#define MOUNT_FLAG_IGNORE_SLOPPY_OPTS 8
+
+struct btrfs_device {
+ char *device_name;
+ char *device_uuid;
+ char *fs_name;
+ char *fs_uuid;
+ struct btrfs_device *next;
+};
+
+/* Parse program args, and set the related variables */
+static int parse_args(int argc, char **argv, char **options,
+ char **spec, char **dir, int *flag)
+{
+ char opt;
+
+ *options = NULL;
+
+ while ((opt = getopt(argc, argv, "sfnvo:")) != -1) {
+
+ switch (opt) {
+
+ case 's': /* tolerate sloppy mount options */
+ *flag |= MOUNT_FLAG_IGNORE_SLOPPY_OPTS;
+ break;
+ case 'f': /* fake mount */
+ *flag |= MOUNT_FLAG_FAKE_MOUNT;
+ break;
+ case 'n': /* mount without writing in mtab */
+ *flag |= MOUNT_FLAG_NOT_WRITIING_MTAB;
+ break;
+ case 'v': /* verbose */
+ *flag |= MOUNT_FLAG_VERBOSE;
+ break;
+ case 'o':
+ *options = optarg;
+ break;
+ default:
+ fprintf( stderr,"ERROR: unknown option: '%c'\n", opt);
+ return 1;
+ }
+ }
+
+ if (argc-optind != 2) {
+ fprintf(stderr, "ERROR: two arguments are needed\n");
+ return 1;
+ }
+
+ *spec = argv[optind];
+ *dir = argv[optind+1];
+
+ return 0;
+
+}
+
+/* add a new string to the string array */
+static void add_to_list(struct btrfs_device **head, struct btrfs_device *d)
+{
+ d->next = (*head);
+ *head = d;
+}
+
+/* free a btrfs_device struct */
+static void free_btrfs_device(struct btrfs_device *p)
+{
+ if (!p) return;
+
+ free( p->device_name );
+ free( p->device_uuid );
+ free( p->fs_name );
+ free( p->fs_uuid );
+ free(p);
+}
+
+/* free a btrfs devices(s) list */
+static void free_btrfs_devices_list(struct btrfs_device **p)
+{
+ while (*p) {
+ struct btrfs_device *next;
+ next = (*p)->next;
+ free_btrfs_device(*p);
+ *p = next;
+ }
+}
+
+/*
+ * this function extracts information from a device
+ */
+static int get_btrfs_dev_info(const char *devname, struct btrfs_device **device)
+{
+ int rc, ret=0;
+ blkid_probe pr;
+
+ *device = NULL;
+
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr) {
+ fprintf(stderr, "ERROR: faild to create a new libblkid "
+ "probe for '%s'\n", devname);
+ return 1;
+ }
+
+ /* enable topology probing */
+ blkid_probe_enable_superblocks(pr, 1);
+
+ /* set all flags */
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL |
+ BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_TYPE );
+
+ rc = blkid_do_safeprobe(pr);
+ if (rc == -1) {
+ fprintf(stderr, "ERROR: blkid_do_safeprobe() failed for '%s'\n",
+ devname);
+ ret = 2;
+ } else if (rc == 1) {
+ fprintf(stderr, "ERROR: cannot gather information about "
+ "superblocks for '%s'\n", devname);
+ ret = 3;
+ } else {
+ int i, nvals = blkid_probe_numof_values(pr);
+
+ *device = calloc(sizeof(struct btrfs_device), 1);
+ if (!*device) {
+ fprintf(stderr, "ERROR: not enough memory!\n");
+ ret = 20;
+ goto quit;
+ }
+ (*device)->device_name = strdup(devname);
+ if (!(*device)->device_name) {
+ fprintf(stderr, "ERROR: not enough memory!\n");
+ ret = 21;
+ goto quit;
+ }
+ for (i = 0; i < nvals; i++) {
+ const char *name, *data;
+ blkid_probe_get_value(pr, i, &name, &data, NULL);
+
+ if (!strcmp("UUID_SUB", name)) {
+ (*device)->device_uuid = strdup(data);
+ if ((*device)->device_uuid)
+ continue;
+ fprintf(stderr,
+ "ERROR: not enough memory!\n");
+ ret = 22;
+ goto quit;
+ } else if (!strcmp("UUID", name)) {
+ (*device)->fs_uuid = strdup(data);
+ if ((*device)->fs_uuid)
+ continue;
+ fprintf(stderr,
+ "ERROR: not enough memory!\n");
+ ret = 23;
+ goto quit;
+ } else if (!strcmp("LABEL", name)) {
+ (*device)->fs_name = strdup(data);
+ if ((*device)->fs_name)
+ continue;
+ fprintf(stderr,
+ "ERROR: not enough memory!\n");
+ ret = 24;
+ goto quit;
+ } else if (!strcmp("TYPE", name) &&
+ strcmp(data, "btrfs")) {
+
+ fprintf(stderr, "ERROR: scanning a non-btrfs"
+ " device (%s)\n", devname);
+ ret = 25;
+ goto quit;
+ }
+ }
+ }
+quit:
+ /* if failed, clean *device memory allocation */
+ if (ret && *device) {
+ free_btrfs_device(*device);
+ *device = NULL;
+ }
+ blkid_free_probe(pr);
+ return ret;
+}
+
+/* check if path is a valid block device */
+static int is_block_device(char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st))
+ return 0;
+
+ return S_ISBLK(st.st_mode);
+}
+
+/*
+ * this function get all the devices related to a filesystem
+ * return values:
+ * 0 -> OK
+ * >0 -> error
+ * <0 -> cache incoerency
+ */
+static int _get_devices_list(int flag, char *spec,
+ struct btrfs_device **devices, blkid_cache *bcache)
+{
+ /*blkid_cache bcache;*/
+ blkid_dev_iterate bit;
+ blkid_dev bdev;
+
+ char *dev;
+ struct btrfs_device *device0;
+ struct btrfs_device *d, *prevd;
+ int ret;
+
+ *devices = NULL;
+
+ if (!strncmp(spec, "LABEL=", 6)) {
+ dev = blkid_evaluate_tag("LABEL", spec+6, bcache);
+ } else if (!strncmp(spec, "UUID=", 5)) {
+ dev = blkid_evaluate_tag("UUID", spec+5, bcache);
+ } else {
+ dev = spec;
+ }
+
+ if (!dev || !is_block_device(dev)) {
+ fprintf(stderr, "ERROR: '%s' is not a valid block device\n",
+ spec);
+ return 2;
+ }
+
+ if (flag & MOUNT_FLAG_VERBOSE)
+ printf("INFO: start from device %s\n", dev);
+
+ if (flag & MOUNT_FLAG_VERBOSE)
+ printf("INFO: scan the first device\n");
+
+ ret = get_btrfs_dev_info(dev, &device0);
+ if (ret)
+ /* the error messages was already emitted */
+ return 3;
+
+ bit = blkid_dev_iterate_begin(*bcache);
+ if (blkid_dev_set_search(bit, "UUID", device0->fs_uuid)) {
+ fprintf(stderr,"ERROR: unable to setup blkid_dev_set_search()\n");
+ ret = 4;
+ goto exit;
+ }
+
+ while (!blkid_dev_next(bit, &bdev)) {
+ const char *dev;
+
+ struct btrfs_device *p;
+ dev = blkid_dev_devname(bdev);
+
+ if (get_btrfs_dev_info(dev, &p)) {
+ /* the error messages was already emitted */
+ ret = 5;
+ goto exit;
+ }
+ if (strcmp(device0->fs_uuid, p->fs_uuid)) {
+ /* cache incoherency */
+ free_btrfs_devices_list(devices);
+ *devices = NULL;
+ ret = -1;
+ goto exit;
+ };
+ add_to_list(devices, p);
+ }
+
+ /* sort the list so dev is the first device */
+ for (prevd = NULL, d = *devices ; d ; prevd = d, d = d->next) {
+ if (strcmp(d->device_name, dev))
+ continue;
+ if (!prevd)
+ /* ok, dev is the first device: do nothing */
+ break;
+
+ prevd->next = d->next;
+ d->next = *devices;
+ *devices = d;
+ }
+
+exit:
+ if (ret && *devices) {
+ free_btrfs_devices_list(devices);
+ *devices = NULL;
+ }
+
+ blkid_dev_iterate_end(bit);
+ free_btrfs_device(device0);
+
+ return ret;
+}
+
+/*
+ * this function get all the devices related to a filesystem
+ * return values:
+ * 0 -> OK
+ * >0 -> error
+ */
+static int get_devices_list(int flag, char *spec,
+ struct btrfs_device **devices)
+{
+ blkid_cache bcache;
+ int ret;
+
+ if (blkid_get_cache(&bcache, NULL)) {
+ fprintf(stderr, "ERROR: cannot get blkid cache\n");
+ return 1;
+ }
+
+ ret = _get_devices_list(flag, spec, devices, &bcache);
+ /*
+ * If ret < 0 a cache inchoernecy was detected
+ * if ret == 0 and *devices == 0, still a cache inchoernecy
+ * was detected: anyway we rebuild the cache and retry a new serach
+ */
+ if (ret < 0 || (!ret && !*devices)) {
+ if (blkid_probe_all(bcache)) {
+ fprintf(stderr,
+ "ERROR: cannot do a blkid_probe_all()\n");
+ ret = 6;
+ goto exit;
+ }
+ ret = _get_devices_list(flag, spec, devices, &bcache);
+ }
+
+exit:
+ blkid_put_cache(bcache);
+ return ret;
+}
+
+/* joins two options string */
+static int join_options(char **dst, char *fs_opts, char *vfs_opts)
+{
+ int l1=0, l2=0;
+
+ if (fs_opts && *fs_opts)
+ l1 = strlen(fs_opts);
+
+ if (vfs_opts && *vfs_opts)
+ l2 = strlen(vfs_opts);
+
+ if (!l1 && !l2) {
+ *dst = strdup("");
+ return *dst == NULL;
+ }
+
+ *dst = calloc(l1+l2+2, 1);
+ if (!*dst)
+ return 3;
+
+ if (l1) {
+ strcpy(*dst, fs_opts);
+ strcat(*dst, ",");
+ }
+ if (l2)
+ strcat(*dst, vfs_opts);
+
+ return 0;
+}
+
+/*
+ * This function rearrange the options
+ * 1) removes from "options":
+ * - the vfs_options (which became bits in mount_flags)
+ * - eventually device=<xxx> options passed (these aren't used)
+ * 2) adds to "options" a true list of device=<xxx>
+ * 3) put all the options in all_options, which will be used in
+ * updating mtab
+ */
+static int rearrange_options(int flags, char **options,
+ unsigned long *mount_flags,
+ char **all_options,
+ struct btrfs_device *devices)
+{
+ int rc;
+ char *user_opts=NULL, *vfs_opts=NULL, *fs_opts=NULL;
+ char *value=NULL;
+ int ret=0;
+ size_t size;
+ struct btrfs_device *device;
+
+ *all_options = NULL;
+
+ rc = mnt_split_optstr(*options, &user_opts, &vfs_opts, &fs_opts, 0, 0);
+ if (rc) {
+ fprintf(stderr, "ERROR: not enough memory\n");
+ ret = 1;
+ goto exit;
+ }
+
+ rc = mnt_optstr_get_flags(vfs_opts, mount_flags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ if (rc) {
+ fprintf(stderr, "ERROR: not enough memory\n");
+ ret = 2;
+ goto exit;
+ }
+
+ /* removes devices */
+ while (!mnt_optstr_get_option(fs_opts, "device", &value, &size)) {
+ rc = mnt_optstr_remove_option(&fs_opts, "device");
+ if (rc) {
+ fprintf(stderr, "ERROR: not enough memory\n");
+ ret = 3;
+ goto exit;
+ }
+ if (flags & MOUNT_FLAG_IGNORE_SLOPPY_OPTS) {
+ fprintf(stderr, "WARNING: 'device=' option ignored\n");
+ continue;
+ }
+ fprintf(stderr, "ERROR: 'device=' option not allowed\n");
+ ret=7;
+ goto exit;
+
+ }
+ /* append additional devices */
+ device = devices->next;
+ while (device) {
+ rc = mnt_optstr_append_option(&fs_opts,
+ "device", device->device_name);
+ if (rc) {
+ fprintf(stderr, "ERROR: not enough memory\n");
+ ret = 4;
+ goto exit;
+ }
+ device = device->next;
+ }
+
+ if (join_options(all_options, fs_opts, vfs_opts)) {
+ fprintf(stderr, "ERROR: not enough memory\n");
+ ret = 4;
+ goto exit;
+ }
+
+ *options = fs_opts;
+ fs_opts = NULL;
+
+ if (flags & MOUNT_FLAG_VERBOSE) {
+ printf("INFO: final flags: %s\n", *options);
+ printf("INFO: all flags : %s\n", *all_options);
+ }
+
+exit:
+ free(vfs_opts);
+ free(fs_opts);
+ free(user_opts);
+ return ret;
+
+}
+
+/* this function update the mtab file (if needed */
+static int update_mtab(int flags, char *device, char *target, char *all_opts )
+{
+
+ struct libmnt_fs *fs = NULL;
+ struct libmnt_update *update = NULL;
+
+ char *vfs_opts = NULL;
+ int ret = 0, rc;
+
+ fs = mnt_new_fs();
+ if (!fs)
+ goto memoryerror;
+ if (mnt_fs_set_options(fs, all_opts))
+ goto memoryerror;
+ if (mnt_fs_set_source(fs, device))
+ goto memoryerror;
+ if (mnt_fs_set_target(fs, target))
+ goto memoryerror;
+ if (mnt_fs_set_fstype(fs, "btrfs"))
+ goto memoryerror;
+ if (flags & MOUNT_FLAG_VERBOSE)
+ printf("INFO: info for updating 'mtab' prepared\n");
+ if (!(update = mnt_new_update()))
+ goto memoryerror;
+
+ rc = mnt_update_set_fs(update, 0, NULL, fs);
+
+ if (rc == 1) {
+ fprintf(stderr, "WARNING: update of mtab not needed\n");
+ ret = 0;
+ goto exit;
+ } else if (rc) {
+ fprintf(stderr, "ERROR: failed to set fs\n");
+ ret = 10;
+ goto exit;
+ }
+
+ ret = mnt_update_table(update, NULL);
+ if (ret)
+ fprintf(stderr, "ERROR: failed to update mtab\n");
+ else if (flags & MOUNT_FLAG_VERBOSE)
+ printf("INFO: 'mtab' updated\n");
+ goto exit;
+
+memoryerror:
+ fprintf(stderr, "ERROR: not enough memory\n");
+ if (fs) mnt_free_fs(fs);
+ if (update) mnt_free_update(update);
+
+ free(vfs_opts);
+
+ return 100;
+
+exit:
+ if (fs) mnt_free_fs(fs);
+ if (update) mnt_free_update(update);
+
+ free(vfs_opts);
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+
+ char *fs_opts, *spec, *dir, *all_options;
+ int ret, flags=0;
+ struct btrfs_device *devices;
+ unsigned long mount_flags = 0;
+
+ ret = parse_args(argc, argv, &fs_opts, &spec, &dir, &flags);
+
+ if (ret)
+ goto internalerror;
+
+ if (flags & MOUNT_FLAG_VERBOSE)
+ printf("INFO: parsed arguments\n");
+
+ ret = get_devices_list(flags, spec, &devices);
+ if (ret)
+ goto internalerror;
+
+ assert(devices);
+
+ if (flags & MOUNT_FLAG_VERBOSE)
+ printf("INFO: extracted the devices list\n");
+
+ if (flags & MOUNT_FLAG_VERBOSE) {
+ struct btrfs_device *d;
+ printf("INFO: filesystem info:\n");
+ printf("INFO:\tUUID=%s\n", devices->fs_uuid);
+ printf("INFO:\tLABEL=%s\n", devices->fs_name);
+ for (d = devices ; d ; d=d->next)
+ printf("INFO:\tUUID_SUB=%s - dev=%s\n",
+ d->device_uuid, d->device_name);
+ }
+
+ ret = rearrange_options(flags, &fs_opts, &mount_flags, &all_options,
+ devices);
+ if (ret)
+ goto internalerror;
+
+ if (!(flags & MOUNT_FLAG_FAKE_MOUNT)) {
+ int e;
+
+ if (flags & MOUNT_FLAG_VERBOSE) {
+ char *vfs_opts=NULL;
+ printf("INFO: source: %s\n", devices->device_name);
+ printf("INFO: target: %s\n", dir);
+ mnt_optstr_apply_flags(&vfs_opts, mount_flags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ printf("INFO: vfs_opts: 0x%08lx - %s\n",
+ mount_flags, vfs_opts);
+ printf("INFO: fs_opts: %s\n", fs_opts);
+ free(vfs_opts);
+ }
+
+ if (!(flags & MOUNT_FLAG_NOT_WRITIING_MTAB)) {
+ ret = update_mtab(flags, devices->device_name, dir,
+ all_options);
+ /* update_mtab error messages alredy printed */
+ if (ret)
+ goto errormtab;
+ }
+
+ ret = mount(devices->device_name, dir, "btrfs", mount_flags,
+ fs_opts);
+ e = errno;
+ if (!ret) {
+ if (flags & MOUNT_FLAG_VERBOSE)
+ printf("INFO: mount succeded\n");
+ } else {
+ fprintf(stderr, "ERROR: mount failed : %d - %s\n",
+ e, strerror(e));
+ }
+
+ } else {
+ ret = 0;
+ }
+
+ exit(ret);
+
+errormtab:
+ exit(16);
+
+internalerror:
+ exit(2);
+
+}