1 use anyhow
::{bail, Error}
;
3 use crate::{const_regex, ApiStringFormat, ApiType, Schema, StringSchema}
;
5 /// Unique Process/Task Identifier
7 /// We use this to uniquely identify worker task. UPIDs have a short
8 /// string repesentaion, which gives additional information about the
9 /// type of the task. for example:
11 /// UPID:{node}:{pid}:{pstart}:{task_id}:{starttime}:{worker_type}:{worker_id}:{userid}:
12 /// UPID:elsa:00004F37:0039E469:00000000:5CA78B83:garbage_collection::root@pam:
14 /// Please note that we use tokio, so a single thread can run multiple
16 // #[api] - manually implemented API type
17 #[derive(Debug, Clone)]
20 pub pid
: i32, // really libc::pid_t, but we don't want this as a dependency for proxmox-schema
21 /// The Unix process start time from `/proc/pid/stat`
23 /// The task start time (Epoch)
25 /// The task ID (inside the process/thread)
27 /// Worker type (arbitrary ASCII string)
28 pub worker_type
: String
,
29 /// Worker ID (arbitrary ASCII string)
30 pub worker_id
: Option
<String
>,
31 /// The authenticated entity who started the task
38 pub PROXMOX_UPID_REGEX
= concat
!(
39 r
"^UPID:(?P<node>[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):(?P<pid>[0-9A-Fa-f]{8}):",
40 r
"(?P<pstart>[0-9A-Fa-f]{8,9}):(?P<task_id>[0-9A-Fa-f]{8,16}):(?P<starttime>[0-9A-Fa-f]{8}):",
41 r
"(?P<wtype>[^:\s]+):(?P<wid>[^:\s]*):(?P<authid>[^:\s]+):$"
45 pub const PROXMOX_UPID_FORMAT
: ApiStringFormat
= ApiStringFormat
::Pattern(&PROXMOX_UPID_REGEX
);
47 pub const UPID_SCHEMA
: Schema
= StringSchema
::new("Unique Process/Task Identifier")
48 .min_length("UPID:N:12345678:12345678:12345678:::".len())
49 .format(&PROXMOX_UPID_FORMAT
)
52 impl ApiType
for UPID
{
53 const API_SCHEMA
: Schema
= UPID_SCHEMA
;
56 impl std
::str::FromStr
for UPID
{
59 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
60 if let Some(cap
) = PROXMOX_UPID_REGEX
.captures(s
) {
61 let worker_id
= if cap
["wid"].is_empty() {
64 let wid
= unescape_id(&cap
["wid"])?
;
69 pid
: i32::from_str_radix(&cap
["pid"], 16).unwrap(),
70 pstart
: u64::from_str_radix(&cap
["pstart"], 16).unwrap(),
71 starttime
: i64::from_str_radix(&cap
["starttime"], 16).unwrap(),
72 task_id
: usize::from_str_radix(&cap
["task_id"], 16).unwrap(),
73 worker_type
: cap
["wtype"].to_string(),
75 auth_id
: cap
["authid"].to_string(),
76 node
: cap
["node"].to_string(),
79 bail
!("unable to parse UPID '{}'", s
);
84 impl std
::fmt
::Display
for UPID
{
85 fn fmt(&self, f
: &mut std
::fmt
::Formatter
) -> std
::fmt
::Result
{
86 let wid
= if let Some(ref id
) = self.worker_id
{
92 // Note: pstart can be > 32bit if uptime > 497 days, so this can result in
93 // more that 8 characters for pstart
97 "UPID:{}:{:08X}:{:08X}:{:08X}:{:08X}:{}:{}:{}:",
110 impl serde
::Serialize
for UPID
{
111 fn serialize
<S
>(&self, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
113 S
: serde
::ser
::Serializer
,
115 serializer
.serialize_str(&ToString
::to_string(self))
119 impl<'de
> serde
::Deserialize
<'de
> for UPID
{
120 fn deserialize
<D
>(deserializer
: D
) -> Result
<UPID
, D
::Error
>
122 D
: serde
::Deserializer
<'de
>,
124 struct ForwardToStrVisitor
;
126 impl<'a
> serde
::de
::Visitor
<'a
> for ForwardToStrVisitor
{
129 fn expecting(&self, formatter
: &mut std
::fmt
::Formatter
) -> std
::fmt
::Result
{
130 formatter
.write_str("a valid UPID")
133 fn visit_str
<E
: serde
::de
::Error
>(self, v
: &str) -> Result
<UPID
, E
> {
134 v
.parse
::<UPID
>().map_err(|_
| {
135 serde
::de
::Error
::invalid_value(serde
::de
::Unexpected
::Str(v
), &self)
140 deserializer
.deserialize_str(ForwardToStrVisitor
)
144 // the following two are copied as they're the only `proxmox-systemd` dependencies in this crate,
145 // and this crate has MUCH fewer dependencies without it
146 /// non-path systemd-unit compatible escaping
147 fn escape_id(unit
: &str) -> String
{
150 let mut escaped
= String
::new();
152 for (i
, &c
) in unit
.as_bytes().iter().enumerate() {
155 } else if (i
== 0 && c
== b'
.'
)
156 || !matches
!(c
, b'_'
| b'
.'
| b'
0'
..=b'
9'
| b'a'
..=b'z'
| b'A'
..=b'Z'
)
158 // unwrap: writing to a String
159 write
!(escaped
, "\\x{:02x}", c
).unwrap();
161 escaped
.push(char::from(c
));
168 fn hex_digit(d
: u8) -> Result
<u8, Error
> {
170 b'
0'
..=b'
9'
=> Ok(d
- b'
0'
),
171 b'A'
..=b'F'
=> Ok(d
- b'A'
+ 10),
172 b'a'
..=b'f'
=> Ok(d
- b'a'
+ 10),
173 _
=> bail
!("got invalid hex digit"),
177 /// systemd-unit compatible escaping
178 fn unescape_id(text
: &str) -> Result
<String
, Error
> {
179 let mut i
= text
.as_bytes();
181 let mut data
: Vec
<u8> = Vec
::new();
189 if i
.len() < 4 || i
[1] != b'x'
{
190 bail
!("error in escape sequence");
192 let h1
= hex_digit(i
[2])?
;
193 let h0
= hex_digit(i
[3])?
;
194 data
.push(h1
<< 4 | h0
);
196 } else if next
== b'
-'
{
205 let text
= String
::from_utf8(data
)?
;
210 #[cfg(feature = "upid-api-impl")]
212 use std
::os
::unix
::ffi
::OsStrExt
;
213 use std
::sync
::atomic
::{AtomicUsize, Ordering}
;
215 use anyhow
::{bail, format_err, Error}
;
220 /// Create a new UPID
223 worker_id
: Option
<String
>,
225 ) -> Result
<Self, Error
> {
226 let pid
= unsafe { libc::getpid() }
;
228 let bad
: &[_
] = &['
/'
, '
:'
, ' '
];
230 if worker_type
.contains(bad
) {
231 bail
!("illegal characters in worker type '{}'", worker_type
);
234 if auth_id
.contains(bad
) {
235 bail
!("illegal characters in auth_id '{}'", auth_id
);
238 static WORKER_TASK_NEXT_ID
: AtomicUsize
= AtomicUsize
::new(0);
240 let task_id
= WORKER_TASK_NEXT_ID
.fetch_add(1, Ordering
::SeqCst
);
244 pstart
: get_pid_start(pid
)?
,
245 starttime
: epoch_i64(),
247 worker_type
: worker_type
.to_owned(),
250 node
: std
::str::from_utf8(nix
::sys
::utsname
::uname()?
.nodename().as_bytes())
251 .map_err(|_
| format_err
!("non-utf8 nodename not supported"))?
254 .ok_or_else(|| format_err
!("failed to get nodename from uname()"))?
260 fn get_pid_start(pid
: libc
::pid_t
) -> Result
<u64, Error
> {
261 let statstr
= String
::from_utf8(std
::fs
::read(format
!("/proc/{}/stat", pid
))?
)?
;
264 .ok_or_else(|| format_err
!("missing ')' in /proc/PID/stat"))?
;
265 let starttime
= statstr
[cmdend
+ 1..]
267 .split_ascii_whitespace()
269 .ok_or_else(|| format_err
!("failed to find starttime in /proc/{}/stat", pid
))?
;
270 starttime
.parse().map_err(|err
| {
272 "failed to parse starttime from /proc/{}/stat ({:?}): {}",
280 // Copied as this is the only `proxmox-time` dependency in this crate
281 // and this crate has MUCH fewer dependencies without it
282 fn epoch_i64() -> i64 {
283 use std
::time
::{SystemTime, UNIX_EPOCH}
;
285 let now
= SystemTime
::now();
287 if now
> UNIX_EPOCH
{
288 i64::try_from(now
.duration_since(UNIX_EPOCH
).unwrap().as_secs())
289 .expect("epoch_i64: now is too large")
291 -i64::try_from(UNIX_EPOCH
.duration_since(now
).unwrap().as_secs())
292 .expect("epoch_i64: now is too small")