1use std;
9use std::fs;
10use std::time::{Duration, SystemTime};
11use std::ops::BitAnd;
12use std::error::Error;
13use std::path::{Path, PathBuf};
14
15use libc;
16use time;
17use nix::sys::signal;
18
19use timber;
20
21use settings::Directories;
22use errors::Illusion;
23use log;
24
25const RUNTIME_DIR_VAR: &'static str = "XDG_RUNTIME_DIR";
28const DATA_DIR_VAR: &'static str = "XDG_DATA_HOME";
29const CACHE_DIR_VAR: &'static str = "XDG_CACHE_HOME";
30const CONFIG_DIR_VAR: &'static str = "XDG_CONFIG_HOME";
31
32const DEFAULT_RUNTIME_DIR: &'static str = "/tmp";
33const DEFAULT_SYSTEM_CONFIG_DIR_BASE: &'static str = "/etc/";
34
35const DEFAULT_DATA_DIR_FRAGMENT: &'static str = ".local/share";
36const DEFAULT_CACHE_DIR_FRAGMENT: &'static str = ".cache";
37const DEFAULT_CONFIG_DIR_FRAGMENT: &'static str = ".config";
38
39pub enum LogDestination {
42 StdOut,
43 LogFile,
44 Disabled,
45}
46
47pub enum Directory {
50 Data,
51 Cache,
52 Runtime,
53}
54
55pub struct Env {
58 dirs: Directories,
59 progname: String,
60}
61
62impl Env {
67 pub fn create(log_destination: LogDestination, progname: &str) -> Self {
73 Self::register_signal_handler();
75
76 let cache_dir = Self::create_cache_dir(progname).unwrap();
78 if let Err(err) = Self::initialize_logger(log_destination, &cache_dir, progname) {
79 log_warn1!("{}", err);
80 }
81
82 let data_dir = Self::create_data_dir(progname).unwrap();
84
85 let runtime_dir = Self::create_runtime_dir(progname).unwrap();
87
88 let (system_config_dir, user_config_dir) = Self::check_config_dirs(progname);
90
91 let mine = Env {
93 dirs: Directories {
94 runtime: runtime_dir,
95 data: data_dir,
96 cache: cache_dir,
97 user_config: user_config_dir,
98 system_config: system_config_dir,
99 },
100 progname: progname.to_string(),
101 };
102
103 mine.remove_old_logs();
105 mine
106 }
107
108 pub fn open_file(&self, name: String, dir: Directory) -> Result<fs::File, Illusion> {
110 let mut dir = {
111 match dir {
112 Directory::Data => self.dirs.data.clone(),
113 Directory::Cache => self.dirs.cache.clone(),
114 Directory::Runtime => self.dirs.runtime.clone(),
115 }
116 };
117
118 dir.set_file_name(name);
119 match fs::OpenOptions::new().read(true).write(true).create(true).open(dir.as_path()) {
120 Ok(file) => Ok(file),
121 Err(err) => Err(Illusion::IO(err.description().to_string())),
122 }
123 }
124
125 pub fn get_directories(&self) -> &Directories {
127 &self.dirs
128 }
129}
130
131impl Env {
135 fn initialize_logger(destination: LogDestination,
137 dir: &Path,
138 progname: &str)
139 -> Result<(), Illusion> {
140 match destination {
141 LogDestination::LogFile => Self::initialize_logger_for_log_file(dir, progname),
142 LogDestination::StdOut => Self::initialize_logger_for_stdout(),
143 LogDestination::Disabled => Self::disable_logger(),
144 }
145 }
146
147 fn initialize_logger_for_log_file(dir: &Path, progname: &str) -> Result<(), Illusion> {
149 let path = dir.join(format!("log-{}.log", Self::get_time_representation()));
150 match timber::init(&path) {
151 Ok(ok) => {
152 println!("Welcome to {}", progname);
153 println!("Log file in {:?}", path);
154 Ok(ok)
155 }
156 Err(err) => Err(Illusion::General(err.description().to_owned())),
157 }
158 }
159
160 fn disable_logger() -> Result<(), Illusion> {
162 if let Err(err) = timber::init(Path::new("/dev/null")) {
163 Err(Illusion::General(format!("Failed to disable logger: {}", err)))
164 } else {
165 Ok(())
166 }
167 }
168
169 fn initialize_logger_for_stdout() -> Result<(), Illusion> {
171 Ok(())
173 }
174}
175
176impl Env {
180 fn cleanup(&mut self) {
182 if let Err(err) = fs::remove_dir_all(&self.dirs.runtime) {
183 log_warn1!("Failed to remove runtime directory: {:?}", err);
184 }
185 }
186
187 fn remove_old_logs(&self) {
189 let transition_time = SystemTime::now() - Duration::new(2 * 24 * 60 * 60, 0);
190 if let Ok(entries) = fs::read_dir(&self.dirs.cache) {
191 for entry in entries {
192 if let Ok(entry) = entry {
193 let path = entry.path();
194 if let Some(extension) = path.extension() {
195 if extension == "log" {
196 let meta = path.metadata();
199 let good_to_remove = {
200 if let Ok(meta) = meta {
201 if let Ok(access_time) = meta.accessed() {
202 access_time < transition_time
203 } else {
204 true
205 }
206 } else {
207 true
208 }
209 };
210
211 if good_to_remove {
212 if let Err(err) = fs::remove_file(&path) {
213 log_warn1!("Failed to remove old log file {:?}: {}", path, err);
214 }
215 }
216 }
217 }
218 }
219 }
220 }
221 }
222}
223
224impl Env {
228 fn create_runtime_dir(progname: &str) -> Result<PathBuf, Illusion> {
230 let mut path = Self::read_path(RUNTIME_DIR_VAR, DEFAULT_RUNTIME_DIR);
231 path.push(format!("{}-{}", progname, Self::get_time_representation()));
232 Self::mkdir(&path).and(Ok(path))
233 }
234
235 fn create_data_dir(progname: &str) -> Result<PathBuf, Illusion> {
237 let mut default_path = std::env::home_dir().unwrap();
238 default_path.push(DEFAULT_DATA_DIR_FRAGMENT);
239 let mut path = Self::read_path(DATA_DIR_VAR, default_path.to_str().unwrap());
240 path.push(progname);
241 Self::mkdir(&path).and(Ok(path))
242 }
243
244 fn create_cache_dir(progname: &str) -> Result<PathBuf, Illusion> {
246 let mut default_path = std::env::home_dir().unwrap();
247 default_path.push(DEFAULT_CACHE_DIR_FRAGMENT);
248 let mut path = Self::read_path(CACHE_DIR_VAR, default_path.to_str().unwrap());
249 path.push(progname);
250 Self::mkdir(&path).and(Ok(path))
251 }
252
253 fn check_config_dirs(progname: &str) -> (Option<PathBuf>, Option<PathBuf>) {
260 let user_config_dir = {
262 let mut default_path = std::env::home_dir().unwrap();
263 default_path.push(DEFAULT_CONFIG_DIR_FRAGMENT);
264 let mut user = Self::read_path(CONFIG_DIR_VAR, default_path.to_str().unwrap());
265 user.push(progname);
266 if user.exists() && user.is_dir() {
267 Some(user)
268 } else {
269 None
270 }
271 };
272
273 let system_config_dir = {
275 let system = PathBuf::from(format!("{}{}", DEFAULT_SYSTEM_CONFIG_DIR_BASE, progname));
276 if system.exists() && system.is_dir() {
277 Some(system)
278 } else {
279 None
280 }
281 };
282
283 (system_config_dir, user_config_dir)
285 }
286
287 fn read_path(var: &str, default_path: &str) -> PathBuf {
289 let mut path = PathBuf::new();
290 path.push(std::env::var(var).unwrap_or(default_path.to_string()));
291 path
292 }
293
294 fn mkdir(path: &PathBuf) -> Result<(), Illusion> {
296 if path.exists() {
297 if path.is_dir() {
298 Ok(())
299 } else {
300 Err(Illusion::InvalidArgument(format!("Path '{:?}' is not directory!", path)))
301 }
302 } else if let Err(err) = fs::create_dir(path) {
303 Err(Illusion::General(format!("Could not create directory '{:?}': {}", path, err)))
304 } else {
305 Ok(())
306 }
307 }
308
309 fn get_time_representation() -> String {
317 let tm = time::now().to_local();
318 format!("{:03}-{:02}-{:02}-{:02}", tm.tm_yday, tm.tm_hour, tm.tm_min, tm.tm_sec)
319 }
320}
321
322impl Env {
326 fn register_signal_handler() {
329 let flags = signal::SaFlags::empty().bitand(signal::SA_SIGINFO);
330 let handler = signal::SigHandler::Handler(Self::signal_handler);
331 let sa = signal::SigAction::new(handler, flags, signal::SigSet::empty());
332
333 unsafe {
334 signal::sigaction(signal::SIGINT, &sa).unwrap();
335 signal::sigaction(signal::SIGTERM, &sa).unwrap();
336 signal::sigaction(signal::SIGSEGV, &sa).unwrap();
337 signal::sigaction(signal::SIGABRT, &sa).unwrap();
338 }
339 }
340
341 #[cfg_attr(rustfmt, rustfmt_skip)]
348 extern fn signal_handler(signum: libc::c_int) {
349 if (signum == signal::SIGSEGV as libc::c_int)
350 || (signum == signal::SIGABRT as libc::c_int) {
351 log_info1!("Signal {} received asynchronously", signum);
352 log::backtrace();
353 std::process::exit(1);
354 } else if (signum == signal::SIGINT as libc::c_int)
355 || (signum == signal::SIGTERM as libc::c_int) {
356 log_info1!("Signal {} received asynchronously", signum);
357 log::backtrace();
358 } else {
359 log_info2!("Signal {} received asynchronously: ignore", signum);
360 }
361 }
362}
363
364impl Drop for Env {
367 fn drop(&mut self) {
368 self.cleanup();
369 log_info1!("Thank you for running {}! Bye!", self.progname);
370 }
371}
372
373