~goorzhel/radm

ref: d822cd6f6b83262c129d34f6543994b6bc2b763b radm/src/cli.rs -rw-r--r-- 4.1 KiB
d822cd6f — Antonio Gurgel Add missing comments 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Command-line interface.

use std::{
    fs::{read_dir, OpenOptions},
    path::{Path, PathBuf},
};

use anyhow::{anyhow, Context, Result};
use clap::Parser;
use log::{debug, trace, LevelFilter};
use simplelog::WriteLogger;
use syslog::{BasicLogger, Facility, Formatter3164};
use xdg::BaseDirectories;

/// The options set at the command line.
#[derive(Parser, Debug)]
#[clap(about = "A console-based display manager.")]
pub struct Options {
    // Imperative mood instead of present indicative to be consistent with clap's -h option.
    // (Honestly, I'd use imperative everywhere, but the `std` crate doesn't, so I decided
    // to be consistent with that.)
    /// Focus the given TTY when rstdm starts.
    #[clap(short, long, name = "TTY")]
    pub focus_on_start: Option<i32>,
    /// Enable DEBUG-level logging (twice for TRACE).
    #[clap(short, long, parse(from_occurrences))]
    pub verbose: u8,
    /// Use `dbus-run-session` to start the desktop.
    #[clap(short, long, parse(from_flag))]
    pub dbus: Dbus,
    /// Include this folder when searching for sessions (can be used more than once).
    #[clap(short, long, name = "PATH")]
    pub session_dirs: Vec<PathBuf>,
    /// Log to this path instead of syslog.
    #[clap(short, long, name = "PATH")]
    pub log_file: Option<String>,
}

impl Options {
    pub fn new() -> Self {
        let mut out = Options::parse();

        /// Return each of the XDG_DATA_DIRS entries with `leaf_dir` appended, so long as the
        /// resulting path exists.
        ///
        /// Function exists because firing off a "Couldn't read /usr/local/share/wayland-sessions:
        /// no such directory" error line on every startup would be rude.
        fn gather_default_dirs(leaf_dir: &str) -> impl Iterator<Item = PathBuf> + '_ {
            let xdg = BaseDirectories::new().unwrap();
            // unwrap: there's an error if and only if:
            // 1. $HOME is unset, AND
            // 2. rstdm's user -- usually `root` -- has no homedir.
            xdg.get_data_dirs().into_iter().filter_map(move |mut p| {
                p.push(leaf_dir);
                trace!("Checking existence of {}", p.to_string_lossy());
                match read_dir(&p) {
                    Ok(_) => Some(p),
                    Err(_) => None,
                }
            })
        }

        out.session_dirs = out
            .session_dirs
            .into_iter()
            .chain(gather_default_dirs("wayland-sessions"))
            .collect::<Vec<_>>();
        out
    }

    fn verbosity(&self) -> LevelFilter {
        match self.verbose {
            0 => LevelFilter::Info,
            1 => LevelFilter::Debug,
            _ => LevelFilter::Trace,
        }
    }
}

/// Connects to a running syslog instance.
pub fn init_syslog(verbosity: LevelFilter) -> Result<()> {
    let formatter = Formatter3164 {
        facility: Facility::LOG_AUTH,
        hostname: None,
        process: "rstdm".into(),
        pid: std::process::id(),
    };
    let logger =
        syslog::unix(formatter).map_err(|e| anyhow!("Couldn't connect to syslog: {}", e))?;
    log::set_boxed_logger(Box::new(BasicLogger::new(logger)))?;
    log::set_max_level(verbosity);
    debug!("Syslog initialized");
    Ok(())
}

/// Initializes a file as a log sink.
pub fn init_log_file(path: &Path, verbosity: LevelFilter) -> Result<()> {
    let file = OpenOptions::new()
        .append(true)
        .create(true)
        .open(&path)
        .with_context(|| anyhow!("Couldn't open {}", &path.display()))?;
    WriteLogger::init(verbosity, Default::default(), file)?;
    debug!("Logfile {} initialized", path.display());
    Ok(())
}

/// Initializes either a log file or a syslog connection.
pub fn init_logger(opts: &Options) -> Result<()> {
    match &opts.log_file {
        Some(path) => init_log_file(Path::new(&path), opts.verbosity()),
        None => init_syslog(opts.verbosity()),
    }
}

#[derive(clap::ArgEnum, Debug, Clone)]
/// Whether a session should be run with D-Bus.
pub enum Dbus {
    No,
    Yes,
}

impl From<bool> for Dbus {
    fn from(b: bool) -> Self {
        match b {
            false => Dbus::No,
            true => Dbus::Yes,
        }
    }
}