]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/server/jobstate.rs
use new fsync parameter to replace_file and atomic_open_or_create
[proxmox-backup.git] / src / server / jobstate.rs
index e55a193e6b30a9511cd073484d8570a619a8563e..8df245d6ae59c511eb722d288b1fd362e10205fc 100644 (file)
@@ -14,7 +14,7 @@
 //! an example usage would be
 //! ```no_run
 //! # use anyhow::{bail, Error};
-//! # use proxmox_backup::server::TaskState;
+//! # use proxmox_rest_server::TaskState;
 //! # use proxmox_backup::server::jobstate::*;
 //! # fn some_code() -> TaskState { TaskState::OK { endtime: 0 } }
 //! # fn code() -> Result<(), Error> {
 //! # }
 //!
 //! ```
-use std::fs::File;
 use std::path::{Path, PathBuf};
-use std::time::Duration;
 
 use anyhow::{bail, format_err, Error};
+use serde::{Deserialize, Serialize};
+
 use proxmox::tools::fs::{
-    create_path, file_read_optional_string, open_file_locked, replace_file, CreateOptions,
+    create_path, file_read_optional_string, replace_file, CreateOptions,
 };
-use serde::{Deserialize, Serialize};
 
-use crate::server::{upid_read_status, worker_is_active_local, TaskState, UPID};
+use proxmox_systemd::time::{compute_next_event, parse_calendar_event};
+use pbs_config::{open_backup_lockfile, BackupLockGuard};
+use pbs_api_types::{UPID, JobScheduleStatus};
+
+use proxmox_rest_server::{upid_read_status, worker_is_active_local, TaskState};
 
-#[serde(rename_all = "kebab-case")]
 #[derive(Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
 /// Represents the State of a specific Job
 pub enum JobState {
     /// A job was created at 'time', but never started/finished
     Created { time: i64 },
     /// The Job was last started in 'upid',
     Started { upid: String },
-    /// The Job was last started in 'upid', which finished with 'state'
-    Finished { upid: String, state: TaskState },
+    /// The Job was last started in 'upid', which finished with 'state', and was last updated at 'updated'
+    Finished {
+        upid: String,
+        state: TaskState,
+        updated: Option<i64>,
+    },
 }
 
 /// Represents a Job and holds the correct lock
@@ -67,14 +74,14 @@ pub struct Job {
     jobname: String,
     /// The State of the job
     pub state: JobState,
-    _lock: File,
+    _lock: BackupLockGuard,
 }
 
 const JOB_STATE_BASEDIR: &str = "/var/lib/proxmox-backup/jobstates";
 
 /// Create jobstate stat dir with correct permission
 pub fn create_jobstate_dir() -> Result<(), Error> {
-    let backup_user = crate::backup::backup_user()?;
+    let backup_user = pbs_config::backup_user()?;
     let opts = CreateOptions::new()
         .owner(backup_user.uid)
         .group(backup_user.gid);
@@ -91,16 +98,13 @@ fn get_path(jobtype: &str, jobname: &str) -> PathBuf {
     path
 }
 
-fn get_lock<P>(path: P) -> Result<File, Error>
+fn get_lock<P>(path: P) -> Result<BackupLockGuard, Error>
 where
     P: AsRef<Path>,
 {
     let mut path = path.as_ref().to_path_buf();
     path.set_extension("lck");
-    let lock = open_file_locked(&path, Duration::new(10, 0), true)?;
-    let backup_user = crate::backup::backup_user()?;
-    nix::unistd::chown(&path, Some(backup_user.uid), Some(backup_user.gid))?;
-    Ok(lock)
+    open_backup_lockfile(&path, None, true)
 }
 
 /// Removes the statefile of a job, this is useful if we delete a job
@@ -135,12 +139,47 @@ pub fn create_state_file(jobtype: &str, jobname: &str) -> Result<(), Error> {
     job.write_state()
 }
 
+/// Tries to update the state file with the current time
+/// if the job is currently running, does nothing.
+/// Intended for use when the schedule changes.
+pub fn update_job_last_run_time(jobtype: &str, jobname: &str) -> Result<(), Error> {
+    let mut job = match Job::new(jobtype, jobname) {
+        Ok(job) => job,
+        Err(_) => return Ok(()), // was locked (running), so do not update
+    };
+    let time = proxmox_time::epoch_i64();
+
+    job.state = match JobState::load(jobtype, jobname)? {
+        JobState::Created { .. } => JobState::Created { time },
+        JobState::Started { .. } => return Ok(()), // currently running (without lock?)
+        JobState::Finished {
+            upid,
+            state,
+            updated: _,
+        } => JobState::Finished {
+            upid,
+            state,
+            updated: Some(time),
+        },
+    };
+    job.write_state()
+}
+
 /// Returns the last run time of a job by reading the statefile
 /// Note that this is not locked
 pub fn last_run_time(jobtype: &str, jobname: &str) -> Result<i64, Error> {
     match JobState::load(jobtype, jobname)? {
         JobState::Created { time } => Ok(time),
-        JobState::Started { upid } | JobState::Finished { upid, .. } => {
+        JobState::Finished {
+            updated: Some(time),
+            ..
+        } => Ok(time),
+        JobState::Started { upid }
+        | JobState::Finished {
+            upid,
+            state: _,
+            updated: None,
+        } => {
             let upid: UPID = upid
                 .parse()
                 .map_err(|err| format_err!("could not parse upid from state: {}", err))?;
@@ -168,7 +207,11 @@ impl JobState {
                         let state = upid_read_status(&parsed)
                             .map_err(|err| format_err!("error reading upid log status: {}", err))?;
 
-                        Ok(JobState::Finished { upid, state })
+                        Ok(JobState::Finished {
+                            upid,
+                            state,
+                            updated: None,
+                        })
                     } else {
                         Ok(JobState::Started { upid })
                     }
@@ -177,7 +220,7 @@ impl JobState {
             }
         } else {
             Ok(JobState::Created {
-                time: proxmox::tools::time::epoch_i64() - 30,
+                time: proxmox_time::epoch_i64() - 30,
             })
         }
     }
@@ -198,7 +241,7 @@ impl Job {
             jobtype: jobtype.to_string(),
             jobname: jobname.to_string(),
             state: JobState::Created {
-                time: proxmox::tools::time::epoch_i64(),
+                time: proxmox_time::epoch_i64(),
             },
             _lock,
         })
@@ -207,11 +250,8 @@ impl Job {
     /// Start the job and update the statefile accordingly
     /// Fails if the job was already started
     pub fn start(&mut self, upid: &str) -> Result<(), Error> {
-        match self.state {
-            JobState::Started { .. } => {
-                bail!("cannot start job that is started!");
-            }
-            _ => {}
+        if let JobState::Started { .. } = self.state {
+            bail!("cannot start job that is started!");
         }
 
         self.state = JobState::Started {
@@ -231,7 +271,11 @@ impl Job {
         }
         .to_string();
 
-        self.state = JobState::Finished { upid, state };
+        self.state = JobState::Finished {
+            upid,
+            state,
+            updated: None,
+        };
 
         self.write_state()
     }
@@ -248,7 +292,7 @@ impl Job {
         let serialized = serde_json::to_string(&self.state)?;
         let path = get_path(&self.jobtype, &self.jobname);
 
-        let backup_user = crate::backup::backup_user()?;
+        let backup_user = pbs_config::backup_user()?;
         let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
         // set the correct owner/group/permissions while saving file
         // owner(rw) = backup, group(r)= backup
@@ -257,6 +301,46 @@ impl Job {
             .owner(backup_user.uid)
             .group(backup_user.gid);
 
-        replace_file(path, serialized.as_bytes(), options)
+        replace_file(path, serialized.as_bytes(), options, false)
+    }
+}
+
+pub fn compute_schedule_status(
+    job_state: &JobState,
+    schedule: Option<&str>,
+) -> Result<JobScheduleStatus, Error> {
+    let (upid, endtime, state, last) = match job_state {
+        JobState::Created { time } => (None, None, None, *time),
+        JobState::Started { upid } => {
+            let parsed_upid: UPID = upid.parse()?;
+            (Some(upid), None, None, parsed_upid.starttime)
+        }
+        JobState::Finished {
+            upid,
+            state,
+            updated,
+        } => {
+            let last = updated.unwrap_or_else(|| state.endtime());
+            (
+                Some(upid),
+                Some(state.endtime()),
+                Some(state.to_string()),
+                last,
+            )
+        }
+    };
+
+    let mut status = JobScheduleStatus::default();
+    status.last_run_upid = upid.map(String::from);
+    status.last_run_state = state;
+    status.last_run_endtime = endtime;
+
+    if let Some(schedule) = schedule {
+        if let Ok(event) = parse_calendar_event(&schedule) {
+            // ignore errors
+            status.next_run = compute_next_event(&event, last, false).unwrap_or(None);
+        }
     }
+
+    Ok(status)
 }