1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// Putting a container on disk: a file that appears only once it is complete.
//
// The reading side has taken a path since 0.1.0, through `Container::open`.
// This is the other half of that symmetry, and it is a feature rather than
// part of the default surface because a caller who only reads containers
// should not acquire a temporary-file dependency to do it.
//
// Author: David M. Anderson
// Built with AI assistance (Claude, Anthropic)
use ;
use ;
use ;
use NamedTempFile;
use crateResult;
/// A file that appears under its real name only once it has been written.
///
/// Requires the `fs` feature, which is off by default:
/// `slpc = { version = "0.3", features = ["fs"] }`.
///
/// Everything is written to a temporary file beside the destination and renamed
/// into place at the end, so a write that fails partway leaves nothing behind
/// rather than a truncated container that looks like one, and replacing a file
/// cannot destroy the old one and then fail to produce the new.
///
/// The handle it lends out is a [`File`], so it satisfies the `Write + Seek`
/// that [`Repack::write`](crate::Repack::write) asks for.
///
/// ```no_run
/// # fn main() -> slpc::Result<()> {
/// let mut out = slpc::Destination::new("report.pdf.slpc", false)?;
/// slpc::pack_file("report.pdf", slpc::toml_edit::DocumentMut::new(), out.writer())?;
/// out.commit()?;
/// # Ok(())
/// # }
/// ```
///
/// Nothing is written to the destination until [`commit`](Destination::commit)
/// is called, and dropping one without committing removes the temporary file.
/// The refusal to overwrite, carrying the path that was refused.
/// The permissions a file created the ordinary way beside `near` would have.
///
/// Measured rather than asked for. What a new file gets is 0666 with the
/// process umask taken out of it, and there is no way to read the umask without
/// setting it, which needs a C call and the `unsafe` this crate forbids. So a
/// file is created the ordinary way, asked what it got, and removed. It costs
/// three system calls once per destination, and it is the difference between
/// handing back a container the umask decided who can read and one only its
/// author can.
///
/// The probe sits beside the temporary file and borrows its name, which is
/// already unique to this process, so nothing else can be creating it.
/// Where a payload named `name` belongs inside `dir`, spelled the way this
/// platform can address it.
///
/// **Use this rather than `dir.join(name)`.**
/// [`check_payload_name`](crate::check_payload_name) answers whether a name is
/// legal under SPEC 2.3, and a legal name is not always a file. Win32 resolves
/// `CON`, `CON.txt`, `con`, `COM1`, `AUX`, `LPT1`, `PRN` and `NUL` to devices
/// wherever the name appears, so `dir.join("CON")` is the console rather than a
/// path in `dir`. It is not a traversal, and the check against SPEC 2.3 does not
/// catch it — writing there can silently discard the payload, and reading it
/// back can block forever.
///
/// On Windows this answers in the `\\?\` verbatim form, which reaches the
/// filesystem without those names being looked for. Nothing here keeps a list of
/// reserved names: the form is asked of the *directory*, so which names are
/// devices stays Windows's to know as that list changes.
///
/// **Two things a caller has to know about the result.** It is where the file
/// *is* rather than how the caller spelled it — `canonicalize` expands 8.3 short
/// names and resolves junctions — so compare files rather than strings if you
/// hold a path of your own. And [`display_path`] is what takes the prefix off
/// before a person reads it, since the prefix is how a path is addressed and not
/// part of its name.
///
/// Everywhere but Windows the directory is the directory and this joins and
/// returns. The verbatim form is deliberately not produced on Unix, where
/// `canonicalize` would also resolve symbolic links and so move where a payload
/// lands to fix a problem that platform does not have.
///
/// Opening the result is a separate question and still fails: the shell answers
/// *the specified device name is invalid* for a file named after one. That is
/// the truth about such a container on that platform rather than something left
/// undone here.
///
/// # Errors
///
/// On Windows, whatever `canonicalize` says about `dir`, so a directory that is
/// not there is an error here rather than at the first write. Nowhere else.
/// A path as it should be shown to a person.
///
/// [`payload_path`] hands back the `\\?\` verbatim form on Windows, because
/// that is what addresses a file whose name Windows would otherwise read as a
/// device. The prefix is how a path is addressed and not part of its name, so
/// printing it would tell somebody their payload went to a place spelled in a
/// way they have never seen and could not type. This crate introduced the
/// prefix, so this crate owes a caller the way to take it off.
///
/// Only the display form changes: every filesystem call keeps the spelling that
/// works. A no-op on a path that never carried the prefix, so a caller does not
/// have to know which kind it is holding.