shadowsocks_rust/
config.rs1use std::{
4 env,
5 fs::OpenOptions,
6 io::{self, Read},
7 path::{Path, PathBuf},
8 str::FromStr,
9};
10
11use clap::ArgMatches;
12use directories::ProjectDirs;
13use serde::Deserialize;
14
15pub fn get_default_config_path(config_file: &str) -> Option<PathBuf> {
17 let config_files = vec![config_file, "config.json"];
19 if let Ok(mut path) = env::current_dir() {
20 for filename in &config_files {
21 path.push(filename);
22 if path.exists() {
23 return Some(path);
24 }
25 path.pop();
26 }
27 } else {
28 for filename in &config_files {
30 let relative_path = PathBuf::from(filename);
31 if relative_path.exists() {
32 return Some(relative_path);
33 }
34 }
35 }
36
37 if let Some(project_dirs) = ProjectDirs::from("org", "shadowsocks", "shadowsocks-rust") {
39 let mut config_path = project_dirs.config_dir().to_path_buf();
45 for filename in &config_files {
46 config_path.push(filename);
47 if config_path.exists() {
48 return Some(config_path);
49 }
50 config_path.pop();
51 }
52 }
53
54 #[cfg(unix)]
57 {
58 let base_directories = xdg::BaseDirectories::with_prefix("shadowsocks-rust");
59 for filename in &config_files {
62 if let Some(config_path) = base_directories.find_config_file(filename) {
63 return Some(config_path);
64 }
65 }
66 }
67
68 #[cfg(unix)]
70 {
71 let mut global_config_path = PathBuf::from("/etc/shadowsocks-rust");
72 for filename in &config_files {
73 global_config_path.push(filename);
74 if global_config_path.exists() {
75 return Some(global_config_path.to_path_buf());
76 }
77 global_config_path.pop();
78 }
79 }
80
81 None
82}
83
84#[derive(thiserror::Error, Debug)]
86pub enum ConfigError {
87 #[error("{0}")]
89 IoError(#[from] io::Error),
90 #[error("{0}")]
92 JsonError(#[from] json5::Error),
93 #[error("Invalid value: {0}")]
95 InvalidValue(String),
96}
97
98#[derive(Debug, Clone, Default)]
100pub struct Config {
101 #[cfg(feature = "logging")]
103 pub log: LogConfig,
104
105 pub runtime: RuntimeConfig,
107}
108
109impl Config {
110 pub fn load_from_file<P: AsRef<Path>>(filename: &P) -> Result<Self, ConfigError> {
112 let filename = filename.as_ref();
113
114 let mut reader = OpenOptions::new().read(true).open(filename)?;
115 let mut content = String::new();
116 reader.read_to_string(&mut content)?;
117
118 Self::load_from_str(&content)
119 }
120
121 pub fn load_from_str(s: &str) -> Result<Self, ConfigError> {
123 let ssconfig = json5::from_str(s)?;
124 Self::load_from_ssconfig(ssconfig)
125 }
126
127 fn load_from_ssconfig(ssconfig: SSConfig) -> Result<Self, ConfigError> {
128 let mut config = Self::default();
129
130 #[cfg(feature = "logging")]
131 if let Some(log) = ssconfig.log {
132 let mut nlog = LogConfig::default();
133 if let Some(level) = log.level {
134 nlog.level = level;
135 }
136
137 if let Some(format) = log.format {
138 let mut nformat = LogFormatConfig::default();
139 if let Some(without_time) = format.without_time {
140 nformat.without_time = without_time;
141 }
142 nlog.format = nformat;
143 }
144
145 if let Some(config_path) = log.config_path {
146 nlog.config_path = Some(PathBuf::from(config_path));
147 }
148
149 config.log = nlog;
150 }
151
152 if let Some(runtime) = ssconfig.runtime {
153 let mut nruntime = RuntimeConfig::default();
154
155 #[cfg(feature = "multi-threaded")]
156 if let Some(worker_count) = runtime.worker_count {
157 nruntime.worker_count = Some(worker_count);
158 }
159
160 if let Some(mode) = runtime.mode {
161 match mode.parse::<RuntimeMode>() {
162 Ok(m) => nruntime.mode = m,
163 Err(..) => return Err(ConfigError::InvalidValue(mode)),
164 }
165 }
166
167 config.runtime = nruntime;
168 }
169
170 Ok(config)
171 }
172
173 pub fn set_options(&mut self, matches: &ArgMatches) {
175 #[cfg(feature = "logging")]
176 {
177 let debug_level = matches.get_count("VERBOSE");
178 if debug_level > 0 {
179 self.log.level = debug_level as u32;
180 }
181
182 if matches.get_flag("LOG_WITHOUT_TIME") {
183 self.log.format.without_time = true;
184 }
185
186 if let Some(log_config) = matches.get_one::<PathBuf>("LOG_CONFIG").cloned() {
187 self.log.config_path = Some(log_config);
188 }
189 }
190
191 #[cfg(feature = "multi-threaded")]
192 if matches.get_flag("SINGLE_THREADED") {
193 self.runtime.mode = RuntimeMode::SingleThread;
194 }
195
196 #[cfg(feature = "multi-threaded")]
197 if let Some(worker_count) = matches.get_one::<usize>("WORKER_THREADS") {
198 self.runtime.worker_count = Some(*worker_count);
199 }
200
201 let _ = matches;
202 }
203}
204
205#[cfg(feature = "logging")]
207#[derive(Debug, Clone, Default)]
208pub struct LogConfig {
209 pub level: u32,
211 pub format: LogFormatConfig,
213 pub config_path: Option<PathBuf>,
215}
216
217#[cfg(feature = "logging")]
219#[derive(Debug, Clone, Default)]
220pub struct LogFormatConfig {
221 pub without_time: bool,
222}
223
224#[derive(Debug, Clone, Copy, Default)]
226pub enum RuntimeMode {
227 #[cfg_attr(not(feature = "multi-threaded"), default)]
229 SingleThread,
230 #[cfg(feature = "multi-threaded")]
232 #[cfg_attr(feature = "multi-threaded", default)]
233 MultiThread,
234}
235
236#[derive(Debug)]
238pub struct RuntimeModeError;
239
240impl FromStr for RuntimeMode {
241 type Err = RuntimeModeError;
242
243 fn from_str(s: &str) -> Result<Self, Self::Err> {
244 match s {
245 "single_thread" => Ok(Self::SingleThread),
246 #[cfg(feature = "multi-threaded")]
247 "multi_thread" => Ok(Self::MultiThread),
248 _ => Err(RuntimeModeError),
249 }
250 }
251}
252
253#[derive(Debug, Clone, Default)]
255pub struct RuntimeConfig {
256 #[cfg(feature = "multi-threaded")]
258 pub worker_count: Option<usize>,
259 pub mode: RuntimeMode,
261}
262
263#[derive(Deserialize)]
264struct SSConfig {
265 #[cfg(feature = "logging")]
266 log: Option<SSLogConfig>,
267 runtime: Option<SSRuntimeConfig>,
268}
269
270#[cfg(feature = "logging")]
271#[derive(Deserialize)]
272struct SSLogConfig {
273 level: Option<u32>,
274 format: Option<SSLogFormat>,
275 config_path: Option<String>,
276}
277
278#[cfg(feature = "logging")]
279#[derive(Deserialize)]
280struct SSLogFormat {
281 without_time: Option<bool>,
282}
283
284#[derive(Deserialize)]
285struct SSRuntimeConfig {
286 #[cfg(feature = "multi-threaded")]
287 worker_count: Option<usize>,
288 mode: Option<String>,
289}