Skip to main content

compatdb_core/
table.rs

1//! The table of rules, each with where it comes from.
2
3#[cfg(doc)]
4use crate::{ENV_VAR, Origin};
5use crate::{Exe, Resolution, Rule, Specificity, VersionInfo, builtin};
6
7/// Where a rule of a [`Table`] comes from. It decides how the log names the
8/// rule's values (see [`Origin`]) and whether its `env` entries are defaults
9/// (see [`Origin::env_is_default`]). Never serialized.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Source {
12    /// Compiled into `compatdb.so`: a rule of [`Table::builtin`].
13    BuiltIn,
14    /// Line `n` of [`ENV_VAR`], counted from 1 as [`Table::parse_lenient`]
15    /// reads the value, blank lines and a legacy header included.
16    Line(usize),
17}
18
19/// The whole database: the built-in rules followed by the rules of
20/// [`ENV_VAR`], in the order they were declared, each with its [`Source`].
21///
22/// A launcher builds the rules it sends with [`Table::new`] and
23/// [`Table::push`] and writes them with `to_string()`, which is the value of
24/// [`ENV_VAR`]; [`Table::with_builtin`] puts the built-in rules in front, as
25/// `compatdb.so` does, to see what a process would get.
26#[derive(Clone, Default, Debug, PartialEq, Eq)]
27pub struct Table {
28    /// The rules, in the order they were declared, which is the order
29    /// [`Table::resolve`] folds rules of equal place in its order in.
30    pub(crate) rules: Vec<Rule>,
31    /// Where each rule comes from, at the same index.
32    pub(crate) sources: Vec<Source>,
33}
34
35impl Table {
36    /// The table without rules.
37    #[must_use]
38    pub const fn new() -> Self {
39        Self {
40            rules: Vec::new(),
41            sources: Vec::new(),
42        }
43    }
44
45    /// The table of the built-in rules ([`builtin::rules`]), which
46    /// `compatdb.so` puts in front of the rules of [`ENV_VAR`].
47    #[must_use]
48    pub fn builtin() -> Self {
49        Self::of_builtin(builtin::rules())
50    }
51
52    /// The table of `rules`, each a built-in rule.
53    pub(crate) fn of_builtin(rules: Vec<Rule>) -> Self {
54        Self {
55            sources: vec![Source::BuiltIn; rules.len()],
56            rules,
57        }
58    }
59
60    /// This table with the built-in rules in front of its own, as
61    /// `compatdb.so` builds its table.
62    #[must_use]
63    pub fn with_builtin(self) -> Self {
64        let mut table = Self::builtin();
65        table.append(self);
66        table
67    }
68
69    /// Add the rules of `other` after these, with their sources.
70    pub(crate) fn append(&mut self, other: Self) {
71        self.rules.extend(other.rules);
72        self.sources.extend(other.sources);
73    }
74
75    /// Add `rule` after the others as the rule of line `line` of
76    /// [`ENV_VAR`].
77    pub(crate) fn push_line(&mut self, rule: Rule, line: usize) {
78        self.rules.push(rule);
79        self.sources.push(Source::Line(line));
80    }
81
82    /// Add `rule` after the others, as a rule of [`ENV_VAR`] on the line
83    /// after the last line of the table, which for a table built with this
84    /// method alone is its line in the value the table writes.
85    pub fn push(&mut self, rule: Rule) {
86        let last = self
87            .sources
88            .iter()
89            .filter_map(|source| match source {
90                Source::Line(n) => Some(*n),
91                Source::BuiltIn => None,
92            })
93            .max()
94            .unwrap_or(0);
95        self.push_line(rule, last.saturating_add(1));
96    }
97
98    /// The rules, in the order they were declared.
99    #[must_use]
100    pub fn rules(&self) -> &[Rule] {
101        &self.rules
102    }
103
104    /// Where each rule of [`Table::rules`] comes from, at the same index.
105    #[must_use]
106    pub fn sources(&self) -> &[Source] {
107        &self.sources
108    }
109
110    /// Each rule with its source, in the order they were declared.
111    pub(crate) fn entries(&self) -> impl Iterator<Item = (Source, &Rule)> {
112        self.sources.iter().copied().zip(&self.rules)
113    }
114
115    /// What a process with the image basename `exe` and the version
116    /// resource `version` would get from `compatdb.so` with this table in
117    /// [`ENV_VAR`]: the built-in rules followed by this table's own (a
118    /// built-in rule in this table is left out, since the library has it
119    /// already), resolved, with [`Resolution::fold_derived_env`] done.
120    ///
121    /// Which env entries apply also depends on the environment the process
122    /// starts with, which [`Resolution::env_steps`] takes.
123    #[must_use]
124    pub fn preview(&self, exe: &str, version: &VersionInfo) -> Resolution {
125        let mut table = Self::builtin();
126        for (source, rule) in self.entries() {
127            if source != Source::BuiltIn {
128                table.rules.push(rule.clone());
129                table.sources.push(source);
130            }
131        }
132        let mut resolution = table.resolve(exe, version);
133        resolution.fold_derived_env();
134        resolution
135    }
136
137    /// The `WINEDEBUG` entries the `debug_channels` of this table's rules
138    /// stand for, in table order. A launcher joins them with `,` after its
139    /// own entries and starts Wine with the result, since Wine reads the
140    /// variable before `compatdb.so` loads.
141    ///
142    /// An entry of a rule for an executable is scoped to it as
143    /// `<exe>:<entry>`, which is Wine's own syntax: Wine compares the prefix
144    /// with the image file name ASCII-case-insensitively and knows nothing
145    /// of fingerprints, so the entry reaches every program of that name. An
146    /// entry of a rule with `exe` `*` is unscoped. Wine lets a later entry
147    /// win for the same channel.
148    #[must_use]
149    pub fn winedebug(&self) -> Vec<String> {
150        self.rules
151            .iter()
152            .flat_map(|rule| {
153                rule.debug_channels
154                    .iter()
155                    .map(move |channel| match &rule.exe {
156                        Exe::Any => channel.to_string(),
157                        Exe::Name(name) => format!("{name}:{channel}"),
158                    })
159            })
160            .collect()
161    }
162
163    /// Every pair of rules that matches exactly the same processes, by their
164    /// index in the table. A duplicate can only be a mistake, so the
165    /// launcher warns about it.
166    ///
167    /// Plain wildcard rules (`exe = *` with no fingerprint) are left out: they
168    /// are defaults that are meant to stack, one per setting, so several of
169    /// them matching every process is not a mistake.
170    #[must_use]
171    pub fn duplicate_matchers(&self) -> Vec<(usize, usize)> {
172        let mut pairs = Vec::new();
173        for (i, rule) in self.rules.iter().enumerate() {
174            if rule.specificity() == Specificity::Any {
175                continue;
176            }
177            for (j, other) in self.rules.iter().enumerate().skip(i.saturating_add(1)) {
178                if rule.matches_the_same_as(other) {
179                    pairs.push((i, j));
180                }
181            }
182        }
183        pairs
184    }
185}
186
187#[cfg(test)]
188mod tests;