|
| 1 | +use fuse_mt::FilesystemMT; |
| 2 | +use gumdrop::Options; |
| 3 | +use i18n_embed::{ |
| 4 | + fluent::{fluent_language_loader, FluentLanguageLoader}, |
| 5 | + DesktopLanguageRequester, |
| 6 | +}; |
| 7 | +use lazy_static::lazy_static; |
| 8 | +use log::{error, info}; |
| 9 | +use rust_embed::RustEmbed; |
| 10 | +use std::ffi::OsStr; |
| 11 | +use std::fmt; |
| 12 | +use std::io; |
| 13 | +use std::path::PathBuf; |
| 14 | + |
| 15 | +mod overlay; |
| 16 | +mod util; |
| 17 | + |
| 18 | +#[derive(RustEmbed)] |
| 19 | +#[folder = "i18n"] |
| 20 | +struct Translations; |
| 21 | + |
| 22 | +const TRANSLATIONS: Translations = Translations {}; |
| 23 | + |
| 24 | +lazy_static! { |
| 25 | + static ref LANGUAGE_LOADER: FluentLanguageLoader = fluent_language_loader!(); |
| 26 | +} |
| 27 | + |
| 28 | +macro_rules! fl { |
| 29 | + ($message_id:literal) => {{ |
| 30 | + i18n_embed_fl::fl!(LANGUAGE_LOADER, $message_id) |
| 31 | + }}; |
| 32 | +} |
| 33 | + |
| 34 | +macro_rules! wfl { |
| 35 | + ($f:ident, $message_id:literal) => { |
| 36 | + write!($f, "{}", fl!($message_id)) |
| 37 | + }; |
| 38 | +} |
| 39 | + |
| 40 | +enum Error { |
| 41 | + Age(age::DecryptError), |
| 42 | + Io(io::Error), |
| 43 | + MissingMountpoint, |
| 44 | + MissingSource, |
| 45 | + MountpointMustBeDir, |
| 46 | + Nix(nix::Error), |
| 47 | + SourceMustBeDir, |
| 48 | +} |
| 49 | + |
| 50 | +impl From<age::DecryptError> for Error { |
| 51 | + fn from(e: age::DecryptError) -> Self { |
| 52 | + Error::Age(e) |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +impl From<io::Error> for Error { |
| 57 | + fn from(e: io::Error) -> Self { |
| 58 | + Error::Io(e) |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +impl From<nix::Error> for Error { |
| 63 | + fn from(e: nix::Error) -> Self { |
| 64 | + Error::Nix(e) |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +// Rust only supports `fn main() -> Result<(), E: Debug>`, so we implement `Debug` |
| 69 | +// manually to provide the error output we want. |
| 70 | +impl fmt::Debug for Error { |
| 71 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 72 | + match self { |
| 73 | + Error::Age(e) => match e { |
| 74 | + age::DecryptError::ExcessiveWork { required, .. } => { |
| 75 | + writeln!(f, "{}", e)?; |
| 76 | + write!( |
| 77 | + f, |
| 78 | + "{}", |
| 79 | + i18n_embed_fl::fl!( |
| 80 | + LANGUAGE_LOADER, |
| 81 | + "rec-dec-excessive-work", |
| 82 | + wf = required |
| 83 | + ) |
| 84 | + ) |
| 85 | + } |
| 86 | + _ => write!(f, "{}", e), |
| 87 | + }, |
| 88 | + Error::Io(e) => write!(f, "{}", e), |
| 89 | + Error::MissingMountpoint => wfl!(f, "err-mnt-missing-mountpoint"), |
| 90 | + Error::MissingSource => wfl!(f, "err-mnt-missing-source"), |
| 91 | + Error::MountpointMustBeDir => wfl!(f, "err-mnt-must-be-dir"), |
| 92 | + Error::Nix(e) => write!(f, "{}", e), |
| 93 | + Error::SourceMustBeDir => wfl!(f, "err-mnt-source-must-be-dir"), |
| 94 | + }?; |
| 95 | + writeln!(f)?; |
| 96 | + writeln!(f, "[ {} ]", fl!("err-ux-A"))?; |
| 97 | + write!( |
| 98 | + f, |
| 99 | + "[ {}: https://str4d.xyz/rage/report {} ]", |
| 100 | + fl!("err-ux-B"), |
| 101 | + fl!("err-ux-C") |
| 102 | + ) |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +#[derive(Debug, Options)] |
| 107 | +struct AgeMountOptions { |
| 108 | + #[options(free, help = "The directory to mount.")] |
| 109 | + directory: String, |
| 110 | + |
| 111 | + #[options(free, help = "The path to mount at.")] |
| 112 | + mountpoint: String, |
| 113 | + |
| 114 | + #[options(help = "Print this help message and exit.")] |
| 115 | + help: bool, |
| 116 | + |
| 117 | + #[options(help = "Print version info and exit.", short = "V")] |
| 118 | + version: bool, |
| 119 | +} |
| 120 | + |
| 121 | +fn mount_fs<T: FilesystemMT + Send + Sync + 'static, F>(open: F, mountpoint: PathBuf) |
| 122 | +where |
| 123 | + F: FnOnce() -> io::Result<T>, |
| 124 | +{ |
| 125 | + let fuse_args: Vec<&OsStr> = vec![&OsStr::new("-o"), &OsStr::new("ro,auto_unmount")]; |
| 126 | + |
| 127 | + match open().map(|fs| fuse_mt::FuseMT::new(fs, 1)) { |
| 128 | + Ok(fs) => { |
| 129 | + info!("{}", fl!("info-mounting-as-fuse")); |
| 130 | + if let Err(e) = fuse_mt::mount(fs, &mountpoint, &fuse_args) { |
| 131 | + error!("{}", e); |
| 132 | + } |
| 133 | + } |
| 134 | + Err(e) => { |
| 135 | + error!("{}", e); |
| 136 | + } |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +fn main() -> Result<(), Error> { |
| 141 | + use std::env::args; |
| 142 | + |
| 143 | + env_logger::builder() |
| 144 | + .format_timestamp(None) |
| 145 | + .filter_level(log::LevelFilter::Off) |
| 146 | + .parse_default_env() |
| 147 | + .init(); |
| 148 | + |
| 149 | + let requested_languages = DesktopLanguageRequester::requested_languages(); |
| 150 | + i18n_embed::select(&*LANGUAGE_LOADER, &TRANSLATIONS, &requested_languages).unwrap(); |
| 151 | + age::localizer().select(&requested_languages).unwrap(); |
| 152 | + |
| 153 | + let args = args().collect::<Vec<_>>(); |
| 154 | + |
| 155 | + if console::user_attended() && args.len() == 1 { |
| 156 | + // If gumdrop ever merges that PR, that can be used here |
| 157 | + // instead. |
| 158 | + println!("{} {} [OPTIONS]", fl!("usage-header"), args[0]); |
| 159 | + println!(); |
| 160 | + println!("{}", AgeMountOptions::usage()); |
| 161 | + |
| 162 | + return Ok(()); |
| 163 | + } |
| 164 | + |
| 165 | + let opts = AgeMountOptions::parse_args_default_or_exit(); |
| 166 | + |
| 167 | + if opts.version { |
| 168 | + println!("rage-mount-dir {}", env!("CARGO_PKG_VERSION")); |
| 169 | + return Ok(()); |
| 170 | + } |
| 171 | + |
| 172 | + if opts.directory.is_empty() { |
| 173 | + return Err(Error::MissingSource); |
| 174 | + } |
| 175 | + if opts.mountpoint.is_empty() { |
| 176 | + return Err(Error::MissingMountpoint); |
| 177 | + } |
| 178 | + |
| 179 | + let directory = PathBuf::from(opts.directory); |
| 180 | + if !directory.is_dir() { |
| 181 | + return Err(Error::SourceMustBeDir); |
| 182 | + } |
| 183 | + let mountpoint = PathBuf::from(opts.mountpoint); |
| 184 | + if !mountpoint.is_dir() { |
| 185 | + return Err(Error::MountpointMustBeDir); |
| 186 | + } |
| 187 | + |
| 188 | + mount_fs( |
| 189 | + || crate::overlay::AgeOverlayFs::new(directory.into()), |
| 190 | + mountpoint, |
| 191 | + ); |
| 192 | + Ok(()) |
| 193 | +} |
0 commit comments