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}
;
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 fn compress(file
: &PathBuf
, options
: &CreateOptions
) -> Result
<(), Error
> {
50 let mut source
= File
::open(file
)?
;
51 let (fd
, tmp_path
) = make_tmp_file(file
, options
.clone())?
;
52 let target
= unsafe { File::from_raw_fd(fd) }
;
53 let mut encoder
= match zstd
::stream
::write
::Encoder
::new(target
, 0) {
54 Ok(encoder
) => encoder
,
56 let _
= unistd
::unlink(&tmp_path
);
57 bail
!("creating zstd encoder failed - {}", err
);
61 if let Err(err
) = std
::io
::copy(&mut source
, &mut encoder
) {
62 let _
= unistd
::unlink(&tmp_path
);
63 bail
!("zstd encoding failed for file {:?} - {}", file
, err
);
66 if let Err(err
) = encoder
.finish() {
67 let _
= unistd
::unlink(&tmp_path
);
68 bail
!("zstd finish failed for file {:?} - {}", file
, err
);
71 if let Err(err
) = rename(&tmp_path
, file
) {
72 let _
= unistd
::unlink(&tmp_path
);
73 bail
!("rename failed for file {:?} - {}", file
, err
);
78 /// Rotates the files up to 'max_files'
79 /// if the 'compress' option was given it will compress the newest file
82 /// foo.2.zst => foo.3.zst
83 /// foo.1.zst => foo.2.zst
86 pub fn do_rotate(&mut self, options
: CreateOptions
, max_files
: Option
<usize>) -> Result
<(), Error
> {
87 let mut filenames
: Vec
<PathBuf
> = self.file_names().collect();
88 if filenames
.is_empty() {
89 return Ok(()); // no file means nothing to rotate
92 let mut next_filename
= self.base_path
.clone().canonicalize()?
.into_os_string();
95 next_filename
.push(format
!(".{}.zst", filenames
.len()));
97 next_filename
.push(format
!(".{}", filenames
.len()));
100 filenames
.push(PathBuf
::from(next_filename
));
101 let count
= filenames
.len();
103 for i
in (0..count
-1).rev() {
104 rename(&filenames
[i
], &filenames
[i
+1])?
;
108 for i
in 2..count
-1 {
109 if filenames
[i
].extension().unwrap_or(std
::ffi
::OsStr
::new("")) != "zst" {
110 Self::compress(&filenames
[i
], &options
)?
;
115 if let Some(max_files
) = max_files
{
116 // delete all files > max_files
117 for file
in filenames
.iter().skip(max_files
) {
118 if let Err(err
) = unistd
::unlink(file
) {
119 eprintln
!("could not remove {:?}: {}", &file
, err
);
130 options
: Option
<CreateOptions
>,
131 max_files
: Option
<usize>
132 ) -> Result
<bool
, Error
> {
134 let options
= match options
{
135 Some(options
) => options
,
137 let backup_user
= crate::backup
::backup_user()?
;
138 CreateOptions
::new().owner(backup_user
.uid
).group(backup_user
.gid
)
142 let metadata
= match self.base_path
.metadata() {
143 Ok(metadata
) => metadata
,
144 Err(err
) if err
.kind() == std
::io
::ErrorKind
::NotFound
=> return Ok(false),
145 Err(err
) => bail
!("unable to open task archive - {}", err
),
148 if metadata
.len() > max_size
{
149 self.do_rotate(options
, max_files
)?
;
157 /// Iterator over logrotated file names
158 pub struct LogRotateFileNames
{
164 impl Iterator
for LogRotateFileNames
{
167 fn next(&mut self) -> Option
<Self::Item
> {
169 let mut path
: std
::ffi
::OsString
= self.base_path
.clone().into();
171 path
.push(format
!(".{}", self.count
));
174 if Path
::new(&path
).is_file() {
176 } else if self.compress
{
178 if Path
::new(&path
).is_file() {
186 } else if self.base_path
.is_file() {
188 Some(self.base_path
.to_path_buf())
195 /// Iterator over logrotated files by returning a boxed reader
196 pub struct LogRotateFiles
{
197 file_names
: LogRotateFileNames
,
200 impl Iterator
for LogRotateFiles
{
201 type Item
= Box
<dyn Read
+ Send
>;
203 fn next(&mut self) -> Option
<Self::Item
> {
204 let filename
= self.file_names
.next()?
;
205 let file
= File
::open(&filename
).ok()?
;
207 if filename
.extension().unwrap_or(std
::ffi
::OsStr
::new("")) == "zst" {
208 let encoder
= zstd
::stream
::read
::Decoder
::new(file
).ok()?
;
209 return Some(Box
::new(encoder
));