config: Overhaul config format and implementation
- Split the config system into multiple files in its own folder. - Use a manual Deserialize implementation for VtSelection. - Change config format to use sections for better organization. - Add temporary fallback to parse old format.
This commit is contained in:
@@ -1,141 +0,0 @@
|
||||
use std::{env, fs::read_to_string};
|
||||
|
||||
use getopts::Options;
|
||||
use serde::Deserialize;
|
||||
|
||||
fn default_vt() -> toml::Value {
|
||||
toml::Value::String("next".to_string())
|
||||
}
|
||||
|
||||
fn default_greeter_user() -> String {
|
||||
"greeter".to_string()
|
||||
}
|
||||
|
||||
fn default_socket_path() -> String {
|
||||
"/run/greetd.sock".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_vt")]
|
||||
pub vt: toml::Value,
|
||||
pub greeter: String,
|
||||
#[serde(default = "default_greeter_user")]
|
||||
pub greeter_user: String,
|
||||
|
||||
#[serde(default = "default_socket_path")]
|
||||
pub socket_path: String,
|
||||
|
||||
#[serde(skip_deserializing)]
|
||||
pub session_worker: usize,
|
||||
}
|
||||
|
||||
pub enum VtSelection {
|
||||
Next,
|
||||
Current,
|
||||
None,
|
||||
Specific(usize),
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn vt(&self) -> VtSelection {
|
||||
match &self.vt {
|
||||
toml::Value::String(s) => match s.as_str() {
|
||||
"next" => VtSelection::Next,
|
||||
"current" => VtSelection::Current,
|
||||
"none" => VtSelection::None,
|
||||
_ => panic!("unknown value of vt, expect next, current, none, or vt number"),
|
||||
},
|
||||
toml::Value::Integer(u) => VtSelection::Specific(*u as usize),
|
||||
_ => panic!("unknown value of vt, expect next, current, or vt number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_usage(program: &str, opts: Options) {
|
||||
let brief = format!("Usage: {} [options]", program);
|
||||
print!("{}", opts.usage(&brief));
|
||||
}
|
||||
|
||||
pub fn read_config() -> Config {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let program = args[0].clone();
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "print this help menu");
|
||||
opts.optopt("t", "vt", "VT to run on", "VT");
|
||||
opts.optopt("s", "socket-path", "socket path to use", "SOCKET_PATH");
|
||||
opts.optopt("g", "greeter", "greeter to run", "GREETER");
|
||||
opts.optopt("u", "greeter-user", "user to run greeter as", "USER");
|
||||
opts.optopt("c", "config", "config file to use", "CONFIG_FILE");
|
||||
opts.optopt(
|
||||
"w",
|
||||
"session-worker",
|
||||
"start a session worker (internal)",
|
||||
"FD",
|
||||
);
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => panic!(f.to_string()),
|
||||
};
|
||||
if matches.opt_present("h") {
|
||||
print_usage(&program, opts);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let mut config = match read_to_string(
|
||||
matches
|
||||
.opt_str("config")
|
||||
.unwrap_or_else(|| "/etc/greetd/config.toml".to_string()),
|
||||
) {
|
||||
Ok(s) => match toml::from_str(&s) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Unable to parse configuration file: {:?}", e);
|
||||
eprintln!("Please fix the configuration file and try again.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(_) => Config {
|
||||
vt: default_vt(),
|
||||
greeter: "".to_string(),
|
||||
greeter_user: "".to_string(),
|
||||
socket_path: default_socket_path(),
|
||||
session_worker: 0,
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(vt) = matches.opt_str("vt") {
|
||||
config.vt = match vt.as_str() {
|
||||
"next" => toml::Value::String("next".to_string()),
|
||||
"current" => toml::Value::String("current".to_string()),
|
||||
"none" => toml::Value::String("none".to_string()),
|
||||
v => toml::Value::Integer(v.parse().expect("could not parse vt number")),
|
||||
}
|
||||
}
|
||||
if let Some(greeter) = matches.opt_str("greeter") {
|
||||
config.greeter = greeter;
|
||||
}
|
||||
if let Some(user) = matches.opt_str("greeter-user") {
|
||||
config.greeter_user = user;
|
||||
}
|
||||
if let Some(socket_path) = matches.opt_str("socket-path") {
|
||||
config.socket_path = socket_path;
|
||||
}
|
||||
if let Some(session_worker) = matches
|
||||
.opt_get("session-worker")
|
||||
.expect("unable to parse session-worker")
|
||||
{
|
||||
config.session_worker = session_worker
|
||||
}
|
||||
|
||||
if config.greeter.is_empty() {
|
||||
eprintln!("No greeter specified. Run with --help for more information.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if config.greeter_user.is_empty() {
|
||||
eprintln!("No greeter user specified. Run with --help for more information.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
9
greetd/src/config/defaults.rs
Normal file
9
greetd/src/config/defaults.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use super::vtselection::VtSelection;
|
||||
|
||||
pub fn default_vt() -> VtSelection {
|
||||
VtSelection::Next
|
||||
}
|
||||
|
||||
pub fn default_greeter_user() -> String {
|
||||
"greeter".to_string()
|
||||
}
|
93
greetd/src/config/mod.rs
Normal file
93
greetd/src/config/mod.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
mod defaults;
|
||||
mod old_config;
|
||||
mod types;
|
||||
mod vtselection;
|
||||
|
||||
use std::{env, fs::read_to_string};
|
||||
|
||||
use getopts::Options;
|
||||
|
||||
use super::error::Error;
|
||||
use old_config::*;
|
||||
|
||||
pub use types::*;
|
||||
pub use vtselection::VtSelection;
|
||||
|
||||
fn print_usage(program: &str, opts: Options) {
|
||||
let brief = format!("Usage: {} [options]", program);
|
||||
print!("{}", opts.usage(&brief));
|
||||
}
|
||||
|
||||
pub fn read_config() -> Result<Config, Error> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let program = args[0].clone();
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "print this help menu");
|
||||
opts.optopt("s", "socket-path", "socket path to use", "SOCKET_PATH");
|
||||
opts.optopt("c", "config", "config file to use", "CONFIG_FILE");
|
||||
opts.optopt(
|
||||
"w",
|
||||
"session-worker",
|
||||
"start a session worker (internal)",
|
||||
"FD",
|
||||
);
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => return Err(format!("could not parse arguments: {}", f).into()),
|
||||
};
|
||||
if matches.opt_present("h") {
|
||||
print_usage(&program, opts);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let internal = ConfigInternal {
|
||||
socket_path: matches
|
||||
.opt_str("socket-path")
|
||||
.unwrap_or_else(|| "/run/greetd.sock".to_string()),
|
||||
session_worker: matches
|
||||
.opt_get("session-worker")
|
||||
.expect("unable to parse session-worker")
|
||||
.unwrap_or(0),
|
||||
};
|
||||
|
||||
let file = match read_to_string(
|
||||
matches
|
||||
.opt_str("config")
|
||||
.unwrap_or_else(|| "/etc/greetd/config.toml".to_string()),
|
||||
) {
|
||||
Ok(s) => match toml::from_str(&s) {
|
||||
Ok(v) => v,
|
||||
Err(e) => match try_read_old_config(&s) {
|
||||
Ok(v) => {
|
||||
eprintln!("warning: Fallback to old config format, caused by : {}", e);
|
||||
v
|
||||
}
|
||||
Err(_e) => {
|
||||
return Err(Error::ConfigError(
|
||||
format!("unable to parse configuration file: {}", e).to_string(),
|
||||
))
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(_) => ConfigFile {
|
||||
default_session: ConfigDefaultSession {
|
||||
user: "greeter".to_string(),
|
||||
command: "".to_string(),
|
||||
},
|
||||
terminal: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
if file.default_session.command.is_empty() {
|
||||
return Err(Error::ConfigError(
|
||||
"no default session user specified".to_string(),
|
||||
));
|
||||
}
|
||||
if file.default_session.user.is_empty() {
|
||||
return Err(Error::ConfigError(
|
||||
"no default session user specified".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Config { file, internal })
|
||||
}
|
35
greetd/src/config/old_config.rs
Normal file
35
greetd/src/config/old_config.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use super::vtselection::VtSelection;
|
||||
|
||||
use super::{defaults::*, types::*};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct OldConfig {
|
||||
#[serde(default = "default_vt")]
|
||||
pub vt: VtSelection,
|
||||
pub greeter: String,
|
||||
#[serde(default = "default_greeter_user")]
|
||||
pub greeter_user: String,
|
||||
}
|
||||
|
||||
pub fn try_read_old_config(s: &str) -> Result<super::ConfigFile, Error> {
|
||||
let oldconfig: OldConfig = match toml::from_str(&s) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Error::ConfigError(
|
||||
format!("unable to parse configuration file: {}", e).to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ConfigFile {
|
||||
terminal: ConfigTerminal { vt: oldconfig.vt },
|
||||
default_session: ConfigDefaultSession {
|
||||
user: oldconfig.greeter_user,
|
||||
command: oldconfig.greeter,
|
||||
},
|
||||
})
|
||||
}
|
37
greetd/src/config/types.rs
Normal file
37
greetd/src/config/types.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use super::{defaults::*, vtselection::VtSelection};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigDefaultSession {
|
||||
pub command: String,
|
||||
#[serde(default = "default_greeter_user")]
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
pub struct ConfigInternal {
|
||||
pub socket_path: String,
|
||||
pub session_worker: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigTerminal {
|
||||
#[serde(default = "default_vt")]
|
||||
pub vt: VtSelection,
|
||||
}
|
||||
|
||||
impl Default for ConfigTerminal {
|
||||
fn default() -> ConfigTerminal {
|
||||
ConfigTerminal { vt: default_vt() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigFile {
|
||||
pub terminal: ConfigTerminal,
|
||||
pub default_session: ConfigDefaultSession,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub file: ConfigFile,
|
||||
pub internal: ConfigInternal,
|
||||
}
|
62
greetd/src/config/vtselection.rs
Normal file
62
greetd/src/config/vtselection.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde::{
|
||||
de::{self, Deserializer, Visitor},
|
||||
Deserialize,
|
||||
};
|
||||
|
||||
struct VtSelectionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for VtSelectionVisitor {
|
||||
type Value = VtSelection;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("next, currrent, none or a positive vt number")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
if value < 1 {
|
||||
Err(de::Error::invalid_value(
|
||||
de::Unexpected::Signed(value),
|
||||
&"next, current, none or a positive vt number",
|
||||
))
|
||||
} else {
|
||||
Ok(VtSelection::Specific(value as usize))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match value {
|
||||
"next" => Ok(VtSelection::Next),
|
||||
"current" => Ok(VtSelection::Current),
|
||||
"none" => Ok(VtSelection::None),
|
||||
_ => Err(de::Error::invalid_value(
|
||||
de::Unexpected::Str(value),
|
||||
&"next, current, none or a postive vt number",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VtSelection {
|
||||
Next,
|
||||
Current,
|
||||
None,
|
||||
Specific(usize),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for VtSelection {
|
||||
fn deserialize<D>(deserializer: D) -> Result<VtSelection, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(VtSelectionVisitor)
|
||||
}
|
||||
}
|
@@ -16,6 +16,9 @@ pub enum Error {
|
||||
|
||||
#[error("i/o error: {0}")]
|
||||
Io(String),
|
||||
|
||||
#[error("configuration error: {0}")]
|
||||
ConfigError(String),
|
||||
}
|
||||
|
||||
impl From<Box<dyn std::error::Error>> for Error {
|
||||
|
@@ -21,7 +21,7 @@ use tokio::task;
|
||||
use crate::{error::Error, session::worker};
|
||||
|
||||
async fn session_worker_main(config: config::Config) -> Result<(), Error> {
|
||||
let raw_fd = config.session_worker as RawFd;
|
||||
let raw_fd = config.internal.session_worker as RawFd;
|
||||
let mut cur_flags = unsafe { FdFlag::from_bits_unchecked(fcntl(raw_fd, FcntlArg::F_GETFD)?) };
|
||||
cur_flags.insert(FdFlag::FD_CLOEXEC);
|
||||
fcntl(raw_fd, FcntlArg::F_SETFD(cur_flags))?;
|
||||
@@ -31,11 +31,17 @@ async fn session_worker_main(config: config::Config) -> Result<(), Error> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config = config::read_config();
|
||||
let config = match config::read_config() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
mlockall(MlockAllFlags::all()).expect("unable to lock pages");
|
||||
let res = task::LocalSet::new()
|
||||
.run_until(async move {
|
||||
if config.session_worker > 0 {
|
||||
if config.internal.session_worker > 0 {
|
||||
session_worker_main(config).await
|
||||
} else {
|
||||
server::main(config).await
|
||||
|
@@ -77,17 +77,12 @@ async fn client_handler(ctx: &Context, mut s: UnixStream) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
pub async fn main(config: Config) -> Result<(), Error> {
|
||||
std::env::set_var("GREETD_SOCK", &config.socket_path);
|
||||
std::env::set_var("GREETD_SOCK", &config.internal.socket_path);
|
||||
|
||||
let _ = std::fs::remove_file(config.socket_path.clone());
|
||||
let mut listener = UnixListener::bind(&config.socket_path)
|
||||
let _ = std::fs::remove_file(&config.internal.socket_path);
|
||||
let mut listener = UnixListener::bind(&config.internal.socket_path)
|
||||
.map_err(|e| format!("unable to open listener: {}", e))?;
|
||||
|
||||
let u = users::get_user_by_name(&config.greeter_user).ok_or(format!(
|
||||
"configured greeter user '{}' not found",
|
||||
&config.greeter_user
|
||||
))?;
|
||||
|
||||
let service = if Path::new("/etc/pam.d/greetd").exists() {
|
||||
"greetd"
|
||||
} else if Path::new("/etc/pam.d/login").exists() {
|
||||
@@ -97,13 +92,22 @@ pub async fn main(config: Config) -> Result<(), Error> {
|
||||
return Err("PAM 'greetd' service missing".into());
|
||||
};
|
||||
|
||||
let u = users::get_user_by_name(&config.file.default_session.user).ok_or(format!(
|
||||
"configured default session user '{}' not found",
|
||||
&config.file.default_session.user
|
||||
))?;
|
||||
|
||||
let uid = Uid::from_raw(u.uid());
|
||||
let gid = Gid::from_raw(u.primary_group_id());
|
||||
chown(config.socket_path.as_str(), Some(uid), Some(gid))
|
||||
.map_err(|e| format!("unable to chown greetd socket: {}", e))?;
|
||||
chown(config.internal.socket_path.as_str(), Some(uid), Some(gid)).map_err(|e| {
|
||||
format!(
|
||||
"unable to chown greetd socket at {}: {}",
|
||||
&config.internal.socket_path, e
|
||||
)
|
||||
})?;
|
||||
|
||||
let term = Terminal::open(0).map_err(|e| format!("unable to open terminal: {}", e))?;
|
||||
let vt = match config.vt() {
|
||||
let vt = match config.file.terminal.vt {
|
||||
VtSelection::Current => term
|
||||
.vt_get_current()
|
||||
.map_err(|e| format!("unable to get current VT: {}", e))?,
|
||||
@@ -116,11 +120,12 @@ pub async fn main(config: Config) -> Result<(), Error> {
|
||||
drop(term);
|
||||
|
||||
let ctx = Rc::new(Context::new(
|
||||
config.greeter,
|
||||
config.greeter_user,
|
||||
config.file.default_session.command,
|
||||
config.file.default_session.user,
|
||||
vt,
|
||||
service.to_string(),
|
||||
));
|
||||
|
||||
if let Err(e) = ctx.greet().await {
|
||||
eprintln!("unable to start greeter: {}", e);
|
||||
reset_vt(vt).map_err(|e| format!("unable to reset VT: {}", e))?;
|
||||
|
@@ -10,6 +10,10 @@ greetd uses a simple TOML configuration file to define its behavior.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
## terminal
|
||||
|
||||
This section contains terminal configuration.
|
||||
|
||||
*vt* = num|"next"|"current"
|
||||
The VT to run on. Can be the number of a specific VT, "next" to select the
|
||||
next available VT, or "current" to stay wherever greetd was started. The
|
||||
@@ -23,16 +27,44 @@ greetd uses a simple TOML configuration file to define its behavior.
|
||||
|
||||
Use of a specific VT with appropriate conflict avoidance is recommended.
|
||||
|
||||
*greeter* = command-line
|
||||
The command-line to run to start the greeter, e.g. "agreety -c sway". The
|
||||
greeter will be run when greetd is initially launched, and every time a
|
||||
created session terminates.
|
||||
## default_session
|
||||
|
||||
This section describes the default session, also referred to as the "greeter".
|
||||
|
||||
*command* = command-line
|
||||
The command-line to run to start the default session, e.g. "agreety -c sway".
|
||||
The default session is automatically started when no other session is
|
||||
running, such as when user session terminate, and when greetd is initially
|
||||
started with no initial session configured.
|
||||
|
||||
See *greetd-ipc*(7) for information on how a greeter can create sessions.
|
||||
|
||||
*greeter_user* = user
|
||||
*user* = user
|
||||
The user to use for running the greeter. Defaults to "greeter".
|
||||
|
||||
## initial_session
|
||||
|
||||
This optional section describes the initial session, commonly referred to as
|
||||
"auto-login".
|
||||
|
||||
*command* = command-line
|
||||
The command-line to run to start the initial session, e.g. "sway". The
|
||||
initial session will be run when exactly once when greetd is initially
|
||||
launched.
|
||||
|
||||
*user* = user
|
||||
The user to use for running the initial session.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
```toml
|
||||
[terminal]
|
||||
vt = 1
|
||||
|
||||
[default_session]
|
||||
command = "agreety -c sway"
|
||||
```
|
||||
|
||||
# AUTHORS
|
||||
|
||||
Maintained by Kenny Levinsen <contact@kl.wtf>. For more information about
|
||||
|
Reference in New Issue
Block a user