umount(8) may only unmount the filesystem without disconnecting
the NBD device.

Add `mount.erofs -u <DIR|BDEV>` to manually disconnect devices for
maintenance.

Signed-off-by: Gao Xiang <hsiang...@linux.alibaba.com>
---
 lib/backends/nbd.c |  10 ++++
 lib/liberofs_nbd.h |   3 +
 mount/main.c       | 134 ++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 138 insertions(+), 9 deletions(-)

diff --git a/lib/backends/nbd.c b/lib/backends/nbd.c
index 398a1e9..43630f0 100644
--- a/lib/backends/nbd.c
+++ b/lib/backends/nbd.c
@@ -24,6 +24,7 @@
 #define NBD_DO_IT              _IO( 0xab, 3 )
 #define NBD_CLEAR_SOCK         _IO( 0xab, 4 )
 #define NBD_SET_SIZE_BLOCKS     _IO( 0xab, 7 )
+#define NBD_DISCONNECT         _IO( 0xab, 8 )
 #define NBD_SET_TIMEOUT                _IO( 0xab, 9 )
 #define NBD_SET_FLAGS          _IO( 0xab, 10)
 
@@ -221,3 +222,12 @@ int erofs_nbd_send_reply_header(int skfd, __le64 cookie, 
int err)
                return 0;
        return ret < 0 ? -errno : -EIO;
 }
+
+int erofs_nbd_disconnect(int nbdfd)
+{
+       int err, err2;
+
+       err = ioctl(nbdfd, NBD_DISCONNECT);
+       err2 = ioctl(nbdfd, NBD_CLEAR_SOCK);
+       return err ?: err2;
+}
diff --git a/lib/liberofs_nbd.h b/lib/liberofs_nbd.h
index 6660df1..a68b7b1 100644
--- a/lib/liberofs_nbd.h
+++ b/lib/liberofs_nbd.h
@@ -7,6 +7,8 @@
 
 #include "erofs/defs.h"
 
+#define NBD_MAJOR                      43      /* Network block device */
+
 /* Supported request types */
 enum {
        EROFS_NBD_CMD_READ              = 0,
@@ -35,5 +37,6 @@ int erofs_nbd_connect(int nbdfd, int blkbits, u64 blocks);
 int erofs_nbd_do_it(int nbdfd);
 int erofs_nbd_get_request(int skfd, struct erofs_nbd_request *rq);
 int erofs_nbd_send_reply_header(int skfd, __le64 cookie, int err);
+int erofs_nbd_disconnect(int nbdfd);
 
 #endif
diff --git a/mount/main.c b/mount/main.c
index 9cb203f..55181bc 100644
--- a/mount/main.c
+++ b/mount/main.c
@@ -6,6 +6,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mount.h>
+#include <sys/types.h>
 #include <pthread.h>
 #include <unistd.h>
 #include "erofs/config.h"
@@ -28,6 +29,9 @@ struct loop_info {
        char    pad1[120];
 };
 #endif
+#ifdef HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
 
 enum erofs_backend_drv {
        EROFSAUTO,
@@ -44,6 +48,7 @@ static struct erofsmount_cfg {
        char *fstype;
        long flags;
        enum erofs_backend_drv backend;
+       bool umount;
 } mountcfg = {
        .full_options = "ro",
        .flags = MS_RDONLY,             /* default mountflags */
@@ -120,7 +125,7 @@ static int erofsmount_parse_options(int argc, char **argv)
        char *dot;
        int opt;
 
-       while ((opt = getopt_long(argc, argv, "Nfno:st:v",
+       while ((opt = getopt_long(argc, argv, "Nfno:st:uv",
                                  long_options, NULL)) != -1) {
                switch (opt) {
                case 'o':
@@ -146,20 +151,23 @@ static int erofsmount_parse_options(int argc, char **argv)
                        }
                        mountcfg.fstype = optarg;
                        break;
+               case 'u':
+                       mountcfg.umount = true;
+                       break;
                default:
                        return -EINVAL;
                }
        }
+       if (!mountcfg.umount) {
+               if (optind >= argc) {
+                       erofs_err("missing argument: DEVICE");
+                       return -EINVAL;
+               }
 
-       if (optind >= argc) {
-               erofs_err("missing argument: DEVICE");
-               return -EINVAL;
+               mountcfg.device = strdup(argv[optind++]);
+               if (!mountcfg.device)
+                       return -ENOMEM;
        }
-
-       mountcfg.device = strdup(argv[optind++]);
-       if (!mountcfg.device)
-               return -ENOMEM;
-
        if (optind >= argc) {
                erofs_err("missing argument: MOUNTPOINT");
                return -EINVAL;
@@ -394,6 +402,106 @@ out_err:
        return -errno;
 }
 
+int erofsmount_umount(char *target)
+{
+       char *device = NULL, *mountpoint = NULL;
+       struct stat st;
+       FILE *mounts;
+       int err, fd;
+       size_t n;
+       char *s;
+       bool isblk;
+
+       target = realpath(target, NULL);
+       if (!target)
+               return -errno;
+
+       err = lstat(target, &st);
+       if (err < 0) {
+               err = -errno;
+               goto err_out;
+       }
+
+       if (S_ISBLK(st.st_mode)) {
+               isblk = true;
+       } else if (S_ISDIR(st.st_mode)) {
+               isblk = false;
+       } else {
+               err = -EINVAL;
+               goto err_out;
+       }
+
+       mounts = fopen("/proc/mounts", "r");
+       if (!mounts) {
+               err = -ENOENT;
+               goto err_out;
+       }
+
+       for (s = NULL; (getline(&s, &n, mounts)) > 0;) {
+               bool hit = false;
+               char *f1, *f2, *end;
+
+               f1 = s;
+               end = strchr(f1, ' ');
+               if (end)
+                       *end = '\0';
+               if (isblk && !strcmp(f1, target))
+                       hit = true;
+               if (end) {
+                       f2 = end + 1;
+                       end = strchr(f2, ' ');
+                       if (end)
+                               *end = '\0';
+                       if (!isblk && !strcmp(f2, target))
+                               hit = true;
+               }
+               if (hit) {
+                       if (isblk) {
+                               err = -EBUSY;
+                               free(s);
+                               fclose(mounts);
+                               goto err_out;
+                       }
+                       free(device);
+                       device = strdup(f1);
+                       if (!mountpoint)
+                               mountpoint = strdup(f2);
+               }
+       }
+       free(s);
+       fclose(mounts);
+       if (!isblk && !device) {
+               err = -ENOENT;
+               goto err_out;
+       }
+
+       /* Avoid TOCTOU issue with NBD_CFLAG_DISCONNECT_ON_CLOSE */
+       fd = open(isblk ? target : device, O_RDWR);
+       if (fd < 0) {
+               err = -errno;
+               goto err_out;
+       }
+       if (mountpoint) {
+               err = umount(mountpoint);
+               if (err) {
+                       err = -errno;
+                       close(fd);
+                       goto err_out;
+               }
+       }
+       err = fstat(fd, &st);
+       if (err < 0)
+               err = -errno;
+       else if (S_ISBLK(st.st_mode) && major(st.st_rdev) == NBD_MAJOR)
+               err = erofs_nbd_disconnect(fd);
+       close(fd);
+err_out:
+       free(device);
+       free(mountpoint);
+       free(target);
+       return err < 0 ? err : 0;
+}
+
 int main(int argc, char *argv[])
 {
        int err;
@@ -406,6 +514,14 @@ int main(int argc, char *argv[])
                return EXIT_FAILURE;
        }
 
+       if (mountcfg.umount) {
+               err = erofsmount_umount(mountcfg.mountpoint);
+               if (err < 0)
+                       fprintf(stderr, "Failed to unmount %s: %s\n",
+                               mountcfg.mountpoint, erofs_strerror(err));
+               return err ? EXIT_FAILURE : EXIT_SUCCESS;
+       }
+
        if (mountcfg.backend == EROFSFUSE) {
                err = erofsmount_fuse(mountcfg.device, mountcfg.mountpoint,
                                      mountcfg.fstype, mountcfg.full_options);
-- 
2.43.5


Reply via email to