all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Stefan Reiter <s.reiter@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup 5/7] fuse_loop: add automatic cleanup of run files and dangling instances
Date: Wed,  7 Oct 2020 13:53:06 +0200	[thread overview]
Message-ID: <20201007115308.6275-6-s.reiter@proxmox.com> (raw)
In-Reply-To: <20201007115308.6275-1-s.reiter@proxmox.com>

A 'map' call will only clean up what it needs, that is only leftover
files or dangling instances of it's own name.

For a full cleanup the user can call 'unmap' without any arguments.

The 'cleanup on error' behaviour of map_loop is removed. It is no longer
needed (since the next call will clean up anyway), and in fact fixes a
bug where trying to map an image twice would result in an error, but
also cleanup the .pid file of the running instance, causing 'unmap' to
fail afterwards.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---
 src/bin/proxmox_backup_client/mount.rs |  4 +-
 src/tools/fuse_loop.rs                 | 88 ++++++++++++++++----------
 2 files changed, 59 insertions(+), 33 deletions(-)

diff --git a/src/bin/proxmox_backup_client/mount.rs b/src/bin/proxmox_backup_client/mount.rs
index ad06cba4..8d4c7133 100644
--- a/src/bin/proxmox_backup_client/mount.rs
+++ b/src/bin/proxmox_backup_client/mount.rs
@@ -83,7 +83,8 @@ const API_METHOD_UNMAP: ApiMethod = ApiMethod::new(
         "Unmap a loop device mapped with 'map' and release all resources.",
         &sorted!([
             ("name", true, &StringSchema::new(
-                "Archive name, path to loopdev (/dev/loopX) or loop device number. Omit to list all current mappings."
+                concat!("Archive name, path to loopdev (/dev/loopX) or loop device number. ",
+                        "Omit to list all current mappings and force cleaning up leftover instances.")
             ).schema()),
         ]),
     )
@@ -337,6 +338,7 @@ fn unmap(
     let mut name = match param["name"].as_str() {
         Some(name) => name.to_owned(),
         None => {
+            tools::fuse_loop::cleanup_unused_run_files(None);
             let mut any = false;
             for (backing, loopdev) in tools::fuse_loop::find_all_mappings()? {
                 let name = tools::systemd::unescape_unit(&backing)?;
diff --git a/src/tools/fuse_loop.rs b/src/tools/fuse_loop.rs
index f0d19acc..da186b4c 100644
--- a/src/tools/fuse_loop.rs
+++ b/src/tools/fuse_loop.rs
@@ -15,7 +15,7 @@ use tokio::io::{AsyncRead, AsyncSeek, AsyncReadExt, AsyncSeekExt};
 use futures::stream::{StreamExt, TryStreamExt};
 use futures::channel::mpsc::{Sender, Receiver};
 
-use proxmox::{try_block, const_regex};
+use proxmox::const_regex;
 use proxmox_fuse::{*, requests::FuseRequest};
 use super::loopdev;
 use super::fs;
@@ -55,6 +55,11 @@ impl<R: AsyncRead + AsyncSeek + Unpin> FuseLoopSession<R> {
         let mut pid_path = path.clone();
         pid_path.set_extension("pid");
 
+        // cleanup previous instance with same name
+        // if loopdev is actually still mapped, this will do nothing and the
+        // create_new below will fail as intended
+        cleanup_unused_run_files(Some(name.as_ref().to_owned()));
+
         match OpenOptions::new().write(true).create_new(true).open(&path) {
             Ok(_) => { /* file created, continue on */ },
             Err(e) => {
@@ -66,40 +71,27 @@ impl<R: AsyncRead + AsyncSeek + Unpin> FuseLoopSession<R> {
             },
         }
 
-        let res: Result<(Fuse, String), Error> = try_block!{
-            let session = Fuse::builder("pbs-block-dev")?
-                .options_os(options)?
-                .enable_read()
-                .build()?
-                .mount(&path)?;
+        let session = Fuse::builder("pbs-block-dev")?
+            .options_os(options)?
+            .enable_read()
+            .build()?
+            .mount(&path)?;
 
-            let loopdev_path = loopdev::get_or_create_free_dev().map_err(|err| {
-                format_err!("loop-control GET_FREE failed - {}", err)
-            })?;
+        let loopdev_path = loopdev::get_or_create_free_dev().map_err(|err| {
+            format_err!("loop-control GET_FREE failed - {}", err)
+        })?;
 
-            // write pidfile so unmap can later send us a signal to exit
-            Self::write_pidfile(&pid_path)?;
+        // write pidfile so unmap can later send us a signal to exit
+        Self::write_pidfile(&pid_path)?;
 
-            Ok((session, loopdev_path))
-        };
-
-        match res {
-            Ok((session, loopdev_path)) =>
-                Ok(Self {
-                    session: Some(session),
-                    reader,
-                    stat: minimal_stat(size as i64),
-                    fuse_path: path.to_string_lossy().into_owned(),
-                    pid_path: pid_path.to_string_lossy().into_owned(),
-                    loopdev_path,
-                }),
-            Err(e) => {
-                // best-effort temp file cleanup in case of error
-                let _ = remove_file(&path);
-                let _ = remove_file(&pid_path);
-                Err(e)
-            }
-        }
+        Ok(Self {
+            session: Some(session),
+            reader,
+            stat: minimal_stat(size as i64),
+            fuse_path: path.to_string_lossy().into_owned(),
+            pid_path: pid_path.to_string_lossy().into_owned(),
+            loopdev_path,
+        })
     }
 
     fn write_pidfile(path: &Path) -> Result<(), Error> {
@@ -229,6 +221,38 @@ impl<R: AsyncRead + AsyncSeek + Unpin> FuseLoopSession<R> {
     }
 }
 
+/// Clean up leftover files as well as FUSE instances without a loop device
+/// connected. Best effort, never returns an error.
+/// If filter_name is Some("..."), only this name will be cleaned up.
+pub fn cleanup_unused_run_files(filter_name: Option<String>) {
+    if let Ok(maps) = find_all_mappings() {
+        for (name, loopdev) in maps {
+            if loopdev.is_none() &&
+                (filter_name.is_none() || &name == filter_name.as_ref().unwrap())
+            {
+                let mut path = PathBuf::from(RUN_DIR);
+                path.push(&name);
+
+                // clean leftover FUSE instances (e.g. user called 'losetup -d' or similar)
+                // does nothing if files are already stagnant (e.g. instance crashed etc...)
+                if let Ok(_) = unmap_from_backing(&path) {
+                    // we have reaped some leftover instance, tell the user
+                    eprintln!(
+                        "Cleaned up dangling mapping '{}': no loop device assigned",
+                        &name
+                    );
+                }
+
+                // remove remnant files
+                // these we're not doing anything, so no need to inform the user
+                let _ = remove_file(&path);
+                path.set_extension("pid");
+                let _ = remove_file(&path);
+            }
+        }
+    }
+}
+
 fn get_backing_file(loopdev: &str) -> Result<String, Error> {
     let num = loopdev.split_at(9).1.parse::<u8>().map_err(|err|
         format_err!("malformed loopdev path, does not end with valid number - {}", err))?;
-- 
2.20.1





  parent reply	other threads:[~2020-10-07 11:53 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-07 11:53 [pbs-devel] [PATCH 0/7] fuse_loop cleanups and named mappings Stefan Reiter
2020-10-07 11:53 ` [pbs-devel] [PATCH proxmox-backup 1/7] format: fix typo in function name Stefan Reiter
2020-10-07 11:53 ` [pbs-devel] [PATCH proxmox-backup 2/7] fuse_loop: add documentation Stefan Reiter
2020-10-07 11:53 ` [pbs-devel] [PATCH proxmox-backup 3/7] loopdev: add module doc Stefan Reiter
2020-10-07 11:53 ` [pbs-devel] [PATCH proxmox-backup 4/7] mount/map: use names for map/unmap for easier use Stefan Reiter
2020-10-07 11:53 ` Stefan Reiter [this message]
2020-10-07 11:53 ` [pbs-devel] [PATCH proxmox-backup 6/7] fuse_loop: wait for instance to close after killing Stefan Reiter
2020-10-07 11:53 ` [pbs-devel] [PATCH proxmox-backup 7/7] fuse_loop: handle unmap on crashed instance Stefan Reiter
2020-10-08  6:45 ` [pbs-devel] applied: [PATCH 0/7] fuse_loop cleanups and named mappings Dietmar Maurer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201007115308.6275-6-s.reiter@proxmox.com \
    --to=s.reiter@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal