1 use std
::path
::{Path, PathBuf}
;
2 use std
::fs
::{File, rename}
;
3 use std
::os
::unix
::io
::FromRawFd
;
6 use anyhow
::{bail, Error}
;
9 use proxmox
::tools
::fs
::{CreateOptions, make_tmp_file, replace_file}
;
11 /// Used for rotating log files and iterating over them
12 pub struct 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() {
25 base_path
: path
.as_ref().to_path_buf(),
33 /// Returns an iterator over the logrotated file names that exist
34 pub fn file_names(&self) -> LogRotateFileNames
{
36 base_path
: self.base_path
.clone(),
38 compress
: self.compress
42 /// Returns an iterator over the logrotated file handles
43 pub fn files(&self) -> LogRotateFiles
{
45 file_names
: self.file_names(),
49 /// Rotates the files up to 'max_files'
50 /// if the 'compress' option was given it will compress the newest file
53 /// foo.2.zst => foo.3.zst
54 /// foo.1.zst => foo.2.zst
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
63 let mut next_filename
= self.base_path
.clone().canonicalize()?
.into_os_string();
66 next_filename
.push(format
!(".{}.zst", filenames
.len()));
68 next_filename
.push(format
!(".{}", filenames
.len()));
71 filenames
.push(PathBuf
::from(next_filename
));
72 let count
= filenames
.len();
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])?
;
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
,
86 let _
= unistd
::unlink(&tmp_path
);
87 bail
!("creating zstd encoder failed - {}", err
);
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
);
96 if let Err(err
) = encoder
.finish() {
97 let _
= unistd
::unlink(&tmp_path
);
98 bail
!("zstd finish failed for file {:?} - {}", &filenames
[1], err
);
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
);
106 unistd
::unlink(&filenames
[0])?
;
108 rename(&filenames
[0], &filenames
[1])?
;
111 // create empty original file
112 replace_file(&filenames
[0], b
"", options
)?
;
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
);
127 /// Iterator over logrotated file names
128 pub struct LogRotateFileNames
{
134 impl Iterator
for LogRotateFileNames
{
137 fn next(&mut self) -> Option
<Self::Item
> {
139 let mut path
: std
::ffi
::OsString
= self.base_path
.clone().into();
141 path
.push(format
!(".{}", self.count
));
144 if Path
::new(&path
).is_file() {
146 } else if self.compress
{
148 if Path
::new(&path
).is_file() {
156 } else if self.base_path
.is_file() {
158 Some(self.base_path
.to_path_buf())
165 /// Iterator over logrotated files by returning a boxed reader
166 pub struct LogRotateFiles
{
167 file_names
: LogRotateFileNames
,
170 impl Iterator
for LogRotateFiles
{
171 type Item
= Box
<dyn Read
+ Send
>;
173 fn next(&mut self) -> Option
<Self::Item
> {
174 let filename
= self.file_names
.next()?
;
175 let file
= File
::open(&filename
).ok()?
;
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
));