imprimatur 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ use std::collections::HashSet;
2
+
3
+ use super::{Dict, Document, Object, Result};
4
+
5
+ struct LabelRange {
6
+ start: u32,
7
+ style: Option<u8>,
8
+ prefix: Option<String>,
9
+ first: i64,
10
+ }
11
+
12
+ const ROMAN: &[(i64, &str)] = &[
13
+ (1000, "M"),
14
+ (900, "CM"),
15
+ (500, "D"),
16
+ (400, "CD"),
17
+ (100, "C"),
18
+ (90, "XC"),
19
+ (50, "L"),
20
+ (40, "XL"),
21
+ (10, "X"),
22
+ (9, "IX"),
23
+ (5, "V"),
24
+ (4, "IV"),
25
+ (1, "I"),
26
+ ];
27
+
28
+ impl Document<'_> {
29
+ pub fn page_labels(&self, count: usize) -> Result<Option<Vec<Option<String>>>> {
30
+ let Some(ranges) = self.label_ranges()? else { return Ok(None) };
31
+ Ok(Some(
32
+ (0..count)
33
+ .map(|index| label_for(&ranges, u32::try_from(index).unwrap_or(u32::MAX)))
34
+ .collect(),
35
+ ))
36
+ }
37
+
38
+ fn label_ranges(&self) -> Result<Option<Vec<LabelRange>>> {
39
+ let Some(root) = self.trailer().get(b"Root") else { return Ok(None) };
40
+ let root = self.resolve(root)?;
41
+ let Some(catalog) = root.as_dict() else { return Ok(None) };
42
+ let Some(tree) = catalog.get(b"PageLabels") else { return Ok(None) };
43
+ let mut ranges = Vec::new();
44
+ self.walk_labels(tree, 0, &mut HashSet::new(), &mut ranges)?;
45
+ ranges.sort_by_key(|range| range.start);
46
+ ranges.dedup_by_key(|range| range.start);
47
+ Ok(Some(ranges))
48
+ }
49
+
50
+ fn walk_labels(
51
+ &self,
52
+ node: &Object,
53
+ depth: usize,
54
+ visited: &mut HashSet<u32>,
55
+ out: &mut Vec<LabelRange>,
56
+ ) -> Result<()> {
57
+ self.budget.check()?;
58
+ self.budget.nest(depth)?;
59
+ if let Some((number, _)) = node.as_reference()
60
+ && !visited.insert(number)
61
+ {
62
+ return Ok(());
63
+ }
64
+ let node = self.resolve(node)?;
65
+ let Some(dict) = node.as_dict() else { return Ok(()) };
66
+ if let Some(Object::Array(nums)) = self.entry(dict, b"Nums")?.as_deref() {
67
+ let mut nums = nums.iter();
68
+ while let (Some(start), Some(value)) = (nums.next(), nums.next()) {
69
+ let Some(start) =
70
+ self.resolve(start)?.as_integer().and_then(|start| u32::try_from(start).ok())
71
+ else {
72
+ continue;
73
+ };
74
+ let value = self.resolve(value)?;
75
+ if let Some(label) = value.as_dict() {
76
+ out.push(self.label_range(start, label)?);
77
+ }
78
+ }
79
+ }
80
+ if let Some(Object::Array(kids)) = self.entry(dict, b"Kids")?.as_deref() {
81
+ for kid in kids {
82
+ self.walk_labels(kid, depth + 1, visited, out)?;
83
+ }
84
+ }
85
+ Ok(())
86
+ }
87
+
88
+ fn label_range(&self, start: u32, dict: &Dict) -> Result<LabelRange> {
89
+ let style = self
90
+ .entry(dict, b"S")?
91
+ .as_deref()
92
+ .and_then(Object::as_name)
93
+ .and_then(|name| name.first().copied());
94
+ let prefix = self
95
+ .entry(dict, b"P")?
96
+ .as_deref()
97
+ .and_then(Object::as_bytes)
98
+ .map(|bytes| String::from_utf8_lossy(bytes).into_owned());
99
+ let first = self.entry(dict, b"St")?.as_deref().and_then(Object::as_integer).unwrap_or(1);
100
+ Ok(LabelRange { start, style, prefix, first })
101
+ }
102
+ }
103
+
104
+ fn label_for(ranges: &[LabelRange], index: u32) -> Option<String> {
105
+ let range = ranges.iter().rev().find(|range| range.start <= index)?;
106
+ let offset = i64::from(index - range.start);
107
+ let number = range.first.checked_add(offset)?;
108
+ let mut label = range.prefix.clone().unwrap_or_default();
109
+ if let Some(style) = range.style {
110
+ label.push_str(&numeral(style, number));
111
+ }
112
+ Some(label)
113
+ }
114
+
115
+ fn numeral(style: u8, number: i64) -> String {
116
+ match style {
117
+ b'R' => roman(number, true),
118
+ b'r' => roman(number, false),
119
+ b'A' => alpha(number, true),
120
+ b'a' => alpha(number, false),
121
+ _ => number.to_string(),
122
+ }
123
+ }
124
+
125
+ fn roman(number: i64, upper: bool) -> String {
126
+ if number < 1 {
127
+ return number.to_string();
128
+ }
129
+ let mut remaining = number;
130
+ let mut out = String::new();
131
+ for &(value, symbol) in ROMAN {
132
+ while remaining >= value {
133
+ out.push_str(symbol);
134
+ remaining -= value;
135
+ }
136
+ }
137
+ if upper { out } else { out.to_ascii_lowercase() }
138
+ }
139
+
140
+ fn alpha(number: i64, upper: bool) -> String {
141
+ if number < 1 {
142
+ return String::new();
143
+ }
144
+ let zero_based = number - 1;
145
+ let letter = u8::try_from(zero_based % 26).unwrap_or(0);
146
+ let repeat = usize::try_from(zero_based / 26 + 1).unwrap_or(1);
147
+ let letter = if upper { b'A' + letter } else { b'a' + letter };
148
+ std::iter::repeat_n(char::from(letter), repeat).collect()
149
+ }
150
+
151
+ #[cfg(test)]
152
+ mod tests;
@@ -1,6 +1,7 @@
1
1
  //! A PDF file's objects on demand: loaded once and shared, decrypted, and
2
2
  //! their streams decoded; then the pages, with what they inherit.
3
3
 
4
+ mod labels;
4
5
  mod pages;
5
6
 
6
7
  use std::borrow::Cow;
@@ -1,105 +1,190 @@
1
- //! A versioned, bounded reply and numeric-only worker arguments.
1
+ //! Versioned replies and numeric-only arguments for the trusted PDF worker.
2
2
 
3
3
  use std::ffi::OsString;
4
4
  use std::io::Write;
5
+ use std::time::Duration;
5
6
 
6
7
  use super::ProcessLimits;
7
8
  use crate::read::Limits;
8
- use crate::read::pdf::RenderOptions;
9
+ use crate::read::pdf::{ReadOptions, RenderOptions};
9
10
  use crate::{Error, Result};
10
11
 
11
- pub(super) const HEADER: &[u8; 6] = b"IMPR\x01\0";
12
+ pub(super) const HEADER: &[u8; 6] = b"IMPR\x02\0";
12
13
  pub(super) const MAX_ERROR_BYTES: usize = 4096;
13
14
 
15
+ pub(super) enum Operation {
16
+ Info,
17
+ Pages(ReadOptions),
18
+ Render(u32, RenderOptions),
19
+ }
20
+
21
+ impl Operation {
22
+ pub fn maximum(&self) -> Result<usize> {
23
+ let invalid = || Error::InvalidPdfOption("worker output limit is too large");
24
+ let maximum = match self {
25
+ Self::Info => MAX_ERROR_BYTES,
26
+ // JSON can escape each text byte as six ASCII bytes. Reasons and
27
+ // record metadata are separately bounded for every selected page.
28
+ Self::Pages(options) => options
29
+ .max_output_bytes
30
+ .checked_mul(6)
31
+ .and_then(|text| {
32
+ options.max_pages.checked_mul(MAX_ERROR_BYTES * 6 + 512)?.checked_add(text)
33
+ })
34
+ .and_then(|bytes| bytes.checked_add(MAX_ERROR_BYTES * 6 + 512))
35
+ .ok_or_else(invalid)?,
36
+ Self::Render(_, options) => options.max_output_bytes,
37
+ };
38
+ maximum.max(MAX_ERROR_BYTES).checked_add(HEADER.len()).ok_or_else(invalid)
39
+ }
40
+ }
41
+
14
42
  pub(super) struct Request {
15
- pub number: u32,
16
- pub options: RenderOptions,
43
+ pub restricted: bool,
44
+ pub operation: Operation,
17
45
  pub limits: Limits,
18
46
  pub memory: u64,
19
47
  pub cpu_seconds: u64,
20
48
  }
21
49
 
22
50
  impl Request {
23
- pub fn new(
24
- number: u32,
25
- options: &RenderOptions,
26
- limits: &Limits,
27
- process: &ProcessLimits,
28
- ) -> Result<Self> {
29
- options
30
- .max_output_bytes
31
- .checked_add(HEADER.len())
32
- .ok_or(Error::InvalidPdfOption("output limit is too large"))?;
51
+ pub fn new(operation: Operation, limits: &Limits, process: &ProcessLimits) -> Result<Self> {
52
+ operation.maximum()?;
53
+ match &operation {
54
+ Operation::Pages(options) => options.validate()?,
55
+ Operation::Render(0, _) => {
56
+ return Err(Error::InvalidPdfOption("page number must be positive"));
57
+ }
58
+ _ => {}
59
+ }
33
60
  if process.memory_bytes == 0 || process.memory_bytes >= i64::MAX as u64 {
34
61
  return Err(Error::InvalidPdfOption("memory limit must be positive and finite"));
35
62
  }
36
- let cpu_seconds = process
37
- .wall_time
63
+ let wall = limits.timeout.unwrap_or(process.wall_time).min(process.wall_time);
64
+ let cpu_seconds = wall
38
65
  .as_secs()
39
- .checked_add(u64::from(process.wall_time.subsec_nanos() != 0))
66
+ .checked_add(u64::from(wall.subsec_nanos() != 0))
40
67
  .filter(|&seconds| seconds != 0 && seconds < i64::MAX as u64)
41
68
  .ok_or(Error::InvalidPdfOption("worker wall time must be positive and finite"))?;
69
+ u64::try_from(wall.as_nanos())
70
+ .map_err(|_| Error::InvalidPdfOption("worker wall time is too large"))?;
42
71
  Ok(Self {
43
- number,
44
- options: options.clone(),
45
- limits: limits.clone(),
72
+ restricted: false,
73
+ operation,
74
+ limits: limits.clone().timeout(Some(wall)),
46
75
  memory: process.memory_bytes,
47
76
  cpu_seconds,
48
77
  })
49
78
  }
50
79
 
51
80
  pub fn arguments(&self) -> Vec<OsString> {
52
- [
53
- u64::from(self.number),
54
- u64::from(self.options.max_side),
55
- self.options.max_pixels,
56
- self.options.max_output_bytes as u64,
57
- u64::from(self.options.grayscale),
81
+ let timeout = self.limits.timeout.unwrap_or_default();
82
+ let mut values = vec![
83
+ u64::from(self.restricted),
58
84
  self.limits.max_input as u64,
59
85
  self.limits.max_inflated,
60
86
  self.limits.max_depth as u64,
61
87
  self.memory,
62
88
  self.cpu_seconds,
63
- ]
64
- .map(|number| OsString::from(number.to_string()))
65
- .to_vec()
89
+ u64::try_from(timeout.as_nanos()).unwrap_or(u64::MAX),
90
+ ];
91
+ match &self.operation {
92
+ Operation::Info => values.push(0),
93
+ Operation::Pages(options) => values.extend([
94
+ 1,
95
+ u64::from(options.first_page),
96
+ u64::from(options.last_page),
97
+ options.max_pages as u64,
98
+ options.max_page_bytes as u64,
99
+ options.max_output_bytes as u64,
100
+ ]),
101
+ Operation::Render(number, options) => values.extend([
102
+ 2,
103
+ u64::from(*number),
104
+ u64::from(options.max_side),
105
+ options.max_pixels,
106
+ options.max_output_bytes as u64,
107
+ u64::from(options.grayscale),
108
+ ]),
109
+ }
110
+ values.into_iter().map(|number| OsString::from(number.to_string())).collect()
66
111
  }
67
112
 
68
113
  pub fn parse() -> Result<Self> {
69
- let invalid = || Error::InvalidPdfOption("invalid render worker arguments");
114
+ let invalid = || Error::InvalidPdfOption("invalid PDF worker arguments");
70
115
  let values: Vec<u64> = std::env::args()
71
116
  .skip(1)
72
117
  .map(|value| value.parse().map_err(|_| invalid()))
73
118
  .collect::<Result<_>>()?;
74
- let [number, side, pixels, output, gray, input, inflated, depth, memory, cpu] =
119
+ let [restricted, input, inflated, depth, memory, cpu, timeout, rest @ ..] =
75
120
  values.as_slice()
76
121
  else {
77
122
  return Err(invalid());
78
123
  };
79
- if *gray > 1
80
- || *memory == 0
81
- || *memory >= i64::MAX as u64
82
- || *cpu == 0
83
- || *cpu >= i64::MAX as u64
84
- {
124
+ if *restricted > 1 {
85
125
  return Err(invalid());
86
126
  }
87
- Ok(Self {
88
- number: u32::try_from(*number).map_err(|_| invalid())?,
89
- options: RenderOptions {
90
- max_side: u16::try_from(*side).map_err(|_| invalid())?,
91
- max_pixels: *pixels,
92
- max_output_bytes: usize::try_from(*output).map_err(|_| invalid())?,
93
- grayscale: *gray == 1,
94
- },
95
- limits: Limits::default()
96
- .max_input(usize::try_from(*input).map_err(|_| invalid())?)
97
- .max_inflated(*inflated)
98
- .max_depth(usize::try_from(*depth).map_err(|_| invalid())?)
99
- .timeout(None),
100
- memory: *memory,
101
- cpu_seconds: *cpu,
102
- })
127
+ let length = |value| usize::try_from(value).map_err(|_| invalid());
128
+ let operation = match rest {
129
+ [0] => Operation::Info,
130
+ [1, first, last, count, page, output] => Operation::Pages(ReadOptions {
131
+ first_page: u32::try_from(*first).map_err(|_| invalid())?,
132
+ last_page: u32::try_from(*last).map_err(|_| invalid())?,
133
+ max_pages: length(*count)?,
134
+ max_page_bytes: length(*page)?,
135
+ max_output_bytes: length(*output)?,
136
+ }),
137
+ [2, number, side, pixels, output, gray] if *gray <= 1 => Operation::Render(
138
+ u32::try_from(*number).map_err(|_| invalid())?,
139
+ RenderOptions {
140
+ max_side: u16::try_from(*side).map_err(|_| invalid())?,
141
+ max_pixels: *pixels,
142
+ max_output_bytes: length(*output)?,
143
+ grayscale: *gray == 1,
144
+ },
145
+ ),
146
+ _ => return Err(invalid()),
147
+ };
148
+ let limits = Limits::default()
149
+ .max_input(length(*input)?)
150
+ .max_inflated(*inflated)
151
+ .max_depth(length(*depth)?)
152
+ .timeout(Some(Duration::from_nanos(*timeout)));
153
+ let process =
154
+ ProcessLimits { memory_bytes: *memory, wall_time: Duration::from_nanos(*timeout) };
155
+ let mut request = Self::new(operation, &limits, &process)?;
156
+ request.restricted = *restricted == 1;
157
+ if request.cpu_seconds != *cpu {
158
+ return Err(invalid());
159
+ }
160
+ Ok(request)
161
+ }
162
+ }
163
+
164
+ pub(super) fn failure(error: &Error) -> (u8, String) {
165
+ let kind = match error {
166
+ Error::PasswordRequired => 1,
167
+ Error::UnsupportedDocument(_) => 2,
168
+ Error::LimitExceeded(_) | Error::Cancelled => 3,
169
+ Error::InvalidPdfOption(_) => 4,
170
+ _ => 5,
171
+ };
172
+ let mut message = error.to_string();
173
+ let mut end = message.len().min(MAX_ERROR_BYTES);
174
+ while !message.is_char_boundary(end) {
175
+ end -= 1;
176
+ }
177
+ message.truncate(end);
178
+ (kind, message)
179
+ }
180
+
181
+ pub(super) fn error(kind: u8, message: String) -> Error {
182
+ match kind {
183
+ 1 => Error::PasswordRequired,
184
+ 2 => Error::UnsupportedDocument(message),
185
+ 3 => Error::LimitExceeded("PDF worker limit"),
186
+ 4 => Error::InvalidPdfOption("the PDF worker rejected the options"),
187
+ _ => Error::malformed("PDF worker", message),
103
188
  }
104
189
  }
105
190
 
@@ -111,20 +196,10 @@ pub(super) fn write_reply(mut out: impl Write, result: Result<Vec<u8>>) -> Resul
111
196
  out.write_all(&data)?;
112
197
  }
113
198
  Err(error) => {
114
- header[5] = match &error {
115
- Error::PasswordRequired => 1,
116
- Error::UnsupportedDocument(_) => 2,
117
- Error::LimitExceeded(_) | Error::Cancelled => 3,
118
- Error::InvalidPdfOption(_) => 4,
119
- _ => 5,
120
- };
121
- let message = error.to_string();
122
- let mut end = message.len().min(MAX_ERROR_BYTES);
123
- while !message.is_char_boundary(end) {
124
- end -= 1;
125
- }
199
+ let (kind, message) = failure(&error);
200
+ header[5] = kind;
126
201
  out.write_all(&header)?;
127
- out.write_all(&message.as_bytes()[..end])?;
202
+ out.write_all(message.as_bytes())?;
128
203
  }
129
204
  }
130
205
  out.flush()?;
@@ -133,23 +208,17 @@ pub(super) fn write_reply(mut out: impl Write, result: Result<Vec<u8>>) -> Resul
133
208
 
134
209
  pub(super) fn decode(mut data: Vec<u8>, maximum: usize) -> Result<Vec<u8>> {
135
210
  if data.len() < HEADER.len() || data[..5] != HEADER[..5] {
136
- return Err(Error::malformed("render worker", "missing or incompatible response"));
211
+ return Err(Error::malformed("PDF worker", "missing or incompatible response"));
137
212
  }
138
213
  let status = data[5];
139
214
  data.drain(..HEADER.len());
140
215
  if status == 0 {
141
216
  return if data.len() > maximum {
142
- Err(Error::LimitExceeded("rendered bytes"))
217
+ Err(Error::LimitExceeded("PDF worker output"))
143
218
  } else {
144
219
  Ok(data)
145
220
  };
146
221
  }
147
222
  let message = String::from_utf8_lossy(&data[..data.len().min(MAX_ERROR_BYTES)]).into_owned();
148
- Err(match status {
149
- 1 => Error::PasswordRequired,
150
- 2 => Error::UnsupportedDocument(message),
151
- 3 => Error::LimitExceeded("render worker limit"),
152
- 4 => Error::InvalidPdfOption("the render worker rejected the rendering options"),
153
- _ => Error::malformed("render worker", message),
154
- })
223
+ Err(error(status, message))
155
224
  }
@@ -0,0 +1,113 @@
1
+ //! Flushed page records survive a worker timeout or resource termination.
2
+
3
+ use std::io::Write;
4
+
5
+ use serde::{Deserialize, Serialize};
6
+
7
+ use super::{Output, protocol};
8
+ use crate::read::pdf::{Info, PageResult, Pages, ReadOptions, Status, bounded};
9
+ use crate::{Error, Result};
10
+
11
+ #[derive(Serialize, Deserialize)]
12
+ pub(super) enum Frame {
13
+ Info(Info),
14
+ Page(PageResult),
15
+ End,
16
+ Error { kind: u8, message: String },
17
+ }
18
+
19
+ pub(super) fn write(out: &mut impl Write, frame: &Frame) -> Result<()> {
20
+ serde_json::to_writer(&mut *out, frame)
21
+ .map_err(|error| Error::malformed("PDF worker", error.to_string()))?;
22
+ out.write_all(b"\n")?;
23
+ out.flush()?;
24
+ Ok(())
25
+ }
26
+
27
+ pub(super) fn decode(output: Output, options: &ReadOptions) -> Result<Pages> {
28
+ let mut failure = output.failure;
29
+ if output.data.len() < protocol::HEADER.len() {
30
+ return Err(failure.unwrap_or_else(|| malformed("missing response")));
31
+ }
32
+ let bytes =
33
+ protocol::decode(output.data, protocol::Operation::Pages(options.clone()).maximum()?)?;
34
+ let mut info = None;
35
+ let mut pages = Vec::new();
36
+ let mut text_bytes = 0_usize;
37
+ let mut ended = false;
38
+ for line in bytes.split_inclusive(|&byte| byte == b'\n') {
39
+ if ended {
40
+ return Err(malformed("records after the end of the response"));
41
+ }
42
+ if !line.ends_with(b"\n") && failure.is_some() {
43
+ break; // A killed worker may leave one incomplete final record.
44
+ }
45
+ let frame: Frame =
46
+ serde_json::from_slice(line).map_err(|error| malformed(&error.to_string()))?;
47
+ match frame {
48
+ Frame::Info(value) if info.is_none() && pages.is_empty() => info = Some(value),
49
+ Frame::Page(page) => {
50
+ let metadata = info.ok_or_else(|| malformed("page before metadata"))?;
51
+ validate(&page, pages.len(), metadata, options)?;
52
+ text_bytes = text_bytes
53
+ .checked_add(page.text.len())
54
+ .filter(|&bytes| bytes <= options.max_output_bytes)
55
+ .ok_or_else(|| malformed("text exceeds the requested limit"))?;
56
+ pages.push(page);
57
+ }
58
+ Frame::End => ended = true,
59
+ Frame::Error { kind, message } => {
60
+ failure = Some(protocol::error(kind, message));
61
+ ended = true;
62
+ }
63
+ Frame::Info(_) => return Err(malformed("duplicate metadata")),
64
+ }
65
+ }
66
+ let info =
67
+ info.ok_or_else(|| failure.take().unwrap_or_else(|| malformed("missing metadata")))?;
68
+ let count = selected(info, options);
69
+ if pages.len() == count && ended {
70
+ return Ok(Pages { info, pages });
71
+ }
72
+ let error = failure.ok_or_else(|| malformed("incomplete response"))?;
73
+ let reason = error.to_string();
74
+ let timed_out = matches!(
75
+ error,
76
+ Error::LimitExceeded("time" | "render worker wall time" | "PDF worker limit")
77
+ );
78
+ let first_missing = pages.len();
79
+ for index in first_missing..count {
80
+ let number =
81
+ options.first_page + u32::try_from(index).map_err(|_| malformed("page index"))?;
82
+ let status =
83
+ if timed_out || index > first_missing { Status::Skipped } else { Status::Failed };
84
+ pages.push(bounded::unread(number, status, reason.clone()));
85
+ }
86
+ Ok(Pages { info, pages })
87
+ }
88
+
89
+ fn selected(info: Info, options: &ReadOptions) -> usize {
90
+ let count = options
91
+ .last_page
92
+ .min(info.page_count)
93
+ .checked_sub(options.first_page)
94
+ .map_or(0, |difference| u64::from(difference) + 1);
95
+ usize::try_from(count).unwrap_or(usize::MAX).min(options.max_pages)
96
+ }
97
+
98
+ fn validate(page: &PageResult, index: usize, info: Info, options: &ReadOptions) -> Result<()> {
99
+ let expected = u64::from(options.first_page) + index as u64;
100
+ if index >= selected(info, options)
101
+ || u64::from(page.number) != expected
102
+ || page.text.len() > options.max_page_bytes
103
+ || page.reason.as_ref().is_some_and(|reason| reason.len() > protocol::MAX_ERROR_BYTES)
104
+ || (matches!(page.status, Status::Failed | Status::Skipped) && !page.text.is_empty())
105
+ {
106
+ return Err(malformed("invalid page record"));
107
+ }
108
+ Ok(())
109
+ }
110
+
111
+ fn malformed(reason: &str) -> Error {
112
+ Error::malformed("PDF worker", reason)
113
+ }
@@ -28,6 +28,33 @@ pub(super) fn apply_limits(memory: u64, cpu: u64, output: u64) -> Result<()> {
28
28
  Ok(())
29
29
  }
30
30
 
31
+ /// Replace the bootstrap after setting address-space limits, so the loader
32
+ /// and parser cannot reuse memory mapped before the cap was installed.
33
+ #[cfg(any(target_os = "linux", target_os = "freebsd"))]
34
+ pub(super) fn reexec(arguments: &[std::ffi::OsString]) -> Result<()> {
35
+ use std::ffi::CString;
36
+ use std::os::unix::ffi::OsStrExt;
37
+
38
+ let string = |value: &[u8]| {
39
+ CString::new(value)
40
+ .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidInput, error))
41
+ };
42
+ let executable = string(std::env::current_exe()?.as_os_str().as_bytes())?;
43
+ let mut args = vec![executable.clone(), string(b"1")?];
44
+ for argument in arguments.iter().skip(1) {
45
+ args.push(string(argument.as_os_str().as_bytes())?);
46
+ }
47
+ match nix::unistd::execv(&executable, &args) {
48
+ Ok(never) => match never {},
49
+ // A tiny address-space cap can prevent exec from allocating its
50
+ // argument/environment pages and surface as E2BIG rather than ENOMEM.
51
+ Err(Errno::ENOMEM | Errno::E2BIG) => {
52
+ Err(Error::LimitExceeded("PDF worker startup resources"))
53
+ }
54
+ Err(error) => Err(std::io::Error::from(error).into()),
55
+ }
56
+ }
57
+
31
58
  fn lower(resource: Resource, value: u64) -> Result<()> {
32
59
  let (soft, hard) = getrlimit(resource).map_err(std::io::Error::from)?;
33
60
  let value = bounded_limit(value, soft, hard)?;