1 // Copyright 2018 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 mod read_dir;
6
7 use std::cmp::min;
8 use std::collections::btree_map;
9 use std::collections::BTreeMap;
10 use std::ffi::CStr;
11 use std::ffi::CString;
12 use std::fs::File;
13 use std::io;
14 use std::io::Cursor;
15 use std::io::Read;
16 use std::io::Write;
17 use std::mem;
18 use std::mem::MaybeUninit;
19 use std::ops::Deref;
20 use std::os::unix::ffi::OsStrExt;
21 use std::os::unix::fs::FileExt;
22 use std::os::unix::io::AsRawFd;
23 use std::os::unix::io::FromRawFd;
24 use std::os::unix::io::RawFd;
25 use std::path::Path;
26 use std::str::FromStr;
27
28 use read_dir::read_dir;
29 use serde::Deserialize;
30 use serde::Serialize;
31
32 use crate::protocol::*;
33 use crate::syscall;
34
35 // Tlopen and Tlcreate flags. Taken from "include/net/9p/9p.h" in the linux tree.
36 const P9_RDONLY: u32 = 0o00000000;
37 const P9_WRONLY: u32 = 0o00000001;
38 const P9_RDWR: u32 = 0o00000002;
39 const P9_NOACCESS: u32 = 0o00000003;
40 const P9_CREATE: u32 = 0o00000100;
41 const P9_EXCL: u32 = 0o00000200;
42 const P9_NOCTTY: u32 = 0o00000400;
43 const P9_TRUNC: u32 = 0o00001000;
44 const P9_APPEND: u32 = 0o00002000;
45 const P9_NONBLOCK: u32 = 0o00004000;
46 const P9_DSYNC: u32 = 0o00010000;
47 const P9_FASYNC: u32 = 0o00020000;
48 const P9_DIRECT: u32 = 0o00040000;
49 const P9_LARGEFILE: u32 = 0o00100000;
50 const P9_DIRECTORY: u32 = 0o00200000;
51 const P9_NOFOLLOW: u32 = 0o00400000;
52 const P9_NOATIME: u32 = 0o01000000;
53 const _P9_CLOEXEC: u32 = 0o02000000;
54 const P9_SYNC: u32 = 0o04000000;
55
56 // Mapping from 9P flags to libc flags.
57 const MAPPED_FLAGS: [(u32, i32); 16] = [
58 (P9_WRONLY, libc::O_WRONLY),
59 (P9_RDWR, libc::O_RDWR),
60 (P9_CREATE, libc::O_CREAT),
61 (P9_EXCL, libc::O_EXCL),
62 (P9_NOCTTY, libc::O_NOCTTY),
63 (P9_TRUNC, libc::O_TRUNC),
64 (P9_APPEND, libc::O_APPEND),
65 (P9_NONBLOCK, libc::O_NONBLOCK),
66 (P9_DSYNC, libc::O_DSYNC),
67 (P9_FASYNC, 0), // Unsupported
68 (P9_DIRECT, libc::O_DIRECT),
69 (P9_LARGEFILE, libc::O_LARGEFILE),
70 (P9_DIRECTORY, libc::O_DIRECTORY),
71 (P9_NOFOLLOW, libc::O_NOFOLLOW),
72 (P9_NOATIME, libc::O_NOATIME),
73 (P9_SYNC, libc::O_SYNC),
74 ];
75
76 // 9P Qid types. Taken from "include/net/9p/9p.h" in the linux tree.
77 const P9_QTDIR: u8 = 0x80;
78 const _P9_QTAPPEND: u8 = 0x40;
79 const _P9_QTEXCL: u8 = 0x20;
80 const _P9_QTMOUNT: u8 = 0x10;
81 const _P9_QTAUTH: u8 = 0x08;
82 const _P9_QTTMP: u8 = 0x04;
83 const P9_QTSYMLINK: u8 = 0x02;
84 const _P9_QTLINK: u8 = 0x01;
85 const P9_QTFILE: u8 = 0x00;
86
87 // Bitmask values for the getattr request.
88 const _P9_GETATTR_MODE: u64 = 0x00000001;
89 const _P9_GETATTR_NLINK: u64 = 0x00000002;
90 const _P9_GETATTR_UID: u64 = 0x00000004;
91 const _P9_GETATTR_GID: u64 = 0x00000008;
92 const _P9_GETATTR_RDEV: u64 = 0x00000010;
93 const _P9_GETATTR_ATIME: u64 = 0x00000020;
94 const _P9_GETATTR_MTIME: u64 = 0x00000040;
95 const _P9_GETATTR_CTIME: u64 = 0x00000080;
96 const _P9_GETATTR_INO: u64 = 0x00000100;
97 const _P9_GETATTR_SIZE: u64 = 0x00000200;
98 const _P9_GETATTR_BLOCKS: u64 = 0x00000400;
99
100 const _P9_GETATTR_BTIME: u64 = 0x00000800;
101 const _P9_GETATTR_GEN: u64 = 0x00001000;
102 const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000;
103
104 const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */
105 const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */
106
107 // Bitmask values for the setattr request.
108 const P9_SETATTR_MODE: u32 = 0x00000001;
109 const P9_SETATTR_UID: u32 = 0x00000002;
110 const P9_SETATTR_GID: u32 = 0x00000004;
111 const P9_SETATTR_SIZE: u32 = 0x00000008;
112 const P9_SETATTR_ATIME: u32 = 0x00000010;
113 const P9_SETATTR_MTIME: u32 = 0x00000020;
114 const P9_SETATTR_CTIME: u32 = 0x00000040;
115 const P9_SETATTR_ATIME_SET: u32 = 0x00000080;
116 const P9_SETATTR_MTIME_SET: u32 = 0x00000100;
117
118 // 9p lock constants. Taken from "include/net/9p/9p.h" in the linux kernel.
119 const _P9_LOCK_TYPE_RDLCK: u8 = 0;
120 const _P9_LOCK_TYPE_WRLCK: u8 = 1;
121 const P9_LOCK_TYPE_UNLCK: u8 = 2;
122 const _P9_LOCK_FLAGS_BLOCK: u8 = 1;
123 const _P9_LOCK_FLAGS_RECLAIM: u8 = 2;
124 const P9_LOCK_SUCCESS: u8 = 0;
125 const _P9_LOCK_BLOCKED: u8 = 1;
126 const _P9_LOCK_ERROR: u8 = 2;
127 const _P9_LOCK_GRACE: u8 = 3;
128
129 // Minimum and maximum message size that we'll expect from the client.
130 const MIN_MESSAGE_SIZE: u32 = 256;
131 const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32;
132
133 #[derive(PartialEq, Eq)]
134 enum FileType {
135 Regular,
136 Directory,
137 Other,
138 }
139
140 impl From<libc::mode_t> for FileType {
from(mode: libc::mode_t) -> Self141 fn from(mode: libc::mode_t) -> Self {
142 match mode & libc::S_IFMT {
143 libc::S_IFREG => FileType::Regular,
144 libc::S_IFDIR => FileType::Directory,
145 _ => FileType::Other,
146 }
147 }
148 }
149
150 // Represents state that the server is holding on behalf of a client. Fids are somewhat like file
151 // descriptors but are not restricted to open files and directories. Fids are identified by a unique
152 // 32-bit number chosen by the client. Most messages sent by clients include a fid on which to
153 // operate. The fid in a Tattach message represents the root of the file system tree that the client
154 // is allowed to access. A client can create more fids by walking the directory tree from that fid.
155 struct Fid {
156 path: File,
157 file: Option<File>,
158 filetype: FileType,
159 }
160
161 impl From<libc::stat64> for Qid {
from(st: libc::stat64) -> Qid162 fn from(st: libc::stat64) -> Qid {
163 let ty = match st.st_mode & libc::S_IFMT {
164 libc::S_IFDIR => P9_QTDIR,
165 libc::S_IFREG => P9_QTFILE,
166 libc::S_IFLNK => P9_QTSYMLINK,
167 _ => 0,
168 };
169
170 Qid {
171 ty,
172 // TODO: deal with the 2038 problem before 2038
173 version: st.st_mtime as u32,
174 path: st.st_ino,
175 }
176 }
177 }
178
statat(d: &File, name: &CStr, flags: libc::c_int) -> io::Result<libc::stat64>179 fn statat(d: &File, name: &CStr, flags: libc::c_int) -> io::Result<libc::stat64> {
180 let mut st = MaybeUninit::<libc::stat64>::zeroed();
181
182 // Safe because the kernel will only write data in `st` and we check the return
183 // value.
184 let res = unsafe {
185 libc::fstatat64(
186 d.as_raw_fd(),
187 name.as_ptr(),
188 st.as_mut_ptr(),
189 flags | libc::AT_SYMLINK_NOFOLLOW,
190 )
191 };
192 if res >= 0 {
193 // Safe because the kernel guarantees that the struct is now fully initialized.
194 Ok(unsafe { st.assume_init() })
195 } else {
196 Err(io::Error::last_os_error())
197 }
198 }
199
stat(f: &File) -> io::Result<libc::stat64>200 fn stat(f: &File) -> io::Result<libc::stat64> {
201 // Safe because this is a constant value and a valid C string.
202 let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
203
204 statat(f, pathname, libc::AT_EMPTY_PATH)
205 }
206
string_to_cstring(s: String) -> io::Result<CString>207 fn string_to_cstring(s: String) -> io::Result<CString> {
208 CString::new(s).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))
209 }
210
error_to_rmessage(err: io::Error) -> Rmessage211 fn error_to_rmessage(err: io::Error) -> Rmessage {
212 let errno = if let Some(errno) = err.raw_os_error() {
213 errno
214 } else {
215 // Make a best-effort guess based on the kind.
216 match err.kind() {
217 io::ErrorKind::NotFound => libc::ENOENT,
218 io::ErrorKind::PermissionDenied => libc::EPERM,
219 io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED,
220 io::ErrorKind::ConnectionReset => libc::ECONNRESET,
221 io::ErrorKind::ConnectionAborted => libc::ECONNABORTED,
222 io::ErrorKind::NotConnected => libc::ENOTCONN,
223 io::ErrorKind::AddrInUse => libc::EADDRINUSE,
224 io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL,
225 io::ErrorKind::BrokenPipe => libc::EPIPE,
226 io::ErrorKind::AlreadyExists => libc::EEXIST,
227 io::ErrorKind::WouldBlock => libc::EWOULDBLOCK,
228 io::ErrorKind::InvalidInput => libc::EINVAL,
229 io::ErrorKind::InvalidData => libc::EINVAL,
230 io::ErrorKind::TimedOut => libc::ETIMEDOUT,
231 io::ErrorKind::WriteZero => libc::EIO,
232 io::ErrorKind::Interrupted => libc::EINTR,
233 io::ErrorKind::Other => libc::EIO,
234 io::ErrorKind::UnexpectedEof => libc::EIO,
235 _ => libc::EIO,
236 }
237 };
238
239 Rmessage::Lerror(Rlerror {
240 ecode: errno as u32,
241 })
242 }
243
244 // Sigh.. Cow requires the underlying type to implement Clone.
245 enum MaybeOwned<'b, T> {
246 Borrowed(&'b T),
247 Owned(T),
248 }
249
250 impl<'a, T> Deref for MaybeOwned<'a, T> {
251 type Target = T;
deref(&self) -> &Self::Target252 fn deref(&self) -> &Self::Target {
253 use MaybeOwned::*;
254 match *self {
255 Borrowed(borrowed) => borrowed,
256 Owned(ref owned) => owned,
257 }
258 }
259 }
260
261 impl<'a, T> AsRef<T> for MaybeOwned<'a, T> {
as_ref(&self) -> &T262 fn as_ref(&self) -> &T {
263 use MaybeOwned::*;
264 match self {
265 Borrowed(borrowed) => borrowed,
266 Owned(ref owned) => owned,
267 }
268 }
269 }
270
ebadf() -> io::Error271 fn ebadf() -> io::Error {
272 io::Error::from_raw_os_error(libc::EBADF)
273 }
274
275 pub type ServerIdMap<T> = BTreeMap<T, T>;
276 pub type ServerUidMap = ServerIdMap<libc::uid_t>;
277 pub type ServerGidMap = ServerIdMap<libc::gid_t>;
278
map_id_from_host<T: Clone + Ord>(map: &ServerIdMap<T>, id: T) -> T279 fn map_id_from_host<T: Clone + Ord>(map: &ServerIdMap<T>, id: T) -> T {
280 map.get(&id).map_or(id.clone(), |v| v.clone())
281 }
282
283 // Performs an ascii case insensitive lookup and returns an O_PATH fd for the entry, if found.
ascii_casefold_lookup(proc: &File, parent: &File, name: &[u8]) -> io::Result<File>284 fn ascii_casefold_lookup(proc: &File, parent: &File, name: &[u8]) -> io::Result<File> {
285 let mut dir = open_fid(proc, parent, P9_DIRECTORY)?;
286 let mut dirents = read_dir(&mut dir, 0)?;
287
288 while let Some(entry) = dirents.next().transpose()? {
289 if name.eq_ignore_ascii_case(entry.name.to_bytes()) {
290 return lookup(parent, entry.name);
291 }
292 }
293
294 Err(io::Error::from_raw_os_error(libc::ENOENT))
295 }
296
lookup(parent: &File, name: &CStr) -> io::Result<File>297 fn lookup(parent: &File, name: &CStr) -> io::Result<File> {
298 // Safe because this doesn't modify any memory and we check the return value.
299 let fd = syscall!(unsafe {
300 libc::openat64(
301 parent.as_raw_fd(),
302 name.as_ptr(),
303 libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
304 )
305 })?;
306
307 // Safe because we just opened this fd.
308 Ok(unsafe { File::from_raw_fd(fd) })
309 }
310
do_walk( proc: &File, wnames: Vec<String>, start: &File, ascii_casefold: bool, mds: &mut Vec<libc::stat64>, ) -> io::Result<File>311 fn do_walk(
312 proc: &File,
313 wnames: Vec<String>,
314 start: &File,
315 ascii_casefold: bool,
316 mds: &mut Vec<libc::stat64>,
317 ) -> io::Result<File> {
318 let mut current = MaybeOwned::Borrowed(start);
319
320 for wname in wnames {
321 let name = string_to_cstring(wname)?;
322 current = MaybeOwned::Owned(lookup(current.as_ref(), &name).or_else(|e| {
323 if ascii_casefold {
324 if let Some(libc::ENOENT) = e.raw_os_error() {
325 return ascii_casefold_lookup(proc, current.as_ref(), name.to_bytes());
326 }
327 }
328
329 Err(e)
330 })?);
331 mds.push(stat(¤t)?);
332 }
333
334 match current {
335 MaybeOwned::Owned(owned) => Ok(owned),
336 MaybeOwned::Borrowed(borrowed) => borrowed.try_clone(),
337 }
338 }
339
open_fid(proc: &File, path: &File, p9_flags: u32) -> io::Result<File>340 fn open_fid(proc: &File, path: &File, p9_flags: u32) -> io::Result<File> {
341 let pathname = string_to_cstring(format!("self/fd/{}", path.as_raw_fd()))?;
342
343 // We always open files with O_CLOEXEC.
344 let mut flags: i32 = libc::O_CLOEXEC;
345 for &(p9f, of) in &MAPPED_FLAGS {
346 if (p9_flags & p9f) != 0 {
347 flags |= of;
348 }
349 }
350
351 if p9_flags & P9_NOACCESS == P9_RDONLY {
352 flags |= libc::O_RDONLY;
353 }
354
355 // Safe because this doesn't modify any memory and we check the return value. We need to
356 // clear the O_NOFOLLOW flag because we want to follow the proc symlink.
357 let fd = syscall!(unsafe {
358 libc::openat64(
359 proc.as_raw_fd(),
360 pathname.as_ptr(),
361 flags & !libc::O_NOFOLLOW,
362 )
363 })?;
364
365 // Safe because we just opened this fd and we know it is valid.
366 Ok(unsafe { File::from_raw_fd(fd) })
367 }
368
369 #[derive(Clone, Serialize, Deserialize)]
370 pub struct Config {
371 pub root: Box<Path>,
372 pub msize: u32,
373
374 pub uid_map: ServerUidMap,
375 pub gid_map: ServerGidMap,
376
377 pub ascii_casefold: bool,
378 }
379
380 impl FromStr for Config {
381 type Err = &'static str;
382
from_str(params: &str) -> Result<Self, Self::Err>383 fn from_str(params: &str) -> Result<Self, Self::Err> {
384 let mut cfg = Self::default();
385 if params.is_empty() {
386 return Ok(cfg);
387 }
388 for opt in params.split(':') {
389 let mut o = opt.splitn(2, '=');
390 let kind = o.next().ok_or("`cfg` options mut not be empty")?;
391 let value = o
392 .next()
393 .ok_or("`cfg` options must be of the form `kind=value`")?;
394 match kind {
395 "ascii_casefold" => {
396 let ascii_casefold = value
397 .parse()
398 .map_err(|_| "`ascii_casefold` must be a boolean")?;
399 cfg.ascii_casefold = ascii_casefold;
400 }
401 _ => return Err("unrecognized option for p9 config"),
402 }
403 }
404 Ok(cfg)
405 }
406 }
407
408 impl Default for Config {
default() -> Config409 fn default() -> Config {
410 Config {
411 root: Path::new("/").into(),
412 msize: MAX_MESSAGE_SIZE,
413 uid_map: Default::default(),
414 gid_map: Default::default(),
415 ascii_casefold: false,
416 }
417 }
418 }
419 pub struct Server {
420 fids: BTreeMap<u32, Fid>,
421 proc: File,
422 cfg: Config,
423 }
424
425 impl Server {
new<P: Into<Box<Path>>>( root: P, uid_map: ServerUidMap, gid_map: ServerGidMap, ) -> io::Result<Server>426 pub fn new<P: Into<Box<Path>>>(
427 root: P,
428 uid_map: ServerUidMap,
429 gid_map: ServerGidMap,
430 ) -> io::Result<Server> {
431 Server::with_config(Config {
432 root: root.into(),
433 msize: MAX_MESSAGE_SIZE,
434 uid_map,
435 gid_map,
436 ascii_casefold: false,
437 })
438 }
439
with_config(cfg: Config) -> io::Result<Server>440 pub fn with_config(cfg: Config) -> io::Result<Server> {
441 // Safe because this is a valid c-string.
442 let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc\0") };
443
444 // Safe because this doesn't modify any memory and we check the return value.
445 let fd = syscall!(unsafe {
446 libc::openat64(
447 libc::AT_FDCWD,
448 proc_cstr.as_ptr(),
449 libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
450 )
451 })?;
452
453 // Safe because we just opened this fd and we know it is valid.
454 let proc = unsafe { File::from_raw_fd(fd) };
455 Ok(Server {
456 fids: BTreeMap::new(),
457 proc,
458 cfg,
459 })
460 }
461
keep_fds(&self) -> Vec<RawFd>462 pub fn keep_fds(&self) -> Vec<RawFd> {
463 vec![self.proc.as_raw_fd()]
464 }
465
handle_message<R: Read, W: Write>( &mut self, reader: &mut R, writer: &mut W, ) -> io::Result<()>466 pub fn handle_message<R: Read, W: Write>(
467 &mut self,
468 reader: &mut R,
469 writer: &mut W,
470 ) -> io::Result<()> {
471 let Tframe { tag, msg } = WireFormat::decode(&mut reader.take(self.cfg.msize as u64))?;
472
473 let rmsg = match msg {
474 Ok(Tmessage::Version(ref version)) => self.version(version).map(Rmessage::Version),
475 Ok(Tmessage::Flush(ref flush)) => self.flush(flush).and(Ok(Rmessage::Flush)),
476 Ok(Tmessage::Walk(walk)) => self.walk(walk).map(Rmessage::Walk),
477 Ok(Tmessage::Read(ref read)) => self.read(read).map(Rmessage::Read),
478 Ok(Tmessage::Write(ref write)) => self.write(write).map(Rmessage::Write),
479 Ok(Tmessage::Clunk(ref clunk)) => self.clunk(clunk).and(Ok(Rmessage::Clunk)),
480 Ok(Tmessage::Remove(ref remove)) => self.remove(remove).and(Ok(Rmessage::Remove)),
481 Ok(Tmessage::Attach(ref attach)) => self.attach(attach).map(Rmessage::Attach),
482 Ok(Tmessage::Auth(ref auth)) => self.auth(auth).map(Rmessage::Auth),
483 Ok(Tmessage::Statfs(ref statfs)) => self.statfs(statfs).map(Rmessage::Statfs),
484 Ok(Tmessage::Lopen(ref lopen)) => self.lopen(lopen).map(Rmessage::Lopen),
485 Ok(Tmessage::Lcreate(lcreate)) => self.lcreate(lcreate).map(Rmessage::Lcreate),
486 Ok(Tmessage::Symlink(ref symlink)) => self.symlink(symlink).map(Rmessage::Symlink),
487 Ok(Tmessage::Mknod(ref mknod)) => self.mknod(mknod).map(Rmessage::Mknod),
488 Ok(Tmessage::Rename(ref rename)) => self.rename(rename).and(Ok(Rmessage::Rename)),
489 Ok(Tmessage::Readlink(ref readlink)) => self.readlink(readlink).map(Rmessage::Readlink),
490 Ok(Tmessage::GetAttr(ref get_attr)) => self.get_attr(get_attr).map(Rmessage::GetAttr),
491 Ok(Tmessage::SetAttr(ref set_attr)) => {
492 self.set_attr(set_attr).and(Ok(Rmessage::SetAttr))
493 }
494 Ok(Tmessage::XattrWalk(ref xattr_walk)) => {
495 self.xattr_walk(xattr_walk).map(Rmessage::XattrWalk)
496 }
497 Ok(Tmessage::XattrCreate(ref xattr_create)) => self
498 .xattr_create(xattr_create)
499 .and(Ok(Rmessage::XattrCreate)),
500 Ok(Tmessage::Readdir(ref readdir)) => self.readdir(readdir).map(Rmessage::Readdir),
501 Ok(Tmessage::Fsync(ref fsync)) => self.fsync(fsync).and(Ok(Rmessage::Fsync)),
502 Ok(Tmessage::Lock(ref lock)) => self.lock(lock).map(Rmessage::Lock),
503 Ok(Tmessage::GetLock(ref get_lock)) => self.get_lock(get_lock).map(Rmessage::GetLock),
504 Ok(Tmessage::Link(link)) => self.link(link).and(Ok(Rmessage::Link)),
505 Ok(Tmessage::Mkdir(mkdir)) => self.mkdir(mkdir).map(Rmessage::Mkdir),
506 Ok(Tmessage::RenameAt(rename_at)) => {
507 self.rename_at(rename_at).and(Ok(Rmessage::RenameAt))
508 }
509 Ok(Tmessage::UnlinkAt(unlink_at)) => {
510 self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt))
511 }
512 Err(e) => {
513 // The header was successfully decoded, but the body failed to decode - send an
514 // error response for this tag.
515 let error = format!("Tframe message decode failed: {}", e);
516 Err(io::Error::new(io::ErrorKind::InvalidData, error))
517 }
518 };
519
520 // Errors while handling requests are never fatal.
521 let response = Rframe {
522 tag,
523 msg: rmsg.unwrap_or_else(error_to_rmessage),
524 };
525
526 response.encode(writer)?;
527 writer.flush()
528 }
529
auth(&mut self, _auth: &Tauth) -> io::Result<Rauth>530 fn auth(&mut self, _auth: &Tauth) -> io::Result<Rauth> {
531 // Returning an error for the auth message means that the server does not require
532 // authentication.
533 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
534 }
535
attach(&mut self, attach: &Tattach) -> io::Result<Rattach>536 fn attach(&mut self, attach: &Tattach) -> io::Result<Rattach> {
537 // TODO: Check attach parameters
538 match self.fids.entry(attach.fid) {
539 btree_map::Entry::Vacant(entry) => {
540 let root = CString::new(self.cfg.root.as_os_str().as_bytes())
541 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
542
543 // Safe because this doesn't modify any memory and we check the return value.
544 let fd = syscall!(unsafe {
545 libc::openat64(
546 libc::AT_FDCWD,
547 root.as_ptr(),
548 libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
549 )
550 })?;
551
552 let root_path = unsafe { File::from_raw_fd(fd) };
553 let st = stat(&root_path)?;
554
555 let fid = Fid {
556 // Safe because we just opened this fd.
557 path: root_path,
558 file: None,
559 filetype: st.st_mode.into(),
560 };
561 let response = Rattach { qid: st.into() };
562 entry.insert(fid);
563 Ok(response)
564 }
565 btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
566 }
567 }
568
version(&mut self, version: &Tversion) -> io::Result<Rversion>569 fn version(&mut self, version: &Tversion) -> io::Result<Rversion> {
570 if version.msize < MIN_MESSAGE_SIZE {
571 return Err(io::Error::from_raw_os_error(libc::EINVAL));
572 }
573
574 // A Tversion request clunks all open fids and terminates any pending I/O.
575 self.fids.clear();
576 self.cfg.msize = min(self.cfg.msize, version.msize);
577
578 Ok(Rversion {
579 msize: self.cfg.msize,
580 version: if version.version == "9P2000.L" {
581 String::from("9P2000.L")
582 } else {
583 String::from("unknown")
584 },
585 })
586 }
587
588 #[allow(clippy::unnecessary_wraps)]
flush(&mut self, _flush: &Tflush) -> io::Result<()>589 fn flush(&mut self, _flush: &Tflush) -> io::Result<()> {
590 // TODO: Since everything is synchronous we can't actually flush requests.
591 Ok(())
592 }
593
walk(&mut self, walk: Twalk) -> io::Result<Rwalk>594 fn walk(&mut self, walk: Twalk) -> io::Result<Rwalk> {
595 // `newfid` must not currently be in use unless it is the same as `fid`.
596 if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
597 return Err(io::Error::from_raw_os_error(libc::EBADF));
598 }
599
600 // We need to walk the tree. First get the starting path.
601 let start = &self.fids.get(&walk.fid).ok_or_else(ebadf)?.path;
602
603 // Now walk the tree and break on the first error, if any.
604 let expected_len = walk.wnames.len();
605 let mut mds = Vec::with_capacity(expected_len);
606 match do_walk(
607 &self.proc,
608 walk.wnames,
609 start,
610 self.cfg.ascii_casefold,
611 &mut mds,
612 ) {
613 Ok(end) => {
614 // Store the new fid if the full walk succeeded.
615 if mds.len() == expected_len {
616 let st = mds.last().copied().map(Ok).unwrap_or_else(|| stat(&end))?;
617 self.fids.insert(
618 walk.newfid,
619 Fid {
620 path: end,
621 file: None,
622 filetype: st.st_mode.into(),
623 },
624 );
625 }
626 }
627 Err(e) => {
628 // Only return an error if it occurred on the first component.
629 if mds.is_empty() {
630 return Err(e);
631 }
632 }
633 }
634
635 Ok(Rwalk {
636 wqids: mds.into_iter().map(Qid::from).collect(),
637 })
638 }
639
read(&mut self, read: &Tread) -> io::Result<Rread>640 fn read(&mut self, read: &Tread) -> io::Result<Rread> {
641 // Thankfully, `read` cannot be used to read directories in 9P2000.L.
642 let file = self
643 .fids
644 .get_mut(&read.fid)
645 .and_then(|fid| fid.file.as_mut())
646 .ok_or_else(ebadf)?;
647
648 // Use an empty Rread struct to figure out the overhead of the header.
649 let header_size = Rframe {
650 tag: 0,
651 msg: Rmessage::Read(Rread {
652 data: Data(Vec::new()),
653 }),
654 }
655 .byte_size();
656
657 let capacity = min(self.cfg.msize - header_size, read.count);
658 let mut buf = Data(vec![0u8; capacity as usize]);
659
660 let count = file.read_at(&mut buf, read.offset)?;
661 buf.truncate(count);
662
663 Ok(Rread { data: buf })
664 }
665
write(&mut self, write: &Twrite) -> io::Result<Rwrite>666 fn write(&mut self, write: &Twrite) -> io::Result<Rwrite> {
667 let file = self
668 .fids
669 .get_mut(&write.fid)
670 .and_then(|fid| fid.file.as_mut())
671 .ok_or_else(ebadf)?;
672
673 let count = file.write_at(&write.data, write.offset)?;
674 Ok(Rwrite {
675 count: count as u32,
676 })
677 }
678
clunk(&mut self, clunk: &Tclunk) -> io::Result<()>679 fn clunk(&mut self, clunk: &Tclunk) -> io::Result<()> {
680 match self.fids.entry(clunk.fid) {
681 btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
682 btree_map::Entry::Occupied(entry) => {
683 entry.remove();
684 Ok(())
685 }
686 }
687 }
688
remove(&mut self, _remove: &Tremove) -> io::Result<()>689 fn remove(&mut self, _remove: &Tremove) -> io::Result<()> {
690 // Since a file could be linked into multiple locations, there is no way to know exactly
691 // which path we are supposed to unlink. Linux uses unlink_at anyway, so we can just return
692 // an error here.
693 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
694 }
695
statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs>696 fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> {
697 let fid = self.fids.get(&statfs.fid).ok_or_else(ebadf)?;
698 let mut buf = MaybeUninit::zeroed();
699
700 // Safe because this will only modify `out` and we check the return value.
701 syscall!(unsafe { libc::fstatfs64(fid.path.as_raw_fd(), buf.as_mut_ptr()) })?;
702
703 // Safe because this only has integer types and any value is valid.
704 let out = unsafe { buf.assume_init() };
705 Ok(Rstatfs {
706 ty: out.f_type as u32,
707 bsize: out.f_bsize as u32,
708 blocks: out.f_blocks,
709 bfree: out.f_bfree,
710 bavail: out.f_bavail,
711 files: out.f_files,
712 ffree: out.f_ffree,
713 // Safe because the fsid has only integer fields and the compiler will verify that is
714 // the same width as the `fsid` field in Rstatfs.
715 fsid: unsafe { mem::transmute(out.f_fsid) },
716 namelen: out.f_namelen as u32,
717 })
718 }
719
lopen(&mut self, lopen: &Tlopen) -> io::Result<Rlopen>720 fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rlopen> {
721 let fid = self.fids.get_mut(&lopen.fid).ok_or_else(ebadf)?;
722
723 let file = open_fid(&self.proc, &fid.path, lopen.flags)?;
724 let st = stat(&file)?;
725
726 fid.file = Some(file);
727 let iounit = st.st_blksize as u32;
728 Ok(Rlopen {
729 qid: st.into(),
730 iounit,
731 })
732 }
733
lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate>734 fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate> {
735 let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?;
736
737 if fid.filetype != FileType::Directory {
738 return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
739 }
740
741 let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL;
742 for &(p9f, of) in &MAPPED_FLAGS {
743 if (lcreate.flags & p9f) != 0 {
744 flags |= of;
745 }
746 }
747 if lcreate.flags & P9_NOACCESS == P9_RDONLY {
748 flags |= libc::O_RDONLY;
749 }
750
751 let name = string_to_cstring(lcreate.name)?;
752
753 // Safe because this doesn't modify any memory and we check the return value.
754 let fd = syscall!(unsafe {
755 libc::openat64(fid.path.as_raw_fd(), name.as_ptr(), flags, lcreate.mode)
756 })?;
757
758 // Safe because we just opened this fd and we know it is valid.
759 let file = unsafe { File::from_raw_fd(fd) };
760 let st = stat(&file)?;
761 let iounit = st.st_blksize as u32;
762
763 fid.file = Some(file);
764 fid.filetype = FileType::Regular;
765
766 // This fid now refers to the newly created file so we need to update the O_PATH fd for it
767 // as well.
768 fid.path = lookup(&fid.path, &name)?;
769
770 Ok(Rlcreate {
771 qid: st.into(),
772 iounit,
773 })
774 }
775
symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink>776 fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> {
777 // symlinks are not allowed.
778 Err(io::Error::from_raw_os_error(libc::EACCES))
779 }
780
mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod>781 fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> {
782 // No nodes either.
783 Err(io::Error::from_raw_os_error(libc::EACCES))
784 }
785
rename(&mut self, _rename: &Trename) -> io::Result<()>786 fn rename(&mut self, _rename: &Trename) -> io::Result<()> {
787 // We cannot support this as an inode may be linked into multiple directories but we don't
788 // know which one the client wants us to rename. Linux uses rename_at anyway, so we don't
789 // need to worry about this.
790 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
791 }
792
readlink(&mut self, readlink: &Treadlink) -> io::Result<Rreadlink>793 fn readlink(&mut self, readlink: &Treadlink) -> io::Result<Rreadlink> {
794 let fid = self.fids.get(&readlink.fid).ok_or_else(ebadf)?;
795
796 let mut link = vec![0; libc::PATH_MAX as usize];
797
798 // Safe because this will only modify `link` and we check the return value.
799 let len = syscall!(unsafe {
800 libc::readlinkat(
801 fid.path.as_raw_fd(),
802 [0].as_ptr(),
803 link.as_mut_ptr() as *mut libc::c_char,
804 link.len(),
805 )
806 })? as usize;
807 link.truncate(len);
808 let target = String::from_utf8(link)
809 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
810 Ok(Rreadlink { target })
811 }
812
get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr>813 fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> {
814 let fid = self.fids.get_mut(&get_attr.fid).ok_or_else(ebadf)?;
815
816 let st = stat(&fid.path)?;
817
818 Ok(Rgetattr {
819 valid: P9_GETATTR_BASIC,
820 qid: st.into(),
821 mode: st.st_mode,
822 uid: map_id_from_host(&self.cfg.uid_map, st.st_uid),
823 gid: map_id_from_host(&self.cfg.gid_map, st.st_gid),
824 nlink: st.st_nlink as u64,
825 rdev: st.st_rdev,
826 size: st.st_size as u64,
827 blksize: st.st_blksize as u64,
828 blocks: st.st_blocks as u64,
829 atime_sec: st.st_atime as u64,
830 atime_nsec: st.st_atime_nsec as u64,
831 mtime_sec: st.st_mtime as u64,
832 mtime_nsec: st.st_mtime_nsec as u64,
833 ctime_sec: st.st_ctime as u64,
834 ctime_nsec: st.st_ctime_nsec as u64,
835 btime_sec: 0,
836 btime_nsec: 0,
837 gen: 0,
838 data_version: 0,
839 })
840 }
841
set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()>842 fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()> {
843 let fid = self.fids.get(&set_attr.fid).ok_or_else(ebadf)?;
844 let path = string_to_cstring(format!("self/fd/{}", fid.path.as_raw_fd()))?;
845
846 if set_attr.valid & P9_SETATTR_MODE != 0 {
847 // Safe because this doesn't modify any memory and we check the return value.
848 syscall!(unsafe {
849 libc::fchmodat(self.proc.as_raw_fd(), path.as_ptr(), set_attr.mode, 0)
850 })?;
851 }
852
853 if set_attr.valid & (P9_SETATTR_UID | P9_SETATTR_GID) != 0 {
854 let uid = if set_attr.valid & P9_SETATTR_UID != 0 {
855 set_attr.uid
856 } else {
857 -1i32 as u32
858 };
859 let gid = if set_attr.valid & P9_SETATTR_GID != 0 {
860 set_attr.gid
861 } else {
862 -1i32 as u32
863 };
864
865 // Safe because this doesn't modify any memory and we check the return value.
866 syscall!(unsafe { libc::fchownat(self.proc.as_raw_fd(), path.as_ptr(), uid, gid, 0) })?;
867 }
868
869 if set_attr.valid & P9_SETATTR_SIZE != 0 {
870 let file = if fid.filetype == FileType::Directory {
871 return Err(io::Error::from_raw_os_error(libc::EISDIR));
872 } else if let Some(ref file) = fid.file {
873 MaybeOwned::Borrowed(file)
874 } else {
875 MaybeOwned::Owned(open_fid(&self.proc, &fid.path, P9_NONBLOCK | P9_RDWR)?)
876 };
877
878 file.set_len(set_attr.size)?;
879 }
880
881 if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
882 let times = [
883 libc::timespec {
884 tv_sec: set_attr.atime_sec as _,
885 tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
886 libc::UTIME_OMIT
887 } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
888 libc::UTIME_NOW
889 } else {
890 set_attr.atime_nsec as _
891 },
892 },
893 libc::timespec {
894 tv_sec: set_attr.mtime_sec as _,
895 tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
896 libc::UTIME_OMIT
897 } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
898 libc::UTIME_NOW
899 } else {
900 set_attr.mtime_nsec as _
901 },
902 },
903 ];
904
905 // Safe because file is valid and we have initialized times fully.
906 let ret = unsafe {
907 libc::utimensat(
908 self.proc.as_raw_fd(),
909 path.as_ptr(),
910 × as *const libc::timespec,
911 0,
912 )
913 };
914 if ret < 0 {
915 return Err(io::Error::last_os_error());
916 }
917 }
918
919 // The ctime would have been updated by any of the above operations so we only
920 // need to change it if it was the only option given.
921 if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
922 // Setting -1 as the uid and gid will not actually change anything but will
923 // still update the ctime.
924 let ret = unsafe {
925 libc::fchownat(
926 self.proc.as_raw_fd(),
927 path.as_ptr(),
928 libc::uid_t::max_value(),
929 libc::gid_t::max_value(),
930 0,
931 )
932 };
933 if ret < 0 {
934 return Err(io::Error::last_os_error());
935 }
936 }
937
938 Ok(())
939 }
940
xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk>941 fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk> {
942 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
943 }
944
xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()>945 fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()> {
946 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
947 }
948
readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir>949 fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir> {
950 let fid = self.fids.get_mut(&readdir.fid).ok_or_else(ebadf)?;
951
952 if fid.filetype != FileType::Directory {
953 return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
954 }
955
956 // Use an empty Rreaddir struct to figure out the maximum number of bytes that
957 // can be returned.
958 let header_size = Rframe {
959 tag: 0,
960 msg: Rmessage::Readdir(Rreaddir {
961 data: Data(Vec::new()),
962 }),
963 }
964 .byte_size();
965 let count = min(self.cfg.msize - header_size, readdir.count);
966 let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
967
968 let dir = fid.file.as_mut().ok_or_else(ebadf)?;
969 let mut dirents = read_dir(dir, readdir.offset as libc::off64_t)?;
970 while let Some(dirent) = dirents.next().transpose()? {
971 let st = statat(&fid.path, dirent.name, 0)?;
972
973 let name = dirent
974 .name
975 .to_str()
976 .map(String::from)
977 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
978
979 let entry = Dirent {
980 qid: st.into(),
981 offset: dirent.offset,
982 ty: dirent.type_,
983 name,
984 };
985
986 let byte_size = entry.byte_size() as usize;
987
988 if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
989 // No more room in the buffer.
990 break;
991 }
992
993 entry.encode(&mut cursor)?;
994 }
995
996 Ok(Rreaddir {
997 data: Data(cursor.into_inner()),
998 })
999 }
1000
fsync(&mut self, fsync: &Tfsync) -> io::Result<()>1001 fn fsync(&mut self, fsync: &Tfsync) -> io::Result<()> {
1002 let file = self
1003 .fids
1004 .get(&fsync.fid)
1005 .and_then(|fid| fid.file.as_ref())
1006 .ok_or_else(ebadf)?;
1007
1008 if fsync.datasync == 0 {
1009 file.sync_all()?;
1010 } else {
1011 file.sync_data()?;
1012 }
1013 Ok(())
1014 }
1015
1016 /// Implement posix byte range locking code.
1017 /// Our implementation mirrors that of QEMU/9p - that is to say,
1018 /// we essentially punt on mirroring lock state between client/server
1019 /// and defer lock semantics to the VFS layer on the client side. Aside
1020 /// from fd existence check we always return success. QEMU reference:
1021 /// <https://github.com/qemu/qemu/blob/754f756cc4c6d9d14b7230c62b5bb20f9d655888/hw/9pfs/9p.c#L3669>
1022 ///
1023 /// NOTE: this means that files locked on the client may be interefered with
1024 /// from either the server's side, or from other clients (guests). This
1025 /// tracks with QEMU implementation, and will be obviated if crosvm decides
1026 /// to drop 9p in favor of virtio-fs. QEMU only allows for a single client,
1027 /// and we leave it to users of the crate to provide actual lock handling.
lock(&mut self, lock: &Tlock) -> io::Result<Rlock>1028 fn lock(&mut self, lock: &Tlock) -> io::Result<Rlock> {
1029 // Ensure fd passed in TLOCK request exists and has a mapping.
1030 let fd = self
1031 .fids
1032 .get(&lock.fid)
1033 .and_then(|fid| fid.file.as_ref())
1034 .ok_or_else(ebadf)?
1035 .as_raw_fd();
1036
1037 syscall!(unsafe {
1038 // Safe because zero-filled libc::stat is a valid value, fstat
1039 // populates the struct fields.
1040 let mut stbuf: libc::stat64 = std::mem::zeroed();
1041 // Safe because this doesn't modify memory and we check the return value.
1042 libc::fstat64(fd, &mut stbuf)
1043 })?;
1044
1045 Ok(Rlock {
1046 status: P9_LOCK_SUCCESS,
1047 })
1048 }
1049
1050 ///
1051 /// Much like lock(), defer locking semantics to VFS and return success.
1052 ///
get_lock(&mut self, get_lock: &Tgetlock) -> io::Result<Rgetlock>1053 fn get_lock(&mut self, get_lock: &Tgetlock) -> io::Result<Rgetlock> {
1054 // Ensure fd passed in GETTLOCK request exists and has a mapping.
1055 let fd = self
1056 .fids
1057 .get(&get_lock.fid)
1058 .and_then(|fid| fid.file.as_ref())
1059 .ok_or_else(ebadf)?
1060 .as_raw_fd();
1061
1062 // Safe because this doesn't modify memory and we check the return value.
1063 syscall!(unsafe {
1064 let mut stbuf: libc::stat64 = std::mem::zeroed();
1065 libc::fstat64(fd, &mut stbuf)
1066 })?;
1067
1068 Ok(Rgetlock {
1069 type_: P9_LOCK_TYPE_UNLCK,
1070 start: get_lock.start,
1071 length: get_lock.length,
1072 proc_id: get_lock.proc_id,
1073 client_id: get_lock.client_id.clone(),
1074 })
1075 }
1076
link(&mut self, link: Tlink) -> io::Result<()>1077 fn link(&mut self, link: Tlink) -> io::Result<()> {
1078 let target = self.fids.get(&link.fid).ok_or_else(ebadf)?;
1079 let path = string_to_cstring(format!("self/fd/{}", target.path.as_raw_fd()))?;
1080
1081 let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?;
1082 let name = string_to_cstring(link.name)?;
1083
1084 // Safe because this doesn't modify any memory and we check the return value.
1085 syscall!(unsafe {
1086 libc::linkat(
1087 self.proc.as_raw_fd(),
1088 path.as_ptr(),
1089 dir.path.as_raw_fd(),
1090 name.as_ptr(),
1091 libc::AT_SYMLINK_FOLLOW,
1092 )
1093 })?;
1094 Ok(())
1095 }
1096
mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir>1097 fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir> {
1098 let fid = self.fids.get(&mkdir.dfid).ok_or_else(ebadf)?;
1099 let name = string_to_cstring(mkdir.name)?;
1100
1101 // Safe because this doesn't modify any memory and we check the return value.
1102 syscall!(unsafe { libc::mkdirat(fid.path.as_raw_fd(), name.as_ptr(), mkdir.mode) })?;
1103 Ok(Rmkdir {
1104 qid: statat(&fid.path, &name, 0).map(Qid::from)?,
1105 })
1106 }
1107
rename_at(&mut self, rename_at: Trenameat) -> io::Result<()>1108 fn rename_at(&mut self, rename_at: Trenameat) -> io::Result<()> {
1109 let olddir = self.fids.get(&rename_at.olddirfid).ok_or_else(ebadf)?;
1110 let oldname = string_to_cstring(rename_at.oldname)?;
1111
1112 let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?;
1113 let newname = string_to_cstring(rename_at.newname)?;
1114
1115 // Safe because this doesn't modify any memory and we check the return value.
1116 syscall!(unsafe {
1117 libc::renameat(
1118 olddir.path.as_raw_fd(),
1119 oldname.as_ptr(),
1120 newdir.path.as_raw_fd(),
1121 newname.as_ptr(),
1122 )
1123 })?;
1124
1125 Ok(())
1126 }
1127
unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()>1128 fn unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()> {
1129 let dir = self.fids.get(&unlink_at.dirfd).ok_or_else(ebadf)?;
1130 let name = string_to_cstring(unlink_at.name)?;
1131
1132 syscall!(unsafe {
1133 libc::unlinkat(
1134 dir.path.as_raw_fd(),
1135 name.as_ptr(),
1136 unlink_at.flags as libc::c_int,
1137 )
1138 })?;
1139
1140 Ok(())
1141 }
1142 }
1143
1144 #[cfg(test)]
1145 mod tests;
1146