zlayer-agent 0.12.4

Container runtime agent using libcontainer/youki
Documentation
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
//! Backup-stream encoding/decoding for Windows container layers.
//!
//! Windows OCI layers carry file content + NTFS metadata (reparse points,
//! extended attributes, security descriptors, alternate data streams) in
//! `BackupRead`/`BackupWrite` stream format. This module wraps those Win32
//! APIs with Rust-friendly readers and writers, and exposes the privilege
//! helper that `HcsImportLayer` needs to materialize a layer directory.
//!
//! See `hcsshim/internal/wclayer/legacy.go` for the reference implementation.

#![cfg(target_os = "windows")]
#![allow(unsafe_code)]

use std::ffi::OsStr;
use std::io::{self, Read, Write};
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};

use windows::core::PCWSTR;
use windows::Win32::Foundation::ERROR_ALREADY_EXISTS;
use windows::Win32::Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE, LUID};
use windows::Win32::Security::{
    AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED,
    TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY,
};
use windows::Win32::Storage::FileSystem::{
    BackupRead, BackupWrite, CreateDirectoryW, CreateFileW, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
    FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};

/// Convert a path to a UTF-16 null-terminated buffer suitable for
/// `CreateFileW`, with the Windows extended-length-path prefix
/// (`\\?\` or `\\?\UNC\`) applied so the call works for paths
/// longer than `MAX_PATH` (260 chars).
///
/// Required for unpacking deep Windows container layers
/// (e.g. `Files/Windows/WinSxS/…/<60-char SxS component>/<long filename>.dll`)
/// where the post-canonicalization absolute path easily exceeds 260 chars.
/// Without this prefix `CreateFileW` returns `ERROR_PATH_NOT_FOUND` (0x80070003)
/// even when `create_dir_all` has materialized every parent directory.
///
/// # Errors
///
/// Returns the OS error if the path cannot be made absolute (extremely rare —
/// typically only happens if `current_dir()` itself fails for a relative input).
pub(crate) fn to_extended_wide(path: &Path) -> io::Result<Vec<u16>> {
    // Hoisted constants so clippy::items_after_statements doesn't complain
    // about declaring `const` items mid-function. These describe the three
    // path prefixes Windows treats specially:
    //   * `\\?\`  - verbatim (skip kernel path normalisation, ~32k limit)
    //   * `\\.\`  - device namespace (e.g. `\\.\PhysicalDrive0`)
    //   * `\\`    - generic UNC (`\\server\share\...`)
    const VERBATIM_PREFIX: &[u16] = &['\\' as u16, '\\' as u16, '?' as u16, '\\' as u16];
    const DEVICE_PREFIX: &[u16] = &['\\' as u16, '\\' as u16, '.' as u16, '\\' as u16];
    const UNC_PREFIX: &[u16] = &['\\' as u16, '\\' as u16];

    // Resolve to an absolute path. Prefer `canonicalize` (resolves symlinks
    // and produces a verbatim form) when the path exists; fall back to
    // `std::path::absolute` (stable since 1.79) which only normalises
    // lexically, so it works for not-yet-created targets like the file we're
    // about to open with `CREATE_ALWAYS`. Last resort: join CWD by hand.
    let abs: PathBuf = match path.canonicalize() {
        Ok(p) => p,
        Err(_) => match std::path::absolute(path) {
            Ok(p) => p,
            Err(_) => {
                if path.is_absolute() {
                    path.to_path_buf()
                } else {
                    std::env::current_dir()?.join(path)
                }
            }
        },
    };

    let wide_in: Vec<u16> = abs.as_os_str().encode_wide().collect();

    // If already prefixed with `\\?\` or `\\.\`, pass through unchanged
    // (just null-terminate).
    if wide_in.starts_with(VERBATIM_PREFIX) || wide_in.starts_with(DEVICE_PREFIX) {
        let mut out = wide_in;
        out.push(0);
        return Ok(out);
    }

    // UNC path `\\server\share\...` -> `\\?\UNC\server\share\...`
    let mut out: Vec<u16>;
    if wide_in.starts_with(UNC_PREFIX) {
        out = Vec::with_capacity(wide_in.len() + 6);
        out.extend("\\\\?\\UNC".encode_utf16());
        // Drop the leading single backslash from `\\server\share\path` so we
        // end up with `\\?\UNC\server\share\path`.
        out.extend_from_slice(&wide_in[1..]);
    } else {
        // Local path -> `\\?\C:\path`.
        out = Vec::with_capacity(wide_in.len() + 4);
        out.extend("\\\\?\\".encode_utf16());
        out.extend_from_slice(&wide_in);
    }
    out.push(0);
    Ok(out)
}

/// Create (or truncate) a file at `path` using `CreateFileW(CREATE_ALWAYS)`
/// with the extended-length-path prefix so it works for paths longer than
/// `MAX_PATH`. Returns an owned [`std::fs::File`] wrapping the resulting
/// handle so the caller can `write_all` / `io::copy` into it without
/// re-opening the path through a non-long-path-aware API.
///
/// Used by the OCI Windows layer unpacker for non-`Files/` payloads
/// (`Hives/`, `tombstones.txt`, `UtilityVM/`) and as the inner implementation
/// of [`BackupStreamWriter::create_new`] (which immediately drops the
/// returned file and re-opens via `BACKUP_SEMANTICS`).
///
/// `std::fs::File::create` is intentionally avoided because it does not apply
/// the long-path prefix automatically, so it fails with
/// `ERROR_PATH_NOT_FOUND` (0x80070003) for the deep `WinSxS` paths that appear
/// in `mcr.microsoft.com/windows/nanoserver` layers.
///
/// # Errors
///
/// Returns the OS error from `CreateFileW`.
pub(crate) fn create_long_path_file(path: &Path) -> io::Result<std::fs::File> {
    use std::os::windows::io::FromRawHandle;
    let wide = to_extended_wide(path)?;
    // SAFETY: `wide` is a valid null-terminated UTF-16 buffer that outlives
    // the call. All other arguments are plain values.
    let handle = unsafe {
        CreateFileW(
            PCWSTR::from_raw(wide.as_ptr()),
            GENERIC_WRITE.0,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            None,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            None,
        )
    }
    .map_err(|e| io::Error::other(format!("CreateFileW(CREATE_ALWAYS): {e}")))?;
    // SAFETY: `handle` was just returned by a successful `CreateFileW` (so it
    // is non-null and a real Win32 file handle), and no other owner exists.
    // Transferring ownership to `std::fs::File` is sound; the `File`'s Drop
    // will call `CloseHandle` for us.
    let file = unsafe { std::fs::File::from_raw_handle(handle.0.cast()) };
    Ok(file)
}

/// Create a single directory at `path` using `CreateDirectoryW` with the
/// extended-length-path prefix so it works for paths longer than `MAX_PATH`.
/// `ERROR_ALREADY_EXISTS` is treated as success (matches `std::fs::create_dir`
/// behaviour when paired with `create_dir_all`-style walks).
///
/// Used by the OCI Windows layer unpacker's `create_long_path_dir_all`
/// fallback when `std::fs::create_dir_all` returns `ERROR_PATH_NOT_FOUND`
/// (`0x80070003`) on the deep `WinSxS` / `Catroot` directory chains embedded
/// in `mcr.microsoft.com/windows/nanoserver` layers.
///
/// # Errors
///
/// Returns the OS error from `CreateDirectoryW` (except for
/// `ERROR_ALREADY_EXISTS`, which is swallowed).
pub(crate) fn create_long_path_dir(path: &Path) -> io::Result<()> {
    let wide = to_extended_wide(path)?;
    // SAFETY: `wide` is a valid null-terminated UTF-16 buffer that outlives
    // the call. `lpsecurityattributes = None` requests default ACLs.
    let res = unsafe { CreateDirectoryW(PCWSTR::from_raw(wide.as_ptr()), None) };
    if let Err(e) = res {
        // Idempotent: already-exists is fine for a create_dir_all-style walk.
        // windows-rs returns a `windows::core::Error` whose `code()` is the
        // HRESULT-wrapped Win32 status; check both flavours.
        #[allow(clippy::cast_sign_loss)]
        let raw = e.code().0 as u32;
        let already_exists_hresult = 0x8007_0000u32 | (ERROR_ALREADY_EXISTS.0 & 0xFFFF);
        if raw == already_exists_hresult || raw == ERROR_ALREADY_EXISTS.0 {
            return Ok(());
        }
        return Err(io::Error::other(format!("CreateDirectoryW: {e}")));
    }
    Ok(())
}

/// Enable `SeBackupPrivilege` and `SeRestorePrivilege` on the current process
/// token. `HcsImportLayer` (and any `BackupWrite` into a system-protected path)
/// requires both. Idempotent — safe to call multiple times.
///
/// # Errors
///
/// Returns an error if the process token can't be opened, the privilege names
/// don't resolve, or `AdjustTokenPrivileges` reports a partial success (i.e.
/// the caller's account doesn't actually hold the privilege, typical for non-
/// admin users).
pub fn enable_backup_restore_privileges() -> io::Result<()> {
    unsafe {
        let mut token = HANDLE::default();
        OpenProcessToken(
            GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
            &mut token,
        )
        .map_err(|e| io::Error::other(format!("OpenProcessToken: {e}")))?;

        let backup_res = enable_named_privilege(token, "SeBackupPrivilege");
        let restore_res = enable_named_privilege(token, "SeRestorePrivilege");

        // Close the token regardless of the result. Failure is non-fatal — log
        // and continue so we can still surface the underlying privilege error.
        if let Err(e) = CloseHandle(token) {
            tracing::debug!("CloseHandle(token) failed: {e}");
        }

        backup_res?;
        restore_res?;
    }
    Ok(())
}

/// Enable a single named privilege via `LookupPrivilegeValueW` +
/// `AdjustTokenPrivileges`.
///
/// # Safety
///
/// `token` must be a valid process-token handle opened with at least
/// `TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY` access, and must remain valid for
/// the duration of this call.
unsafe fn enable_named_privilege(token: HANDLE, name: &str) -> io::Result<()> {
    let name_w: Vec<u16> = OsStr::new(name)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let mut luid = LUID::default();
    unsafe {
        LookupPrivilegeValueW(PCWSTR::null(), PCWSTR::from_raw(name_w.as_ptr()), &mut luid)
            .map_err(|e| io::Error::other(format!("LookupPrivilegeValueW({name}): {e}")))?;

        let privs = TOKEN_PRIVILEGES {
            PrivilegeCount: 1,
            Privileges: [LUID_AND_ATTRIBUTES {
                Luid: luid,
                Attributes: SE_PRIVILEGE_ENABLED,
            }],
        };
        AdjustTokenPrivileges(token, false, Some(&raw const privs), 0, None, None)
            .map_err(|e| io::Error::other(format!("AdjustTokenPrivileges({name}): {e}")))?;
    }
    Ok(())
}

/// Thin writer that streams bytes into a file via `BackupWrite`, preserving
/// Win32 backup stream headers. Used when applying an incoming wclayer tar
/// entry to the destination folder.
///
/// The caller is responsible for producing the backup-stream framing
/// (`WIN32_STREAM_ID` headers + payload) and feeding that into [`Write::write`]
/// in a single contiguous stream. `BackupWrite` internally tracks state across
/// calls through the `lpContext` pointer we hold in [`Self::context`].
pub struct BackupStreamWriter {
    handle: HANDLE,
    context: *mut core::ffi::c_void,
}

// `BackupStreamWriter` owns its HANDLE and context pointer and is not shared
// across threads from within this module. We still implement `Send` explicitly
// because raw pointers are `!Send` by default — the underlying `BackupWrite`
// state is safe to move between threads as long as only one thread uses it at
// a time, which Rust's `&mut self` borrows guarantee.
unsafe impl Send for BackupStreamWriter {}

impl BackupStreamWriter {
    /// Open an **existing** target file for backup writing. Path must be
    /// UTF-16-encodable. The file must already exist — use [`Self::create_new`]
    /// when materializing a brand-new wclayer file from a tar entry.
    ///
    /// # Errors
    ///
    /// Returns the OS error from `CreateFileW` if the target can't be opened.
    pub fn create(path: &Path) -> io::Result<Self> {
        // CRITICAL: `BackupWrite` applies the `BACKUP_SECURITY_DATA` stream
        // (owner + DACL + SACL) ONLY for the access rights the handle was
        // opened with. Plain `GENERIC_WRITE` lacks `WRITE_DAC`/`WRITE_OWNER`/
        // `ACCESS_SYSTEM_SECURITY`, so the kernel SILENTLY DROPS the security
        // descriptor and the materialized file inherits the host directory's
        // ACL instead of the image's. That ACL omits the BUILTIN\Users (BU) /
        // ALL APPLICATION PACKAGES (AC) read+execute grants that in-guest
        // services running as LocalService (e.g. `mpssvc`, the Windows
        // Firewall) need to load their DLLs — so `mpssvc` fails to start, the
        // SCM-gated `gcs` service that depends on it never starts, and the UVM
        // never dials the host GCS bridge (the "never-dial" bug). Request the
        // security-bearing rights explicitly; the already-enabled
        // `SeRestorePrivilege` + `FILE_FLAG_BACKUP_SEMANTICS` let `CreateFileW`
        // grant them. Mirrors hcsshim's `safefile` open flags for layer writes.
        const WRITE_DAC: u32 = 0x0004_0000;
        const WRITE_OWNER: u32 = 0x0008_0000;
        const ACCESS_SYSTEM_SECURITY: u32 = 0x0100_0000;
        // `to_extended_wide` adds the `\\?\` prefix so we can open paths
        // longer than MAX_PATH (260 chars) — see the helper's doc comment.
        let wide = to_extended_wide(path)?;
        let desired_access = GENERIC_WRITE.0 | WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY;
        let handle = unsafe {
            CreateFileW(
                PCWSTR::from_raw(wide.as_ptr()),
                desired_access,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                None,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
                None,
            )
        }
        .map_err(|e| io::Error::other(format!("CreateFileW: {e}")))?;

        Ok(Self {
            handle,
            context: std::ptr::null_mut(),
        })
    }

    /// Create (or truncate) the target file and open it for backup writing.
    ///
    /// This is the variant used by the OCI Windows layer unpacker: tar
    /// entries name files that don't yet exist on disk, so we first materialize
    /// an empty file via [`create_long_path_file`] (a `CreateFileW(CREATE_ALWAYS)`
    /// with the `\\?\` long-path prefix) and then reopen it with the
    /// `BACKUP_SEMANTICS` flag via [`Self::create`]. The two-step dance
    /// avoids threading a creation-mode parameter through the raw
    /// `CreateFileW` call.
    ///
    /// `std::fs::File::create` is intentionally avoided here because it does
    /// not apply the extended-length-path prefix, so it fails with
    /// `ERROR_PATH_NOT_FOUND` (0x80070003) for the deep `WinSxS` paths that
    /// appear in `mcr.microsoft.com/windows/nanoserver` layers.
    ///
    /// # Errors
    ///
    /// Returns the OS error from either the initial `CreateFileW(CREATE_ALWAYS)`
    /// or the subsequent `CreateFileW(OPEN_EXISTING)` open.
    pub fn create_new(path: &Path) -> io::Result<Self> {
        // Touch the file via the long-path-aware helper. We immediately drop
        // the returned `File` (closing the write handle) and re-open the same
        // path with `BACKUP_SEMANTICS` via `Self::create`.
        drop(create_long_path_file(path)?);
        Self::create(path)
    }
}

impl Write for BackupStreamWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let mut bytes_written: u32 = 0;
        unsafe {
            BackupWrite(
                self.handle,
                buf,
                &mut bytes_written,
                false, // bAbort
                // bProcessSecurity MUST be TRUE for the kernel to apply the
                // `BACKUP_SECURITY_DATA` stream (owner + DACL + SACL) to the
                // file. With FALSE the security stream is parsed but its ACL is
                // DISCARDED — the file keeps the host directory's inherited ACL,
                // which omits the BUILTIN\Users / ALL APPLICATION PACKAGES read
                // grants that in-guest services running as LocalService (e.g.
                // `mpssvc`) need to load their DLLs. Pairs with the
                // WRITE_DAC/WRITE_OWNER/ACCESS_SYSTEM_SECURITY handle rights in
                // `create` — both are required, neither alone applies the SD.
                true, // bProcessSecurity
                &mut self.context,
            )
        }
        .map_err(|e| io::Error::other(format!("BackupWrite: {e}")))?;
        Ok(bytes_written as usize)
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl Drop for BackupStreamWriter {
    fn drop(&mut self) {
        // Tell BackupWrite to release context state with a final call where
        // `babort = true`. windows-rs 0.62 takes `&[u8]` directly (no Option),
        // so we pass an empty slice. Ignore errors; best-effort cleanup.
        let mut bytes_written: u32 = 0;
        unsafe {
            let _ = BackupWrite(
                self.handle,
                &[],
                &mut bytes_written,
                true,
                false,
                &mut self.context,
            );
            let _ = CloseHandle(self.handle);
        }
    }
}

/// Mirror of [`BackupStreamWriter`] for reading existing wclayer content
/// back out (used by `HcsExportLayer` paths).
///
/// The reader emits `BackupRead`-framed bytes: `WIN32_STREAM_ID` headers
/// followed by their payloads. Call [`Read::read`] repeatedly until it
/// returns `Ok(0)` to drain the full stream.
pub struct BackupStreamReader {
    handle: HANDLE,
    context: *mut core::ffi::c_void,
}

// See safety note on `BackupStreamWriter`'s Send impl.
unsafe impl Send for BackupStreamReader {}

impl BackupStreamReader {
    /// Open the target file for backup reading.
    ///
    /// # Errors
    ///
    /// Returns the OS error from `CreateFileW`.
    pub fn open(path: &Path) -> io::Result<Self> {
        // `to_extended_wide` adds the `\\?\` prefix so we can open paths
        // longer than MAX_PATH (260 chars) — see the helper's doc comment.
        let wide = to_extended_wide(path)?;
        let handle = unsafe {
            CreateFileW(
                PCWSTR::from_raw(wide.as_ptr()),
                GENERIC_READ.0,
                FILE_SHARE_READ,
                None,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS,
                None,
            )
        }
        .map_err(|e| io::Error::other(format!("CreateFileW: {e}")))?;
        Ok(Self {
            handle,
            context: std::ptr::null_mut(),
        })
    }
}

impl Read for BackupStreamReader {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let mut bytes_read: u32 = 0;
        unsafe {
            BackupRead(
                self.handle,
                buf,
                &mut bytes_read,
                false,
                false,
                &mut self.context,
            )
        }
        .map_err(|e| io::Error::other(format!("BackupRead: {e}")))?;
        Ok(bytes_read as usize)
    }
}

impl Drop for BackupStreamReader {
    fn drop(&mut self) {
        // Final abort call to release internal context state. See the note on
        // BackupStreamWriter::drop for the empty-slice rationale.
        let mut bytes_read: u32 = 0;
        let mut scratch: [u8; 1] = [0];
        unsafe {
            let _ = BackupRead(
                self.handle,
                &mut scratch[..0],
                &mut bytes_read,
                true,
                false,
                &mut self.context,
            );
            let _ = CloseHandle(self.handle);
        }
    }
}

// ---------------------------------------------------------------------------
// Tests for pure helpers — no Win32 calls, safe to run on any Windows host
// (and they only operate on string-level path transformations, so they don't
// need real files to exist).
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    /// Helper: render a wide buffer back to `String`, stripping the trailing
    /// null terminator that `to_extended_wide` always appends.
    fn render(wide: &[u16]) -> String {
        let nul_pos = wide
            .iter()
            .position(|&c| c == 0)
            .expect("to_extended_wide must null-terminate");
        String::from_utf16(&wide[..nul_pos]).expect("valid UTF-16")
    }

    #[test]
    fn extended_wide_prefixes_local_absolute_path() {
        // Use a path that almost certainly does not exist so we exercise the
        // `std::path::absolute` fallback rather than `canonicalize`.
        let p = Path::new(r"C:\foo\bar\does-not-exist-zlayer-test");
        let wide = to_extended_wide(p).expect("absolutize ok");
        let s = render(&wide);
        assert_eq!(s, r"\\?\C:\foo\bar\does-not-exist-zlayer-test");
        assert_eq!(*wide.last().unwrap(), 0u16, "must be null-terminated");
    }

    #[test]
    fn extended_wide_converts_unc_path_to_unc_verbatim() {
        let p = Path::new(r"\\server\share\baz\zlayer-test");
        let wide = to_extended_wide(p).expect("absolutize ok");
        let s = render(&wide);
        assert_eq!(s, r"\\?\UNC\server\share\baz\zlayer-test");
    }

    #[test]
    fn extended_wide_passes_through_already_verbatim_prefix() {
        let p = Path::new(r"\\?\C:\already\zlayer-test");
        let wide = to_extended_wide(p).expect("absolutize ok");
        let s = render(&wide);
        assert_eq!(s, r"\\?\C:\already\zlayer-test");
    }

    #[test]
    fn extended_wide_passes_through_device_prefix() {
        let p = Path::new(r"\\.\PhysicalDrive0");
        let wide = to_extended_wide(p).expect("absolutize ok");
        let s = render(&wide);
        assert_eq!(s, r"\\.\PhysicalDrive0");
    }
}