]>
Commit | Line | Data |
---|---|---|
8074d2b0 DC |
1 | use std::path::{Path, PathBuf}; |
2 | use std::fs::{File, rename}; | |
3 | use std::os::unix::io::FromRawFd; | |
4 | use std::io::Read; | |
5 | ||
6 | use anyhow::{bail, Error}; | |
7 | use nix::unistd; | |
8 | ||
9 | use proxmox::tools::fs::{CreateOptions, make_tmp_file, replace_file}; | |
10 | ||
11 | /// Used for rotating log files and iterating over them | |
12 | pub struct LogRotate { | |
13 | base_path: PathBuf, | |
14 | compress: bool, | |
15 | } | |
16 | ||
17 | impl LogRotate { | |
18 | /// Creates a new instance if the path given is a valid file name | |
19 | /// (iow. does not end with ..) | |
20 | /// 'compress' decides if compresses files will be created on | |
21 | /// rotation, and if it will search '.zst' files when iterating | |
22 | pub fn new<P: AsRef<Path>>(path: P, compress: bool) -> Option<Self> { | |
23 | if path.as_ref().file_name().is_some() { | |
24 | Some(Self { | |
25 | base_path: path.as_ref().to_path_buf(), | |
26 | compress, | |
27 | }) | |
28 | } else { | |
29 | None | |
30 | } | |
31 | } | |
32 | ||
33 | /// Returns an iterator over the logrotated file names that exist | |
34 | pub fn file_names(&self) -> LogRotateFileNames { | |
35 | LogRotateFileNames { | |
36 | base_path: self.base_path.clone(), | |
37 | count: 0, | |
38 | compress: self.compress | |
39 | } | |
40 | } | |
41 | ||
42 | /// Returns an iterator over the logrotated file handles | |
43 | pub fn files(&self) -> LogRotateFiles { | |
44 | LogRotateFiles { | |
45 | file_names: self.file_names(), | |
46 | } | |
47 | } | |
48 | ||
49 | /// Rotates the files up to 'max_files' | |
50 | /// if the 'compress' option was given it will compress the newest file | |
51 | /// | |
52 | /// e.g. rotates | |
53 | /// foo.2.zst => foo.3.zst | |
54 | /// foo.1.zst => foo.2.zst | |
55 | /// foo => foo.1.zst | |
56 | /// => foo | |
57 | pub fn rotate(&mut self, options: CreateOptions, max_files: Option<usize>) -> Result<(), Error> { | |
58 | let mut filenames: Vec<PathBuf> = self.file_names().collect(); | |
59 | if filenames.is_empty() { | |
60 | return Ok(()); // no file means nothing to rotate | |
61 | } | |
62 | ||
63 | let mut next_filename = self.base_path.clone().canonicalize()?.into_os_string(); | |
64 | ||
65 | if self.compress { | |
66 | next_filename.push(format!(".{}.zst", filenames.len())); | |
67 | } else { | |
68 | next_filename.push(format!(".{}", filenames.len())); | |
69 | } | |
70 | ||
71 | filenames.push(PathBuf::from(next_filename)); | |
72 | let count = filenames.len(); | |
73 | ||
74 | // rotate all but the first, that we maybe have to compress | |
75 | for i in (1..count-1).rev() { | |
76 | rename(&filenames[i], &filenames[i+1])?; | |
77 | } | |
78 | ||
79 | if self.compress { | |
80 | let mut source = File::open(&filenames[0])?; | |
81 | let (fd, tmp_path) = make_tmp_file(&filenames[1], options.clone())?; | |
82 | let target = unsafe { File::from_raw_fd(fd) }; | |
83 | let mut encoder = match zstd::stream::write::Encoder::new(target, 0) { | |
84 | Ok(encoder) => encoder, | |
85 | Err(err) => { | |
86 | let _ = unistd::unlink(&tmp_path); | |
87 | bail!("creating zstd encoder failed - {}", err); | |
88 | } | |
89 | }; | |
90 | ||
91 | if let Err(err) = std::io::copy(&mut source, &mut encoder) { | |
92 | let _ = unistd::unlink(&tmp_path); | |
93 | bail!("zstd encoding failed for file {:?} - {}", &filenames[1], err); | |
94 | } | |
95 | ||
96 | if let Err(err) = encoder.finish() { | |
97 | let _ = unistd::unlink(&tmp_path); | |
98 | bail!("zstd finish failed for file {:?} - {}", &filenames[1], err); | |
99 | } | |
100 | ||
101 | if let Err(err) = rename(&tmp_path, &filenames[1]) { | |
102 | let _ = unistd::unlink(&tmp_path); | |
103 | bail!("rename failed for file {:?} - {}", &filenames[1], err); | |
104 | } | |
105 | ||
106 | unistd::unlink(&filenames[0])?; | |
107 | } else { | |
108 | rename(&filenames[0], &filenames[1])?; | |
109 | } | |
110 | ||
111 | // create empty original file | |
112 | replace_file(&filenames[0], b"", options)?; | |
113 | ||
114 | if let Some(max_files) = max_files { | |
115 | // delete all files > max_files | |
116 | for file in filenames.iter().skip(max_files) { | |
117 | if let Err(err) = unistd::unlink(file) { | |
118 | eprintln!("could not remove {:?}: {}", &file, err); | |
119 | } | |
120 | } | |
121 | } | |
122 | ||
123 | Ok(()) | |
124 | } | |
125 | } | |
126 | ||
127 | /// Iterator over logrotated file names | |
128 | pub struct LogRotateFileNames { | |
129 | base_path: PathBuf, | |
130 | count: usize, | |
131 | compress: bool, | |
132 | } | |
133 | ||
134 | impl Iterator for LogRotateFileNames { | |
135 | type Item = PathBuf; | |
136 | ||
137 | fn next(&mut self) -> Option<Self::Item> { | |
138 | if self.count > 0 { | |
139 | let mut path: std::ffi::OsString = self.base_path.clone().into(); | |
140 | ||
141 | path.push(format!(".{}", self.count)); | |
142 | self.count += 1; | |
143 | ||
144 | if Path::new(&path).is_file() { | |
145 | Some(path.into()) | |
146 | } else if self.compress { | |
147 | path.push(".zst"); | |
148 | if Path::new(&path).is_file() { | |
149 | Some(path.into()) | |
150 | } else { | |
151 | None | |
152 | } | |
153 | } else { | |
154 | None | |
155 | } | |
156 | } else if self.base_path.is_file() { | |
157 | self.count += 1; | |
158 | Some(self.base_path.to_path_buf()) | |
159 | } else { | |
160 | None | |
161 | } | |
162 | } | |
163 | } | |
164 | ||
165 | /// Iterator over logrotated files by returning a boxed reader | |
166 | pub struct LogRotateFiles { | |
167 | file_names: LogRotateFileNames, | |
168 | } | |
169 | ||
170 | impl Iterator for LogRotateFiles { | |
171 | type Item = Box<dyn Read + Send>; | |
172 | ||
173 | fn next(&mut self) -> Option<Self::Item> { | |
174 | let filename = self.file_names.next()?; | |
175 | let file = File::open(&filename).ok()?; | |
176 | ||
177 | if filename.extension().unwrap_or(std::ffi::OsStr::new("")) == "zst" { | |
178 | let encoder = zstd::stream::read::Decoder::new(file).ok()?; | |
179 | return Some(Box::new(encoder)); | |
180 | } | |
181 | ||
182 | Some(Box::new(file)) | |
183 | } | |
184 | } |