summary refs log blame commit diff
path: root/p9/src/server.rs
blob: 8f1d649f2984a09ee275286fd1983f09a1d8791c (plain) (tree)
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


















































































































































































































































































































































































































                                                                                                    




                                                                  
                                                         
                             
 



































































































































                                                                                           
                                       











































































































































































































































































































                                                                                                    
                                     






















































































                                                                                  
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use libc;

use std::cmp::min;
use std::collections::{btree_map, BTreeMap};
use std::ffi::CString;
use std::fs;
use std::io::{self, Cursor, Read, Write};
use std::mem;
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::{DirBuilderExt, FileExt, OpenOptionsExt};
use std::os::unix::io::AsRawFd;
use std::path::{Component, Path, PathBuf};

use protocol::*;

// Tlopen and Tlcreate flags.  Taken from "include/net/9p/9p.h" in the linux tree.
const _P9_RDONLY: u32 = 0o00000000;
const P9_WRONLY: u32 = 0o00000001;
const P9_RDWR: u32 = 0o00000002;
const P9_NOACCESS: u32 = 0o00000003;
const P9_CREATE: u32 = 0o00000100;
const P9_EXCL: u32 = 0o00000200;
const P9_NOCTTY: u32 = 0o00000400;
const P9_TRUNC: u32 = 0o00001000;
const P9_APPEND: u32 = 0o00002000;
const P9_NONBLOCK: u32 = 0o00004000;
const P9_DSYNC: u32 = 0o00010000;
const P9_FASYNC: u32 = 0o00020000;
const P9_DIRECT: u32 = 0o00040000;
const P9_LARGEFILE: u32 = 0o00100000;
const P9_DIRECTORY: u32 = 0o00200000;
const P9_NOFOLLOW: u32 = 0o00400000;
const P9_NOATIME: u32 = 0o01000000;
const _P9_CLOEXEC: u32 = 0o02000000;
const P9_SYNC: u32 = 0o04000000;

// Mapping from 9P flags to libc flags.
const MAPPED_FLAGS: [(u32, i32); 10] = [
    (P9_NOCTTY, libc::O_NOCTTY),
    (P9_NONBLOCK, libc::O_NONBLOCK),
    (P9_DSYNC, libc::O_DSYNC),
    (P9_FASYNC, 0), // Unsupported
    (P9_DIRECT, libc::O_DIRECT),
    (P9_LARGEFILE, libc::O_LARGEFILE),
    (P9_DIRECTORY, libc::O_DIRECTORY),
    (P9_NOFOLLOW, libc::O_NOFOLLOW),
    (P9_NOATIME, libc::O_NOATIME),
    (P9_SYNC, libc::O_SYNC),
];

// 9P Qid types.  Taken from "include/net/9p/9p.h" in the linux tree.
const P9_QTDIR: u8 = 0x80;
const _P9_QTAPPEND: u8 = 0x40;
const _P9_QTEXCL: u8 = 0x20;
const _P9_QTMOUNT: u8 = 0x10;
const _P9_QTAUTH: u8 = 0x08;
const _P9_QTTMP: u8 = 0x04;
const _P9_QTSYMLINK: u8 = 0x02;
const _P9_QTLINK: u8 = 0x01;
const P9_QTFILE: u8 = 0x00;

// Bitmask values for the getattr request.
const _P9_GETATTR_MODE: u64 = 0x00000001;
const _P9_GETATTR_NLINK: u64 = 0x00000002;
const _P9_GETATTR_UID: u64 = 0x00000004;
const _P9_GETATTR_GID: u64 = 0x00000008;
const _P9_GETATTR_RDEV: u64 = 0x00000010;
const _P9_GETATTR_ATIME: u64 = 0x00000020;
const _P9_GETATTR_MTIME: u64 = 0x00000040;
const _P9_GETATTR_CTIME: u64 = 0x00000080;
const _P9_GETATTR_INO: u64 = 0x00000100;
const _P9_GETATTR_SIZE: u64 = 0x00000200;
const _P9_GETATTR_BLOCKS: u64 = 0x00000400;

const _P9_GETATTR_BTIME: u64 = 0x00000800;
const _P9_GETATTR_GEN: u64 = 0x00001000;
const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000;

const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */
const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */

// Bitmask values for the setattr request.
const P9_SETATTR_MODE: u32 = 0x00000001;
const P9_SETATTR_UID: u32 = 0x00000002;
const P9_SETATTR_GID: u32 = 0x00000004;
const P9_SETATTR_SIZE: u32 = 0x00000008;
const P9_SETATTR_ATIME: u32 = 0x00000010;
const P9_SETATTR_MTIME: u32 = 0x00000020;
const P9_SETATTR_CTIME: u32 = 0x00000040;
const P9_SETATTR_ATIME_SET: u32 = 0x00000080;
const P9_SETATTR_MTIME_SET: u32 = 0x00000100;

// Minimum and maximum message size that we'll expect from the client.
const MIN_MESSAGE_SIZE: u32 = 256;
const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32;

// Represents state that the server is holding on behalf of a client. Fids are somewhat like file
// descriptors but are not restricted to open files and directories. Fids are identified by a unique
// 32-bit number chosen by the client. Most messages sent by clients include a fid on which to
// operate. The fid in a Tattach message represents the root of the file system tree that the client
// is allowed to access. A client can create more fids by walking the directory tree from that fid.
struct Fid {
    path: Box<Path>,
    metadata: fs::Metadata,
    file: Option<fs::File>,
    dirents: Option<Vec<Dirent>>,
}

fn metadata_to_qid(metadata: &fs::Metadata) -> Qid {
    let ty = if metadata.is_dir() {
        P9_QTDIR
    } else if metadata.is_file() {
        P9_QTFILE
    } else {
        // Unknown file type...
        0
    };

    Qid {
        ty: ty,
        // TODO: deal with the 2038 problem before 2038
        version: metadata.st_mtime() as u32,
        path: metadata.st_ino(),
    }
}

fn error_to_rmessage(err: io::Error) -> Rmessage {
    let errno = if let Some(errno) = err.raw_os_error() {
        errno
    } else {
        // Make a best-effort guess based on the kind.
        match err.kind() {
            io::ErrorKind::NotFound => libc::ENOENT,
            io::ErrorKind::PermissionDenied => libc::EPERM,
            io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED,
            io::ErrorKind::ConnectionReset => libc::ECONNRESET,
            io::ErrorKind::ConnectionAborted => libc::ECONNABORTED,
            io::ErrorKind::NotConnected => libc::ENOTCONN,
            io::ErrorKind::AddrInUse => libc::EADDRINUSE,
            io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL,
            io::ErrorKind::BrokenPipe => libc::EPIPE,
            io::ErrorKind::AlreadyExists => libc::EEXIST,
            io::ErrorKind::WouldBlock => libc::EWOULDBLOCK,
            io::ErrorKind::InvalidInput => libc::EINVAL,
            io::ErrorKind::InvalidData => libc::EINVAL,
            io::ErrorKind::TimedOut => libc::ETIMEDOUT,
            io::ErrorKind::WriteZero => libc::EIO,
            io::ErrorKind::Interrupted => libc::EINTR,
            io::ErrorKind::Other => libc::EIO,
            io::ErrorKind::UnexpectedEof => libc::EIO,
            _ => libc::EIO,
        }
    };

    Rmessage::Lerror(Rlerror {
        ecode: errno as u32,
    })
}

// Joins `path` to `buf`.  If `path` is '..', removes the last component from `buf`
// only if `buf` != `root` but does nothing if `buf` == `root`.  Pushes `path` onto
// `buf` if it is a normal path component.
//
// Returns an error if `path` is absolute, has more than one component, or contains
// a '.' component.
fn join_path<P: AsRef<Path>, R: AsRef<Path>>(
    mut buf: PathBuf,
    path: P,
    root: R,
) -> io::Result<PathBuf> {
    let path = path.as_ref();
    let root = root.as_ref();
    debug_assert!(buf.starts_with(root));

    if path.components().count() > 1 {
        return Err(io::Error::from_raw_os_error(libc::EINVAL));
    }

    for component in path.components() {
        match component {
            // Prefix should only appear on windows systems.
            Component::Prefix(_) => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
            // Absolute paths are not allowed.
            Component::RootDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
            // '.' elements are not allowed.
            Component::CurDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
            Component::ParentDir => {
                // We only remove the parent path if we are not already at the root of the
                // file system.
                if buf != root {
                    buf.pop();
                }
            }
            Component::Normal(element) => buf.push(element),
        }
    }

    Ok(buf)
}

pub struct Server {
    root: Box<Path>,
    msize: u32,
    fids: BTreeMap<u32, Fid>,
}

impl Server {
    pub fn new<P: AsRef<Path>>(root: P) -> Server {
        Server {
            root: root.as_ref().into(),
            msize: MAX_MESSAGE_SIZE,
            fids: BTreeMap::new(),
        }
    }

    pub fn handle_message<R: Read, W: Write>(
        &mut self,
        reader: &mut R,
        writer: &mut W,
    ) -> io::Result<()> {
        let request: Tframe = WireFormat::decode(&mut reader.take(self.msize as u64))?;

        if cfg!(feature = "trace") {
            println!("{:?}", &request);
        }

        let rmsg = match request.msg {
            Tmessage::Version(ref version) => self.version(version),
            Tmessage::Flush(ref flush) => self.flush(flush),
            Tmessage::Walk(ref walk) => self.walk(walk),
            Tmessage::Read(ref read) => self.read(read),
            Tmessage::Write(ref write) => self.write(write),
            Tmessage::Clunk(ref clunk) => self.clunk(clunk),
            Tmessage::Remove(ref remove) => self.remove(remove),
            Tmessage::Attach(ref attach) => self.attach(attach),
            Tmessage::Auth(ref auth) => self.auth(auth),
            Tmessage::Statfs(ref statfs) => self.statfs(statfs),
            Tmessage::Lopen(ref lopen) => self.lopen(lopen),
            Tmessage::Lcreate(ref lcreate) => self.lcreate(lcreate),
            Tmessage::Symlink(ref symlink) => self.symlink(symlink),
            Tmessage::Mknod(ref mknod) => self.mknod(mknod),
            Tmessage::Rename(ref rename) => self.rename(rename),
            Tmessage::Readlink(ref readlink) => self.readlink(readlink),
            Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr),
            Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr),
            Tmessage::XattrWalk(ref xattr_walk) => self.xattr_walk(xattr_walk),
            Tmessage::XattrCreate(ref xattr_create) => self.xattr_create(xattr_create),
            Tmessage::Readdir(ref readdir) => self.readdir(readdir),
            Tmessage::Fsync(ref fsync) => self.fsync(fsync),
            Tmessage::Lock(ref lock) => self.lock(lock),
            Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock),
            Tmessage::Link(ref link) => self.link(link),
            Tmessage::Mkdir(ref mkdir) => self.mkdir(mkdir),
            Tmessage::RenameAt(ref rename_at) => self.rename_at(rename_at),
            Tmessage::UnlinkAt(ref unlink_at) => self.unlink_at(unlink_at),
        };

        // Errors while handling requests are never fatal.
        let response = Rframe {
            tag: request.tag,
            msg: rmsg.unwrap_or_else(error_to_rmessage),
        };

        if cfg!(feature = "trace") {
            println!("{:?}", &response);
        }

        response.encode(writer)?;
        writer.flush()
    }

    fn auth(&mut self, _auth: &Tauth) -> io::Result<Rmessage> {
        // Returning an error for the auth message means that the server does not require
        // authentication.
        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
    }

    fn attach(&mut self, attach: &Tattach) -> io::Result<Rmessage> {
        // TODO: Check attach parameters
        match self.fids.entry(attach.fid) {
            btree_map::Entry::Vacant(entry) => {
                let fid = Fid {
                    path: self.root.to_path_buf().into_boxed_path(),
                    metadata: fs::metadata(&self.root)?,
                    file: None,
                    dirents: None,
                };
                let response = Rattach {
                    qid: metadata_to_qid(&fid.metadata),
                };
                entry.insert(fid);
                Ok(Rmessage::Attach(response))
            }
            btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
        }
    }

    fn version(&mut self, version: &Tversion) -> io::Result<Rmessage> {
        if version.msize < MIN_MESSAGE_SIZE {
            return Err(io::Error::from_raw_os_error(libc::EINVAL));
        }

        // A Tversion request clunks all open fids and terminates any pending I/O.
        self.fids.clear();
        self.msize = min(MAX_MESSAGE_SIZE, version.msize);

        Ok(Rmessage::Version(Rversion {
            msize: self.msize,
            version: if version.version == "9P2000.L" {
                String::from("9P2000.L")
            } else {
                String::from("unknown")
            },
        }))
    }

    fn flush(&mut self, _flush: &Tflush) -> io::Result<Rmessage> {
        // TODO: Since everything is synchronous we can't actually flush requests.
        Ok(Rmessage::Flush)
    }

    fn do_walk(
        &self,
        wnames: &[String],
        mut buf: PathBuf,
        mds: &mut Vec<fs::Metadata>,
    ) -> io::Result<PathBuf> {
        for wname in wnames {
            let name = Path::new(wname);
            buf = join_path(buf, name, &*self.root)?;
            mds.push(fs::metadata(&buf)?);
        }

        Ok(buf)
    }

    fn walk(&mut self, walk: &Twalk) -> io::Result<Rmessage> {
        // `newfid` must not currently be in use unless it is the same as `fid`.
        if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
            return Err(io::Error::from_raw_os_error(libc::EBADF));
        }

        // We need to walk the tree.  First get the starting path.
        let (buf, oldmd) = self.fids
            .get(&walk.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
            .map(|fid| (fid.path.to_path_buf(), fid.metadata.clone()))?;

        // Now walk the tree and break on the first error, if any.
        let mut mds = Vec::with_capacity(walk.wnames.len());
        match self.do_walk(&walk.wnames, buf, &mut mds) {
            Ok(buf) => {
                // Store the new fid if the full walk succeeded.
                if mds.len() == walk.wnames.len() {
                    // This could just be a duplication operation.
                    let md = if let Some(md) = mds.last() {
                        md.clone()
                    } else {
                        oldmd
                    };

                    self.fids.insert(
                        walk.newfid,
                        Fid {
                            path: buf.into_boxed_path(),
                            metadata: md,
                            file: None,
                            dirents: None,
                        },
                    );
                }
            }
            Err(e) => {
                // Only return an error if it occurred on the first component.
                if mds.is_empty() {
                    return Err(e);
                }
            }
        }

        Ok(Rmessage::Walk(Rwalk {
            wqids: mds.iter().map(metadata_to_qid).collect(),
        }))
    }

    fn read(&mut self, read: &Tread) -> io::Result<Rmessage> {
        // Thankfully, `read` cannot be used to read directories in 9P2000.L.
        let file = self.fids
            .get_mut(&read.fid)
            .and_then(|fid| fid.file.as_mut())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        // Use an empty Rread struct to figure out the overhead of the header.
        let header_size = Rframe {
            tag: 0,
            msg: Rmessage::Read(Rread {
                data: Data(Vec::new()),
            }),
        }.byte_size();

        let capacity = min(self.msize - header_size, read.count);
        let mut buf = Data(Vec::with_capacity(capacity as usize));
        buf.resize(capacity as usize, 0);

        let count = file.read_at(&mut buf, read.offset)?;
        buf.resize(count, 0);

        Ok(Rmessage::Read(Rread { data: buf }))
    }

    fn write(&mut self, write: &Twrite) -> io::Result<Rmessage> {
        let file = self.fids
            .get_mut(&write.fid)
            .and_then(|fid| fid.file.as_mut())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        let count = file.write_at(&write.data, write.offset)?;
        Ok(Rmessage::Write(Rwrite {
            count: count as u32,
        }))
    }

    fn clunk(&mut self, clunk: &Tclunk) -> io::Result<Rmessage> {
        match self.fids.entry(clunk.fid) {
            btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
            btree_map::Entry::Occupied(entry) => {
                entry.remove();
                Ok(Rmessage::Clunk)
            }
        }
    }

    fn remove(&mut self, remove: &Tremove) -> io::Result<Rmessage> {
        match self.fids.entry(remove.fid) {
            btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
            btree_map::Entry::Occupied(o) => {
                let (_, fid) = o.remove_entry();

                if fid.metadata.is_dir() {
                    fs::remove_dir(&fid.path)?;
                } else {
                    fs::remove_file(&fid.path)?;
                }

                Ok(Rmessage::Remove)
            }
        }
    }

    fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rmessage> {
        let fid = self.fids
            .get(&statfs.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        let path = fid.path
            .to_str()
            .and_then(|path| CString::new(path).ok())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;

        // Safe because we are zero-initializing a C struct with only primitive
        // data members.
        let mut out: libc::statfs64 = unsafe { mem::zeroed() };

        // Safe because we know that `path` is valid and we have already initialized `out`.
        let ret = unsafe { libc::statfs64(path.as_ptr(), &mut out) };
        if ret != 0 {
            return Err(io::Error::last_os_error());
        }

        Ok(Rmessage::Statfs(Rstatfs {
            ty: out.f_type as u32,
            bsize: out.f_bsize as u32,
            blocks: out.f_blocks,
            bfree: out.f_bfree,
            bavail: out.f_bavail,
            files: out.f_files,
            ffree: out.f_ffree,
            fsid: 0, // No way to get the fields of a libc::fsid_t
            namelen: out.f_namelen as u32,
        }))
    }

    fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rmessage> {
        let fid = self.fids
            .get_mut(&lopen.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        // We always open files with O_CLOEXEC.
        let mut custom_flags: i32 = libc::O_CLOEXEC;
        for &(p9f, of) in &MAPPED_FLAGS {
            if (lopen.flags & p9f) != 0 {
                custom_flags |= of;
            }
        }

        let file = fs::OpenOptions::new()
            .read((lopen.flags & P9_NOACCESS) == 0 || (lopen.flags & P9_RDWR) != 0)
            .write((lopen.flags & P9_WRONLY) != 0 || (lopen.flags & P9_RDWR) != 0)
            .append((lopen.flags & P9_APPEND) != 0)
            .truncate((lopen.flags & P9_TRUNC) != 0)
            .create((lopen.flags & P9_CREATE) != 0)
            .create_new((lopen.flags & P9_CREATE) != 0 && (lopen.flags & P9_EXCL) != 0)
            .custom_flags(custom_flags)
            .open(&fid.path)?;

        fid.metadata = file.metadata()?;
        fid.file = Some(file);

        Ok(Rmessage::Lopen(Rlopen {
            qid: metadata_to_qid(&fid.metadata),
            iounit: 0,
        }))
    }

    fn lcreate(&mut self, lcreate: &Tlcreate) -> io::Result<Rmessage> {
        let fid = self.fids
            .get_mut(&lcreate.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        if !fid.metadata.is_dir() {
            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
        }

        let name = Path::new(&lcreate.name);
        let path = join_path(fid.path.to_path_buf(), name, &*self.root)?;

        let mut custom_flags: i32 = libc::O_CLOEXEC;
        for &(p9f, of) in &MAPPED_FLAGS {
            if (lcreate.flags & p9f) != 0 {
                custom_flags |= of;
            }
        }

        let file = fs::OpenOptions::new()
            .read(false)
            .write(true)
            .truncate(true)
            .create(true)
            .append((lcreate.flags & P9_APPEND) != 0)
            .create_new((lcreate.flags & P9_EXCL) != 0)
            .custom_flags(custom_flags)
            .mode(lcreate.mode & 0o755)
            .open(&path)?;

        fid.metadata = file.metadata()?;
        fid.file = Some(file);
        fid.path = path.into_boxed_path();

        Ok(Rmessage::Lcreate(Rlcreate {
            qid: metadata_to_qid(&fid.metadata),
            iounit: 0,
        }))
    }

    fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rmessage> {
        // symlinks are not allowed.
        Err(io::Error::from_raw_os_error(libc::EACCES))
    }

    fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmessage> {
        // No nodes either.
        Err(io::Error::from_raw_os_error(libc::EACCES))
    }

    fn rename(&mut self, rename: &Trename) -> io::Result<Rmessage> {
        let newname = Path::new(&rename.name);
        let buf = self.fids
            .get(&rename.dfid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
            .map(|dfid| dfid.path.to_path_buf())?;
        let newpath = join_path(buf, newname, &*self.root)?;

        let fid = self.fids
            .get_mut(&rename.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;

        fs::rename(&fid.path, &newpath)?;

        // TODO: figure out if the client expects |fid.path| to point to
        // the renamed path.
        fid.path = newpath.into_boxed_path();
        Ok(Rmessage::Rename)
    }

    fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rmessage> {
        // symlinks are not allowed
        Err(io::Error::from_raw_os_error(libc::EACCES))
    }

    fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rmessage> {
        let fid = self.fids
            .get_mut(&get_attr.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        // Refresh the metadata since we were explicitly asked for it.
        fid.metadata = fs::metadata(&fid.path)?;

        Ok(Rmessage::GetAttr(Rgetattr {
            valid: P9_GETATTR_BASIC,
            qid: metadata_to_qid(&fid.metadata),
            mode: fid.metadata.st_mode(),
            uid: fid.metadata.st_uid(),
            gid: fid.metadata.st_gid(),
            nlink: fid.metadata.st_nlink(),
            rdev: fid.metadata.st_rdev(),
            size: fid.metadata.st_size(),
            blksize: fid.metadata.st_blksize(),
            blocks: fid.metadata.st_blocks(),
            atime_sec: fid.metadata.st_atime() as u64,
            atime_nsec: fid.metadata.st_atime_nsec() as u64,
            mtime_sec: fid.metadata.st_mtime() as u64,
            mtime_nsec: fid.metadata.st_mtime_nsec() as u64,
            ctime_sec: fid.metadata.st_ctime() as u64,
            ctime_nsec: fid.metadata.st_ctime_nsec() as u64,
            btime_sec: 0,
            btime_nsec: 0,
            gen: 0,
            data_version: 0,
        }))
    }

    fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<Rmessage> {
        let blocked_ops = P9_SETATTR_MODE | P9_SETATTR_UID | P9_SETATTR_GID;
        if set_attr.valid & blocked_ops != 0 {
            return Err(io::Error::from_raw_os_error(libc::EPERM));
        }

        let fid = self.fids
            .get_mut(&set_attr.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        let file = fs::OpenOptions::new().write(true).open(&fid.path)?;

        if set_attr.valid & P9_SETATTR_SIZE != 0 {
            file.set_len(set_attr.size)?;
        }

        if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
            let times = [
                libc::timespec {
                    tv_sec: set_attr.atime_sec as _,
                    tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
                        libc::UTIME_OMIT
                    } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
                        libc::UTIME_NOW
                    } else {
                        set_attr.atime_nsec as _
                    },
                },
                libc::timespec {
                    tv_sec: set_attr.mtime_sec as _,
                    tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
                        libc::UTIME_OMIT
                    } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
                        libc::UTIME_NOW
                    } else {
                        set_attr.mtime_nsec as _
                    },
                },
            ];

            // Safe because file is valid and we have initialized times fully.
            let ret = unsafe { libc::futimens(file.as_raw_fd(), &times as *const libc::timespec) };
            if ret < 0 {
                return Err(io::Error::last_os_error());
            }
        }

        // The ctime would have been updated by any of the above operations so we only
        // need to change it if it was the only option given.
        if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
            // Setting -1 as the uid and gid will not actually change anything but will
            // still update the ctime.
            let ret = unsafe {
                libc::fchown(
                    file.as_raw_fd(),
                    libc::uid_t::max_value(),
                    libc::gid_t::max_value(),
                )
            };
            if ret < 0 {
                return Err(io::Error::last_os_error());
            }
        }

        Ok(Rmessage::SetAttr)
    }

    fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rmessage> {
        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
    }

    fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<Rmessage> {
        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
    }

    fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rmessage> {
        let fid = self.fids
            .get_mut(&readdir.fid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        if !fid.metadata.is_dir() {
            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
        }

        // The p9 client implementation in the kernel doesn't fully read all the contents
        // of the directory.  This means that if some application performs a getdents()
        // call, followed by removing some files, followed by another getdents() call,
        // the offset that we get from the kernel is completely meaningless.  Instead
        // we fully read the contents of the directory here and only re-read the directory
        // if the offset we get from the client is 0.  Any other offset is served from the
        // directory entries in memory.  This ensures consistency even if the directory
        // changes in between Treaddir messages.
        if readdir.offset == 0 {
            let mut offset = 0;
            let iter = fs::read_dir(&fid.path)?;
            let dirents = iter.map(|item| -> io::Result<Dirent> {
                let entry = item?;

                let md = entry.metadata()?;
                let qid = metadata_to_qid(&md);

                let ty = if md.is_dir() {
                    libc::DT_DIR
                } else if md.is_file() {
                    libc::DT_REG
                } else {
                    libc::DT_UNKNOWN
                };

                let name = entry
                    .file_name()
                    .into_string()
                    .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;

                let mut out = Dirent {
                    qid: qid,
                    offset: 0, // set below
                    ty: ty,
                    name: name,
                };

                offset += out.byte_size() as u64;
                out.offset = offset;

                Ok(out)
            });

            // This is taking advantage of the fact that we can turn a Iterator of Result<T, E>
            // into a Result<FromIterator<T>, E> since Result implements FromIterator<Result<T, E>>.
            fid.dirents = Some(dirents.collect::<io::Result<Vec<Dirent>>>()?);
        }

        let mut entries = fid.dirents
            .as_ref()
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?
            .iter()
            .skip_while(|entry| entry.offset <= readdir.offset)
            .peekable();

        // Use an empty Rreaddir struct to figure out the maximum number of bytes that
        // can be returned.
        let header_size = Rframe {
            tag: 0,
            msg: Rmessage::Readdir(Rreaddir {
                data: Data(Vec::new()),
            }),
        }.byte_size();
        let count = min(self.msize - header_size, readdir.count);
        let mut cursor = Cursor::new(Vec::with_capacity(count as usize));

        loop {
            let byte_size = if let Some(entry) = entries.peek() {
                entry.byte_size() as usize
            } else {
                // No more entries.
                break;
            };

            if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
                // No more room in the buffer.
                break;
            }

            // Safe because we just checked that the iterator contains at least one more item.
            entries.next().unwrap().encode(&mut cursor)?;
        }

        Ok(Rmessage::Readdir(Rreaddir {
            data: Data(cursor.into_inner()),
        }))
    }

    fn fsync(&mut self, fsync: &Tfsync) -> io::Result<Rmessage> {
        let file = self.fids
            .get(&fsync.fid)
            .and_then(|fid| fid.file.as_ref())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        if fsync.datasync == 0 {
            file.sync_all()?;
        } else {
            file.sync_data()?;
        }
        Ok(Rmessage::Fsync)
    }

    fn lock(&mut self, _lock: &Tlock) -> io::Result<Rmessage> {
        // File locking is not supported.
        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
    }
    fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rmessage> {
        // File locking is not supported.
        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
    }

    fn link(&mut self, link: &Tlink) -> io::Result<Rmessage> {
        let newname = Path::new(&link.name);
        let buf = self.fids
            .get(&link.dfid)
            .map(|dfid| dfid.path.to_path_buf())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        let newpath = join_path(buf, newname, &*self.root)?;

        let path = self.fids
            .get(&link.fid)
            .map(|fid| &fid.path)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        fs::hard_link(path, &newpath)?;
        Ok(Rmessage::Link)
    }

    fn mkdir(&mut self, mkdir: &Tmkdir) -> io::Result<Rmessage> {
        let fid = self.fids
            .get(&mkdir.dfid)
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;

        let name = Path::new(&mkdir.name);
        let newpath = join_path(fid.path.to_path_buf(), name, &*self.root)?;

        fs::DirBuilder::new()
            .recursive(false)
            .mode(mkdir.mode & 0o755)
            .create(&newpath)?;

        Ok(Rmessage::Mkdir(Rmkdir {
            qid: metadata_to_qid(&fs::metadata(&newpath)?),
        }))
    }

    fn rename_at(&mut self, rename_at: &Trenameat) -> io::Result<Rmessage> {
        let oldname = Path::new(&rename_at.oldname);
        let oldbuf = self.fids
            .get(&rename_at.olddirfid)
            .map(|dfid| dfid.path.to_path_buf())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        let oldpath = join_path(oldbuf, oldname, &*self.root)?;

        let newname = Path::new(&rename_at.newname);
        let newbuf = self.fids
            .get(&rename_at.newdirfid)
            .map(|dfid| dfid.path.to_path_buf())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        let newpath = join_path(newbuf, newname, &*self.root)?;

        fs::rename(&oldpath, &newpath)?;
        Ok(Rmessage::RenameAt)
    }

    fn unlink_at(&mut self, unlink_at: &Tunlinkat) -> io::Result<Rmessage> {
        let name = Path::new(&unlink_at.name);
        let buf = self.fids
            .get(&unlink_at.dirfd)
            .map(|fid| fid.path.to_path_buf())
            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
        let path = join_path(buf, name, &*self.root)?;

        let md = fs::metadata(&path)?;
        if md.is_dir() && (unlink_at.flags & (libc::AT_REMOVEDIR as u32)) == 0 {
            return Err(io::Error::from_raw_os_error(libc::EISDIR));
        }

        if md.is_dir() {
            fs::remove_dir(&path)?;
        } else {
            fs::remove_file(&path)?;
        }

        Ok(Rmessage::UnlinkAt)
    }
}

#[cfg(test)]
mod tests {
    // Most of the server implementation is tested via integration tests.
    use super::*;

    #[test]
    fn path_joins() {
        let root = PathBuf::from("/a/b/c");
        let path = PathBuf::from("/a/b/c/d/e/f");

        assert_eq!(
            &join_path(path.clone(), "nested", &root).expect("normal"),
            Path::new("/a/b/c/d/e/f/nested")
        );

        let p1 = join_path(path.clone(), "..", &root).expect("parent 1");
        assert_eq!(&p1, Path::new("/a/b/c/d/e/"));

        let p2 = join_path(p1, "..", &root).expect("parent 2");
        assert_eq!(&p2, Path::new("/a/b/c/d/"));

        let p3 = join_path(p2, "..", &root).expect("parent 3");
        assert_eq!(&p3, Path::new("/a/b/c/"));

        let p4 = join_path(p3, "..", &root).expect("parent of root");
        assert_eq!(&p4, Path::new("/a/b/c/"));
    }

    #[test]
    fn invalid_joins() {
        let root = PathBuf::from("/a");
        let path = PathBuf::from("/a/b");

        join_path(path.clone(), ".", &root).expect_err("current directory");
        join_path(path.clone(), "c/d/e", &root).expect_err("too many components");
        join_path(path.clone(), "/c/d/e", &root).expect_err("absolute path");
    }
}