~goorzhel/radm

ref: b4ca8d0dde821420d66d488db969c0f7c5da5f6b radm/src/cli.rs -rw-r--r-- 4.1 KiB
b4ca8d0d — Antonio Gurgel 0.3.0 4 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
//! Command-line interface.

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

use anyhow::{anyhow, Context, Result};
use clap::Parser;
use log::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(())
}

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,
        }
    }
}