]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/tasks.rs
replace Userid with Authid
[proxmox-backup.git] / src / api2 / node / tasks.rs
CommitLineData
cad540e9
WB
1use std::fs::File;
2use std::io::{BufRead, BufReader};
063ca5be 3
f7d4e4b5 4use anyhow::{Error};
063ca5be
DM
5use serde_json::{json, Value};
6
e7cb4dc5 7use proxmox::api::{api, Router, RpcEnvironment, Permission};
cad540e9 8use proxmox::api::router::SubdirMap;
9ea4bce4 9use proxmox::{identity, list_subdirs_api_method, sortable};
552c2259
DM
10
11use crate::tools;
4ebf0eab 12use crate::api2::types::*;
768e10d0 13use crate::server::{self, UPID, TaskState, TaskListInfoIterator};
83b6a7cf 14use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
720af9f6
DM
15use crate::config::cached_user_info::CachedUserInfo;
16
5a12c0e2 17
83b6a7cf
DM
18#[api(
19 input: {
20 properties: {
21 node: {
22 schema: NODE_SCHEMA,
23 },
24 upid: {
25 schema: UPID_SCHEMA,
26 },
27 },
28 },
29 returns: {
9809772b 30 description: "Task status information.",
83b6a7cf
DM
31 properties: {
32 node: {
33 schema: NODE_SCHEMA,
34 },
35 upid: {
36 schema: UPID_SCHEMA,
37 },
38 pid: {
39 type: i64,
40 description: "The Unix PID.",
41 },
42 pstart: {
43 type: u64,
44 description: "The Unix process start time from `/proc/pid/stat`",
45 },
46 starttime: {
47 type: i64,
48 description: "The task start time (Epoch)",
49 },
50 "type": {
51 type: String,
52 description: "Worker type (arbitrary ASCII string)",
53 },
54 id: {
55 type: String,
56 optional: true,
57 description: "Worker ID (arbitrary ASCII string)",
58 },
59 user: {
60 type: String,
61 description: "The user who started the task.",
62 },
63 status: {
64 type: String,
65 description: "'running' or 'stopped'",
66 },
67 exitstatus: {
68 type: String,
69 optional: true,
70 description: "'OK', 'Error: <msg>', or 'unkwown'.",
71 },
72 },
73 },
74 access: {
9809772b 75 description: "Users can access their own tasks, or need Sys.Audit on /system/tasks.",
720af9f6 76 permission: &Permission::Anybody,
83b6a7cf
DM
77 },
78)]
79/// Get task status.
5751e495 80async fn get_task_status(
5a12c0e2 81 param: Value,
720af9f6 82 rpcenv: &mut dyn RpcEnvironment,
5a12c0e2
DM
83) -> Result<Value, Error> {
84
85 let upid = extract_upid(&param)?;
86
e6dc35ac 87 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
720af9f6 88
e6dc35ac 89 if auth_id != upid.auth_id {
720af9f6 90 let user_info = CachedUserInfo::new()?;
e6dc35ac 91 user_info.check_privs(&auth_id, &["system", "tasks"], PRIV_SYS_AUDIT, false)?;
720af9f6
DM
92 }
93
c360bd73
DM
94 let mut result = json!({
95 "upid": param["upid"],
96 "node": upid.node,
97 "pid": upid.pid,
98 "pstart": upid.pstart,
99 "starttime": upid.starttime,
100 "type": upid.worker_type,
101 "id": upid.worker_id,
e6dc35ac 102 "user": upid.auth_id,
c360bd73
DM
103 });
104
5751e495 105 if crate::server::worker_is_active(&upid).await? {
c360bd73 106 result["status"] = Value::from("running");
5a12c0e2 107 } else {
77bd2a46 108 let exitstatus = crate::server::upid_read_status(&upid).unwrap_or(TaskState::Unknown { endtime: 0 });
c360bd73 109 result["status"] = Value::from("stopped");
4c116baf 110 result["exitstatus"] = Value::from(exitstatus.to_string());
5a12c0e2
DM
111 };
112
113 Ok(result)
114}
115
116fn extract_upid(param: &Value) -> Result<UPID, Error> {
117
118 let upid_str = tools::required_string_param(&param, "upid")?;
119
8560fe3e 120 upid_str.parse::<UPID>()
5a12c0e2
DM
121}
122
83b6a7cf
DM
123#[api(
124 input: {
125 properties: {
126 node: {
127 schema: NODE_SCHEMA,
128 },
129 upid: {
130 schema: UPID_SCHEMA,
131 },
132 "test-status": {
133 type: bool,
134 optional: true,
135 description: "Test task status, and set result attribute \"active\" accordingly.",
136 },
137 start: {
138 type: u64,
139 optional: true,
140 description: "Start at this line.",
141 default: 0,
142 },
143 limit: {
144 type: u64,
145 optional: true,
146 description: "Only list this amount of lines.",
147 default: 50,
148 },
149 },
150 },
151 access: {
720af9f6
DM
152 description: "Users can access there own tasks, or need Sys.Audit on /system/tasks.",
153 permission: &Permission::Anybody,
83b6a7cf
DM
154 },
155)]
156/// Read task log.
5751e495 157async fn read_task_log(
5a12c0e2 158 param: Value,
e8d1da6a 159 mut rpcenv: &mut dyn RpcEnvironment,
5a12c0e2
DM
160) -> Result<Value, Error> {
161
162 let upid = extract_upid(&param)?;
6b508dd5 163
e6dc35ac 164 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
720af9f6 165
e6dc35ac 166 if auth_id != upid.auth_id {
720af9f6 167 let user_info = CachedUserInfo::new()?;
e6dc35ac 168 user_info.check_privs(&auth_id, &["system", "tasks"], PRIV_SYS_AUDIT, false)?;
720af9f6
DM
169 }
170
6b508dd5
DM
171 let test_status = param["test-status"].as_bool().unwrap_or(false);
172
5a12c0e2
DM
173 let start = param["start"].as_u64().unwrap_or(0);
174 let mut limit = param["limit"].as_u64().unwrap_or(50);
6b508dd5 175
5a12c0e2
DM
176 let mut count: u64 = 0;
177
178 let path = upid.log_path();
179
180 let file = File::open(path)?;
181
182 let mut lines: Vec<Value> = vec![];
183
184 for line in BufReader::new(file).lines() {
185 match line {
186 Ok(line) => {
187 count += 1;
188 if count < start { continue };
11377a47 189 if limit == 0 { continue };
5a12c0e2
DM
190
191 lines.push(json!({ "n": count, "t": line }));
192
193 limit -= 1;
194 }
195 Err(err) => {
196 log::error!("reading task log failed: {}", err);
197 break;
198 }
199 }
200 }
201
e8d1da6a 202 rpcenv["total"] = Value::from(count);
d8d40dd0 203
6b508dd5 204 if test_status {
5751e495 205 let active = crate::server::worker_is_active(&upid).await?;
e8d1da6a 206 rpcenv["active"] = Value::from(active);
6b508dd5
DM
207 }
208
5a12c0e2
DM
209 Ok(json!(lines))
210}
063ca5be 211
83b6a7cf
DM
212#[api(
213 protected: true,
214 input: {
215 properties: {
216 node: {
217 schema: NODE_SCHEMA,
218 },
219 upid: {
220 schema: UPID_SCHEMA,
221 },
222 },
223 },
224 access: {
720af9f6
DM
225 description: "Users can stop there own tasks, or need Sys.Modify on /system/tasks.",
226 permission: &Permission::Anybody,
83b6a7cf
DM
227 },
228)]
229/// Try to stop a task.
a665dea1
DM
230fn stop_task(
231 param: Value,
720af9f6 232 rpcenv: &mut dyn RpcEnvironment,
a665dea1
DM
233) -> Result<Value, Error> {
234
235 let upid = extract_upid(&param)?;
236
e6dc35ac 237 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
720af9f6 238
e6dc35ac 239 if auth_id != upid.auth_id {
720af9f6 240 let user_info = CachedUserInfo::new()?;
e6dc35ac 241 user_info.check_privs(&auth_id, &["system", "tasks"], PRIV_SYS_MODIFY, false)?;
720af9f6
DM
242 }
243
5751e495 244 server::abort_worker_async(upid);
a665dea1
DM
245
246 Ok(Value::Null)
247}
248
2c4b303c
DM
249#[api(
250 input: {
251 properties: {
252 node: {
253 schema: NODE_SCHEMA
254 },
255 start: {
256 type: u64,
257 description: "List tasks beginning from this offset.",
258 default: 0,
259 optional: true,
260 },
261 limit: {
262 type: u64,
263 description: "Only list this amount of tasks.",
264 default: 50,
265 optional: true,
266 },
267 store: {
268 schema: DATASTORE_SCHEMA,
269 optional: true,
270 },
271 running: {
272 type: bool,
273 description: "Only list running tasks.",
274 optional: true,
ca9dfe5f 275 default: false,
2c4b303c
DM
276 },
277 errors: {
278 type: bool,
279 description: "Only list erroneous tasks.",
280 optional:true,
ca9dfe5f 281 default: false,
2c4b303c
DM
282 },
283 userfilter: {
e7cb4dc5 284 optional: true,
2c4b303c
DM
285 type: String,
286 description: "Only list tasks from this user.",
287 },
288 },
289 },
290 returns: {
291 description: "A list of tasks.",
292 type: Array,
99384f79 293 items: { type: TaskListItem },
2c4b303c 294 },
83b6a7cf 295 access: {
720af9f6
DM
296 description: "Users can only see there own tasks, unless the have Sys.Audit on /system/tasks.",
297 permission: &Permission::Anybody,
83b6a7cf 298 },
2c4b303c
DM
299)]
300/// List tasks.
8528fce8 301pub fn list_tasks(
ca9dfe5f
DM
302 start: u64,
303 limit: u64,
304 errors: bool,
305 running: bool,
005a5b96 306 userfilter: Option<String>,
063ca5be 307 param: Value,
e8d1da6a 308 mut rpcenv: &mut dyn RpcEnvironment,
99384f79 309) -> Result<Vec<TaskListItem>, Error> {
063ca5be 310
e6dc35ac 311 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
720af9f6 312 let user_info = CachedUserInfo::new()?;
e6dc35ac 313 let user_privs = user_info.lookup_privs(&auth_id, &["system", "tasks"]);
720af9f6
DM
314
315 let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
316
567d3e00
DM
317 let store = param["store"].as_str();
318
768e10d0 319 let list = TaskListInfoIterator::new(running)?;
d2a2e02b 320
768e10d0
DC
321 let result: Vec<TaskListItem> = list
322 .take_while(|info| !info.is_err())
323 .filter_map(|info| {
324 let info = match info {
325 Ok(info) => info,
326 Err(_) => return None,
327 };
720af9f6 328
e6dc35ac 329 if !list_all && info.upid.auth_id != auth_id { return None; }
063ca5be 330
e6dc35ac
FG
331 if let Some(needle) = &userfilter {
332 if !info.upid.auth_id.to_string().contains(needle) { return None; }
d2a2e02b
DM
333 }
334
567d3e00
DM
335 if let Some(store) = store {
336 // Note: useful to select all tasks spawned by proxmox-backup-client
337 let worker_id = match &info.upid.worker_id {
338 Some(w) => w,
768e10d0 339 None => return None, // skip
567d3e00
DM
340 };
341
503995c7
DM
342 if info.upid.worker_type == "backup" || info.upid.worker_type == "restore" ||
343 info.upid.worker_type == "prune"
344 {
4ebda996 345 let prefix = format!("{}:", store);
768e10d0 346 if !worker_id.starts_with(&prefix) { return None; }
503995c7 347 } else if info.upid.worker_type == "garbage_collection" {
768e10d0 348 if worker_id != store { return None; }
567d3e00 349 } else {
768e10d0 350 return None; // skip
567d3e00
DM
351 }
352 }
353
768e10d0 354 match info.state {
df4827f2 355 Some(_) if running => return None,
768e10d0
DC
356 Some(crate::server::TaskState::OK { .. }) if errors => return None,
357 _ => {},
063ca5be
DM
358 }
359
768e10d0
DC
360 Some(info.into())
361 }).skip(start as usize)
362 .take(limit as usize)
363 .collect();
063ca5be 364
768e10d0
DC
365 let mut count = result.len() + start as usize;
366 if result.len() > 0 && result.len() >= limit as usize { // we have a 'virtual' entry as long as we have any new
367 count += 1;
063ca5be
DM
368 }
369
e8d1da6a 370 rpcenv["total"] = Value::from(count);
063ca5be 371
0196b9bf 372 Ok(result)
063ca5be
DM
373}
374
552c2259 375#[sortable]
83b6a7cf 376const UPID_API_SUBDIRS: SubdirMap = &sorted!([
255f378a
DM
377 (
378 "log", &Router::new()
83b6a7cf 379 .get(&API_METHOD_READ_TASK_LOG)
255f378a
DM
380 ),
381 (
382 "status", &Router::new()
83b6a7cf 383 .get(&API_METHOD_GET_TASK_STATUS)
255f378a 384 )
83b6a7cf 385]);
255f378a
DM
386
387pub const UPID_API_ROUTER: Router = Router::new()
388 .get(&list_subdirs_api_method!(UPID_API_SUBDIRS))
83b6a7cf 389 .delete(&API_METHOD_STOP_TASK)
255f378a
DM
390 .subdirs(&UPID_API_SUBDIRS);
391
392pub const ROUTER: Router = Router::new()
2c4b303c 393 .get(&API_METHOD_LIST_TASKS)
255f378a 394 .match_all("upid", &UPID_API_ROUTER);