Skip to main content

shrub_core/shell/
initscripts.rs

1//! Init script support for shells.
2
3use std::path::PathBuf;
4
5use crate::{Shell, error, extensions, interp};
6
7/// Behavior for loading profile files.
8#[derive(Default)]
9pub enum ProfileLoadBehavior {
10    /// Load the default profile files.
11    #[default]
12    LoadDefault,
13    /// Skip loading profile files.
14    Skip,
15}
16
17impl ProfileLoadBehavior {
18    /// Returns whether profile loading should be skipped.
19    pub const fn skip(&self) -> bool {
20        matches!(self, Self::Skip)
21    }
22}
23
24/// Behavior for loading rc files.
25#[derive(Default)]
26pub enum RcLoadBehavior {
27    /// Load the default rc files.
28    #[default]
29    LoadDefault,
30    /// Load a custom rc file; do not load defaults.
31    LoadCustom(PathBuf),
32    /// Skip loading rc files.
33    Skip,
34}
35
36impl RcLoadBehavior {
37    /// Returns whether rc loading should be skipped.
38    pub const fn skip(&self) -> bool {
39        matches!(self, Self::Skip)
40    }
41}
42
43impl<SE: extensions::ShellExtensions> Shell<SE> {
44    /// Loads and executes standard shell configuration files (i.e., rc and
45    /// profile), and then loads the history file (if command history is
46    /// enabled). History is loaded last since the configuration files may
47    /// change `HISTFILE`.
48    ///
49    /// # Arguments
50    ///
51    /// * `profile_behavior` - Behavior for loading profile files.
52    /// * `rc_behavior` - Behavior for loading rc files.
53    pub async fn load_config(
54        &mut self,
55        profile_behavior: &ProfileLoadBehavior,
56        rc_behavior: &RcLoadBehavior,
57    ) -> Result<(), error::Error> {
58        self.load_config_files(profile_behavior, rc_behavior)
59            .await?;
60
61        // As bash does, skip the file if startup files already added entries
62        // (e.g. via `history -s`). Do NOT fail if we can't load
63        // history.
64        if self.options.enable_command_history
65            && self
66                .history
67                .as_ref()
68                .is_none_or(crate::history::History::is_empty)
69            && let Ok(Some(history)) = self.load_history()
70        {
71            self.history = Some(history);
72        }
73
74        Ok(())
75    }
76
77    async fn load_config_files(
78        &mut self,
79        profile_behavior: &ProfileLoadBehavior,
80        rc_behavior: &RcLoadBehavior,
81    ) -> Result<(), error::Error> {
82        let mut params = self.default_exec_params();
83        params.process_group_policy = interp::ProcessGroupPolicy::SameProcessGroup;
84
85        if self.options.login_shell {
86            // --noprofile means skip this.
87            if matches!(profile_behavior, ProfileLoadBehavior::Skip) {
88                return Ok(());
89            }
90
91            //
92            // Source the system profile if it exists.
93            //
94            // Next source the first of these that exists and is readable (if
95            // any):
96            //     * ~/.bash_profile
97            //     * ~/.bash_login
98            //     * ~/.profile
99            //
100            if let Some(system_profile) = crate::sys::fs::get_system_profile_path() {
101                self.source_if_exists(system_profile, &params).await?;
102            }
103            if let Some(home_path) = self.home_dir() {
104                if self.options.sh_mode {
105                    self.source_if_exists(home_path.join(".profile").as_path(), &params)
106                        .await?;
107                } else {
108                    if !self
109                        .source_if_exists(home_path.join(".bash_profile").as_path(), &params)
110                        .await?
111                    {
112                        if !self
113                            .source_if_exists(home_path.join(".bash_login").as_path(), &params)
114                            .await?
115                        {
116                            self.source_if_exists(home_path.join(".profile").as_path(), &params)
117                                .await?;
118                        }
119                    }
120                }
121            }
122        } else {
123            if self.options.interactive {
124                match rc_behavior {
125                    _ if self.options.sh_mode => (),
126                    RcLoadBehavior::Skip => (),
127                    RcLoadBehavior::LoadCustom(rc_file) => {
128                        // If an explicit rc file is provided, source it.
129                        self.source_if_exists(rc_file, &params).await?;
130                    }
131                    RcLoadBehavior::LoadDefault => {
132                        //
133                        // Otherwise, for non-login interactive shells, load in
134                        // this order:
135                        //
136                        //     system rc file (e.g. /etc/bash.bashrc on Unix)
137                        //     ~/.bashrc
138                        //
139                        if let Some(system_rc) = crate::sys::fs::get_system_rc_path() {
140                            self.source_if_exists(system_rc, &params).await?;
141                        }
142                        if let Some(home_path) = self.home_dir() {
143                            self.source_if_exists(home_path.join(".bashrc").as_path(), &params)
144                                .await?;
145                            self.source_if_exists(home_path.join(".shrubrc").as_path(), &params)
146                                .await?;
147                        }
148                    }
149                }
150            } else {
151                let env_var_name = if self.options.sh_mode {
152                    "ENV"
153                } else {
154                    "BASH_ENV"
155                };
156
157                if self.env.is_set(env_var_name) {
158                    //
159                    // TODO(well-known-vars): look at $ENV/BASH_ENV; source its
160                    // expansion if that file exists
161                    //
162                    return error::unimp(
163                        "load config from $ENV/BASH_ENV for non-interactive, non-login shell",
164                    );
165                }
166            }
167        }
168
169        Ok(())
170    }
171}