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 = 64 * 1024 + 24; // 64 KiB of payload plus some extra for the header
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 Ok(Rlopen {
728 qid: st.into(),
729 iounit: 0, // Allow the client to send requests up to the negotiated max message size.
730 })
731 }
732
lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate>733 fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate> {
734 let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?;
735
736 if fid.filetype != FileType::Directory {
737 return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
738 }
739
740 let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL;
741 for &(p9f, of) in &MAPPED_FLAGS {
742 if (lcreate.flags & p9f) != 0 {
743 flags |= of;
744 }
745 }
746 if lcreate.flags & P9_NOACCESS == P9_RDONLY {
747 flags |= libc::O_RDONLY;
748 }
749
750 let name = string_to_cstring(lcreate.name)?;
751
752 // Safe because this doesn't modify any memory and we check the return value.
753 let fd = syscall!(unsafe {
754 libc::openat64(fid.path.as_raw_fd(), name.as_ptr(), flags, lcreate.mode)
755 })?;
756
757 // Safe because we just opened this fd and we know it is valid.
758 let file = unsafe { File::from_raw_fd(fd) };
759 let st = stat(&file)?;
760
761 fid.file = Some(file);
762 fid.filetype = FileType::Regular;
763
764 // This fid now refers to the newly created file so we need to update the O_PATH fd for it
765 // as well.
766 fid.path = lookup(&fid.path, &name)?;
767
768 Ok(Rlcreate {
769 qid: st.into(),
770 iounit: 0, // Allow the client to send requests up to the negotiated max message size.
771 })
772 }
773
symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink>774 fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> {
775 // symlinks are not allowed.
776 Err(io::Error::from_raw_os_error(libc::EACCES))
777 }
778
mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod>779 fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> {
780 // No nodes either.
781 Err(io::Error::from_raw_os_error(libc::EACCES))
782 }
783
rename(&mut self, _rename: &Trename) -> io::Result<()>784 fn rename(&mut self, _rename: &Trename) -> io::Result<()> {
785 // We cannot support this as an inode may be linked into multiple directories but we don't
786 // know which one the client wants us to rename. Linux uses rename_at anyway, so we don't
787 // need to worry about this.
788 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
789 }
790
readlink(&mut self, readlink: &Treadlink) -> io::Result<Rreadlink>791 fn readlink(&mut self, readlink: &Treadlink) -> io::Result<Rreadlink> {
792 let fid = self.fids.get(&readlink.fid).ok_or_else(ebadf)?;
793
794 let mut link = vec![0; libc::PATH_MAX as usize];
795
796 // Safe because this will only modify `link` and we check the return value.
797 let len = syscall!(unsafe {
798 libc::readlinkat(
799 fid.path.as_raw_fd(),
800 [0].as_ptr(),
801 link.as_mut_ptr() as *mut libc::c_char,
802 link.len(),
803 )
804 })? as usize;
805 link.truncate(len);
806 let target = String::from_utf8(link)
807 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
808 Ok(Rreadlink { target })
809 }
810
811 #[allow(clippy::unnecessary_cast)] // nlink_t is u32 on 32-bit platforms
get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr>812 fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> {
813 let fid = self.fids.get_mut(&get_attr.fid).ok_or_else(ebadf)?;
814
815 let st = stat(&fid.path)?;
816
817 Ok(Rgetattr {
818 valid: P9_GETATTR_BASIC,
819 qid: st.into(),
820 mode: st.st_mode,
821 uid: map_id_from_host(&self.cfg.uid_map, st.st_uid),
822 gid: map_id_from_host(&self.cfg.gid_map, st.st_gid),
823 nlink: st.st_nlink as u64,
824 rdev: st.st_rdev,
825 size: st.st_size as u64,
826 blksize: st.st_blksize as u64,
827 blocks: st.st_blocks as u64,
828 atime_sec: st.st_atime as u64,
829 atime_nsec: st.st_atime_nsec as u64,
830 mtime_sec: st.st_mtime as u64,
831 mtime_nsec: st.st_mtime_nsec as u64,
832 ctime_sec: st.st_ctime as u64,
833 ctime_nsec: st.st_ctime_nsec as u64,
834 btime_sec: 0,
835 btime_nsec: 0,
836 gen: 0,
837 data_version: 0,
838 })
839 }
840
set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()>841 fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()> {
842 let fid = self.fids.get(&set_attr.fid).ok_or_else(ebadf)?;
843 let path = string_to_cstring(format!("self/fd/{}", fid.path.as_raw_fd()))?;
844
845 if set_attr.valid & P9_SETATTR_MODE != 0 {
846 // Safe because this doesn't modify any memory and we check the return value.
847 syscall!(unsafe {
848 libc::fchmodat(self.proc.as_raw_fd(), path.as_ptr(), set_attr.mode, 0)
849 })?;
850 }
851
852 if set_attr.valid & (P9_SETATTR_UID | P9_SETATTR_GID) != 0 {
853 let uid = if set_attr.valid & P9_SETATTR_UID != 0 {
854 set_attr.uid
855 } else {
856 -1i32 as u32
857 };
858 let gid = if set_attr.valid & P9_SETATTR_GID != 0 {
859 set_attr.gid
860 } else {
861 -1i32 as u32
862 };
863
864 // Safe because this doesn't modify any memory and we check the return value.
865 syscall!(unsafe { libc::fchownat(self.proc.as_raw_fd(), path.as_ptr(), uid, gid, 0) })?;
866 }
867
868 if set_attr.valid & P9_SETATTR_SIZE != 0 {
869 let file = if fid.filetype == FileType::Directory {
870 return Err(io::Error::from_raw_os_error(libc::EISDIR));
871 } else if let Some(ref file) = fid.file {
872 MaybeOwned::Borrowed(file)
873 } else {
874 MaybeOwned::Owned(open_fid(&self.proc, &fid.path, P9_NONBLOCK | P9_RDWR)?)
875 };
876
877 file.set_len(set_attr.size)?;
878 }
879
880 if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
881 let times = [
882 libc::timespec {
883 tv_sec: set_attr.atime_sec as _,
884 tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
885 libc::UTIME_OMIT
886 } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
887 libc::UTIME_NOW
888 } else {
889 set_attr.atime_nsec as _
890 },
891 },
892 libc::timespec {
893 tv_sec: set_attr.mtime_sec as _,
894 tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
895 libc::UTIME_OMIT
896 } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
897 libc::UTIME_NOW
898 } else {
899 set_attr.mtime_nsec as _
900 },
901 },
902 ];
903
904 // Safe because file is valid and we have initialized times fully.
905 let ret = unsafe {
906 libc::utimensat(
907 self.proc.as_raw_fd(),
908 path.as_ptr(),
909 × as *const libc::timespec,
910 0,
911 )
912 };
913 if ret < 0 {
914 return Err(io::Error::last_os_error());
915 }
916 }
917
918 // The ctime would have been updated by any of the above operations so we only
919 // need to change it if it was the only option given.
920 if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
921 // Setting -1 as the uid and gid will not actually change anything but will
922 // still update the ctime.
923 let ret = unsafe {
924 libc::fchownat(
925 self.proc.as_raw_fd(),
926 path.as_ptr(),
927 libc::uid_t::max_value(),
928 libc::gid_t::max_value(),
929 0,
930 )
931 };
932 if ret < 0 {
933 return Err(io::Error::last_os_error());
934 }
935 }
936
937 Ok(())
938 }
939
xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk>940 fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk> {
941 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
942 }
943
xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()>944 fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()> {
945 Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
946 }
947
readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir>948 fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir> {
949 let fid = self.fids.get_mut(&readdir.fid).ok_or_else(ebadf)?;
950
951 if fid.filetype != FileType::Directory {
952 return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
953 }
954
955 // Use an empty Rreaddir struct to figure out the maximum number of bytes that
956 // can be returned.
957 let header_size = Rframe {
958 tag: 0,
959 msg: Rmessage::Readdir(Rreaddir {
960 data: Data(Vec::new()),
961 }),
962 }
963 .byte_size();
964 let count = min(self.cfg.msize - header_size, readdir.count);
965 let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
966
967 let dir = fid.file.as_mut().ok_or_else(ebadf)?;
968 let mut dirents = read_dir(dir, readdir.offset as libc::off64_t)?;
969 while let Some(dirent) = dirents.next().transpose()? {
970 let st = statat(&fid.path, dirent.name, 0)?;
971
972 let name = dirent
973 .name
974 .to_str()
975 .map(String::from)
976 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
977
978 let entry = Dirent {
979 qid: st.into(),
980 offset: dirent.offset,
981 ty: dirent.type_,
982 name,
983 };
984
985 let byte_size = entry.byte_size() as usize;
986
987 if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
988 // No more room in the buffer.
989 break;
990 }
991
992 entry.encode(&mut cursor)?;
993 }
994
995 Ok(Rreaddir {
996 data: Data(cursor.into_inner()),
997 })
998 }
999
fsync(&mut self, fsync: &Tfsync) -> io::Result<()>1000 fn fsync(&mut self, fsync: &Tfsync) -> io::Result<()> {
1001 let file = self
1002 .fids
1003 .get(&fsync.fid)
1004 .and_then(|fid| fid.file.as_ref())
1005 .ok_or_else(ebadf)?;
1006
1007 if fsync.datasync == 0 {
1008 file.sync_all()?;
1009 } else {
1010 file.sync_data()?;
1011 }
1012 Ok(())
1013 }
1014
1015 /// Implement posix byte range locking code.
1016 /// Our implementation mirrors that of QEMU/9p - that is to say,
1017 /// we essentially punt on mirroring lock state between client/server
1018 /// and defer lock semantics to the VFS layer on the client side. Aside
1019 /// from fd existence check we always return success. QEMU reference:
1020 /// <https://github.com/qemu/qemu/blob/754f756cc4c6d9d14b7230c62b5bb20f9d655888/hw/9pfs/9p.c#L3669>
1021 ///
1022 /// NOTE: this means that files locked on the client may be interefered with
1023 /// from either the server's side, or from other clients (guests). This
1024 /// tracks with QEMU implementation, and will be obviated if crosvm decides
1025 /// to drop 9p in favor of virtio-fs. QEMU only allows for a single client,
1026 /// and we leave it to users of the crate to provide actual lock handling.
lock(&mut self, lock: &Tlock) -> io::Result<Rlock>1027 fn lock(&mut self, lock: &Tlock) -> io::Result<Rlock> {
1028 // Ensure fd passed in TLOCK request exists and has a mapping.
1029 let fd = self
1030 .fids
1031 .get(&lock.fid)
1032 .and_then(|fid| fid.file.as_ref())
1033 .ok_or_else(ebadf)?
1034 .as_raw_fd();
1035
1036 syscall!(unsafe {
1037 // Safe because zero-filled libc::stat is a valid value, fstat
1038 // populates the struct fields.
1039 let mut stbuf: libc::stat64 = std::mem::zeroed();
1040 // Safe because this doesn't modify memory and we check the return value.
1041 libc::fstat64(fd, &mut stbuf)
1042 })?;
1043
1044 Ok(Rlock {
1045 status: P9_LOCK_SUCCESS,
1046 })
1047 }
1048
1049 ///
1050 /// Much like lock(), defer locking semantics to VFS and return success.
1051 ///
get_lock(&mut self, get_lock: &Tgetlock) -> io::Result<Rgetlock>1052 fn get_lock(&mut self, get_lock: &Tgetlock) -> io::Result<Rgetlock> {
1053 // Ensure fd passed in GETTLOCK request exists and has a mapping.
1054 let fd = self
1055 .fids
1056 .get(&get_lock.fid)
1057 .and_then(|fid| fid.file.as_ref())
1058 .ok_or_else(ebadf)?
1059 .as_raw_fd();
1060
1061 // Safe because this doesn't modify memory and we check the return value.
1062 syscall!(unsafe {
1063 let mut stbuf: libc::stat64 = std::mem::zeroed();
1064 libc::fstat64(fd, &mut stbuf)
1065 })?;
1066
1067 Ok(Rgetlock {
1068 type_: P9_LOCK_TYPE_UNLCK,
1069 start: get_lock.start,
1070 length: get_lock.length,
1071 proc_id: get_lock.proc_id,
1072 client_id: get_lock.client_id.clone(),
1073 })
1074 }
1075
link(&mut self, link: Tlink) -> io::Result<()>1076 fn link(&mut self, link: Tlink) -> io::Result<()> {
1077 let target = self.fids.get(&link.fid).ok_or_else(ebadf)?;
1078 let path = string_to_cstring(format!("self/fd/{}", target.path.as_raw_fd()))?;
1079
1080 let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?;
1081 let name = string_to_cstring(link.name)?;
1082
1083 // Safe because this doesn't modify any memory and we check the return value.
1084 syscall!(unsafe {
1085 libc::linkat(
1086 self.proc.as_raw_fd(),
1087 path.as_ptr(),
1088 dir.path.as_raw_fd(),
1089 name.as_ptr(),
1090 libc::AT_SYMLINK_FOLLOW,
1091 )
1092 })?;
1093 Ok(())
1094 }
1095
mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir>1096 fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir> {
1097 let fid = self.fids.get(&mkdir.dfid).ok_or_else(ebadf)?;
1098 let name = string_to_cstring(mkdir.name)?;
1099
1100 // Safe because this doesn't modify any memory and we check the return value.
1101 syscall!(unsafe { libc::mkdirat(fid.path.as_raw_fd(), name.as_ptr(), mkdir.mode) })?;
1102 Ok(Rmkdir {
1103 qid: statat(&fid.path, &name, 0).map(Qid::from)?,
1104 })
1105 }
1106
rename_at(&mut self, rename_at: Trenameat) -> io::Result<()>1107 fn rename_at(&mut self, rename_at: Trenameat) -> io::Result<()> {
1108 let olddir = self.fids.get(&rename_at.olddirfid).ok_or_else(ebadf)?;
1109 let oldname = string_to_cstring(rename_at.oldname)?;
1110
1111 let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?;
1112 let newname = string_to_cstring(rename_at.newname)?;
1113
1114 // Safe because this doesn't modify any memory and we check the return value.
1115 syscall!(unsafe {
1116 libc::renameat(
1117 olddir.path.as_raw_fd(),
1118 oldname.as_ptr(),
1119 newdir.path.as_raw_fd(),
1120 newname.as_ptr(),
1121 )
1122 })?;
1123
1124 Ok(())
1125 }
1126
unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()>1127 fn unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()> {
1128 let dir = self.fids.get(&unlink_at.dirfd).ok_or_else(ebadf)?;
1129 let name = string_to_cstring(unlink_at.name)?;
1130
1131 syscall!(unsafe {
1132 libc::unlinkat(
1133 dir.path.as_raw_fd(),
1134 name.as_ptr(),
1135 unlink_at.flags as libc::c_int,
1136 )
1137 })?;
1138
1139 Ok(())
1140 }
1141 }
1142
1143 #[cfg(test)]
1144 mod tests;
1145