]> git.sesse.net Git - bcachefs-tools-debian/blob - rust-src/src/cmd_mount.rs
rust-src: Clean up read_super bindings
[bcachefs-tools-debian] / rust-src / src / cmd_mount.rs
1 use atty::Stream;
2 use bch_bindgen::{bcachefs, bcachefs::bch_sb_handle, debug, error, info};
3 use clap::Parser;
4 use colored::Colorize;
5 use uuid::Uuid;
6 use std::path::PathBuf;
7 use crate::key;
8 use crate::key::KeyLoc;
9 use std::ffi::{CStr, CString, OsStr, c_int, c_char, c_void};
10 use std::os::unix::ffi::OsStrExt;
11
12 fn mount_inner(
13     src: String,
14     target: impl AsRef<std::path::Path>,
15     fstype: &str,
16     mountflags: u64,
17     data: Option<String>,
18 ) -> anyhow::Result<()> {
19
20     // bind the CStrings to keep them alive
21     let src = CString::new(src)?;
22     let target = CString::new(target.as_ref().as_os_str().as_bytes())?;
23     let data = data.map(CString::new).transpose()?;
24     let fstype = CString::new(fstype)?;
25
26     // convert to pointers for ffi
27     let src = src.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char;
28     let target = target.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char;
29     let data = data.as_ref().map_or(std::ptr::null(), |data| {
30         data.as_c_str().to_bytes_with_nul().as_ptr() as *const c_void
31     });
32     let fstype = fstype.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char;
33
34     let ret = {
35         info!("mounting filesystem");
36         // REQUIRES: CAP_SYS_ADMIN
37         unsafe { libc::mount(src, target, fstype, mountflags, data) }
38     };
39     match ret {
40         0 => Ok(()),
41         _ => Err(crate::ErrnoError(errno::errno()).into()),
42     }
43 }
44
45 /// Parse a comma-separated mount options and split out mountflags and filesystem
46 /// specific options.
47 fn parse_mount_options(options: impl AsRef<str>) -> (Option<String>, u64) {
48     use either::Either::*;
49     debug!("parsing mount options: {}", options.as_ref());
50     let (opts, flags) = options
51         .as_ref()
52         .split(",")
53         .map(|o| match o {
54             "dirsync"       => Left(libc::MS_DIRSYNC),
55             "lazytime"      => Left(1 << 25), // MS_LAZYTIME
56             "mand"          => Left(libc::MS_MANDLOCK),
57             "noatime"       => Left(libc::MS_NOATIME),
58             "nodev"         => Left(libc::MS_NODEV),
59             "nodiratime"    => Left(libc::MS_NODIRATIME),
60             "noexec"        => Left(libc::MS_NOEXEC),
61             "nosuid"        => Left(libc::MS_NOSUID),
62             "relatime"      => Left(libc::MS_RELATIME),
63             "remount"       => Left(libc::MS_REMOUNT),
64             "ro"            => Left(libc::MS_RDONLY),
65             "rw"            => Left(0),
66             "strictatime"   => Left(libc::MS_STRICTATIME),
67             "sync"          => Left(libc::MS_SYNCHRONOUS),
68             ""              => Left(0),
69             o @ _           => Right(o),
70         })
71         .fold((Vec::new(), 0), |(mut opts, flags), next| match next {
72             Left(f) => (opts, flags | f),
73             Right(o) => {
74                 opts.push(o);
75                 (opts, flags)
76             }
77         });
78
79     use itertools::Itertools;
80     (
81         if opts.len() == 0 {
82             None
83         } else {
84             Some(opts.iter().join(","))
85         },
86         flags,
87     )
88 }
89
90 fn mount(
91     device: String,
92     target: impl AsRef<std::path::Path>,
93     options: impl AsRef<str>,
94 ) -> anyhow::Result<()> {
95     let (data, mountflags) = parse_mount_options(options);
96
97     info!(
98         "mounting bcachefs filesystem, {}",
99         target.as_ref().display()
100     );
101     mount_inner(device, target, "bcachefs", mountflags, data)
102 }
103
104 fn read_super_silent(path: &std::path::PathBuf) -> anyhow::Result<bch_sb_handle> {
105     // Stop libbcachefs from spamming the output
106     let _gag = gag::BufferRedirect::stdout().unwrap();
107
108     bch_bindgen::rs::read_super(&path)
109 }
110
111 fn get_devices_by_uuid(uuid: Uuid) -> anyhow::Result<Vec<(PathBuf, bch_sb_handle)>> {
112     debug!("enumerating udev devices");
113     let mut udev = udev::Enumerator::new()?;
114
115     udev.match_subsystem("block")?;
116
117     let devs = udev
118         .scan_devices()?
119         .into_iter()
120         .filter_map(|dev| dev.devnode().map(ToOwned::to_owned))
121         .map(|dev| (dev.clone(), read_super_silent(&dev)))
122         .filter_map(|(dev, sb)| sb.ok().map(|sb| (dev, sb)))
123         .filter(|(_, sb)| sb.sb().uuid() == uuid)
124         .collect();
125     Ok(devs)
126 }
127
128 fn stdout_isatty() -> &'static str {
129     if atty::is(Stream::Stdout) {
130         "true"
131     } else {
132         "false"
133     }
134 }
135
136 /// Mount a bcachefs filesystem by its UUID.
137 #[derive(Parser, Debug)]
138 #[command(author, version, about, long_about = None)]
139 struct Cli {
140     /// Where the password would be loaded from.
141     ///
142     /// Possible values are:
143     /// "fail" - don't ask for password, fail if filesystem is encrypted;
144     /// "wait" - wait for password to become available before mounting;
145     /// "ask" -  prompt the user for password;
146     #[arg(short, long, default_value = "", verbatim_doc_comment)]
147     key_location:   KeyLoc,
148
149     /// Device, or UUID=<UUID>
150     dev:            String,
151
152     /// Where the filesystem should be mounted. If not set, then the filesystem
153     /// won't actually be mounted. But all steps preceeding mounting the
154     /// filesystem (e.g. asking for passphrase) will still be performed.
155     mountpoint:     std::path::PathBuf,
156
157     /// Mount options
158     #[arg(short, default_value = "")]
159     options:        String,
160
161     /// Force color on/off. Default: autodetect tty
162     #[arg(short, long, action = clap::ArgAction::Set, default_value=stdout_isatty())]
163     colorize:       bool,
164
165     #[arg(short = 'v', long, action = clap::ArgAction::Count)]
166     verbose:        u8,
167 }
168
169 fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> {
170     let (devs, sbs) = if opt.dev.starts_with("UUID=") {
171         let uuid = opt.dev.replacen("UUID=", "", 1);
172         let uuid = Uuid::parse_str(&uuid)?;
173         let devs_sbs = get_devices_by_uuid(uuid)?;
174
175         let devs_strs: Vec<_> = devs_sbs.iter().map(|(dev, _)| dev.clone().into_os_string().into_string().unwrap()).collect();
176         let devs_str = devs_strs.join(":");
177         let sbs = devs_sbs.iter().map(|(_, sb)| *sb).collect();
178
179         (devs_str, sbs)
180     } else {
181         let mut sbs = Vec::new();
182
183         for dev in opt.dev.split(':') {
184             let dev = PathBuf::from(dev);
185             sbs.push(bch_bindgen::rs::read_super(&dev)?);
186         }
187
188         (opt.dev, sbs)
189     };
190
191     if unsafe { bcachefs::bch2_sb_is_encrypted(sbs[0].sb) } {
192         let key = opt
193             .key_location
194             .0
195             .ok_or_else(|| anyhow::anyhow!("no keyoption specified for locked filesystem"))?;
196
197         key::prepare_key(&sbs[0], key)?;
198     }
199
200     mount(devs, &opt.mountpoint, &opt.options)?;
201     Ok(())
202 }
203
204 #[no_mangle]
205 pub extern "C" fn cmd_mount(argc: c_int, argv: *const *const c_char) {
206     let argv: Vec<_> = (0..argc)
207         .map(|i| unsafe { CStr::from_ptr(*argv.add(i as usize)) })
208         .map(|i| OsStr::from_bytes(i.to_bytes()))
209         .collect();
210
211     let opt = Cli::parse_from(argv);
212     bch_bindgen::log::set_verbose_level(opt.verbose + bch_bindgen::log::ERROR);
213     colored::control::set_override(opt.colorize);
214     if let Err(e) = cmd_mount_inner(opt) {
215         error!("Fatal error: {}", e);
216     }
217 }