[go: up one dir, main page]

reis/
eis.rs

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
//! Server-side EI protocol.
//!
//! Use [Listener] to create a socket, listening for clients creating a new
//! [Context].

use std::{
    env, io,
    os::unix::{
        io::{AsFd, AsRawFd, BorrowedFd, RawFd},
        net::{UnixListener, UnixStream},
    },
    path::{Path, PathBuf},
};

use crate::{util, wire::Backend, PendingRequestResult};

// Re-export generate bindings
pub use crate::eiproto_eis::*;

#[derive(Debug)]
pub struct Listener {
    listener: util::UnlinkOnDrop<UnixListener>,
    _lock: Option<util::LockFile>,
}

impl Listener {
    // TODO Use a lock here
    pub fn bind(path: &Path) -> io::Result<Self> {
        Self::bind_inner(PathBuf::from(path), None)
    }

    // XXX result type?
    // Error if XDG_RUNTIME_DIR not set?
    pub fn bind_auto() -> io::Result<Option<Self>> {
        let xdg_dir = if let Some(var) = env::var_os("XDG_RUNTIME_DIR") {
            PathBuf::from(var)
        } else {
            return Ok(None);
        };
        for i in 1..33 {
            let lock_path = xdg_dir.join(format!("eis-{i}.lock"));
            let Some(lock_file) = util::LockFile::new(lock_path)? else {
                // Already locked, continue to next number
                continue;
            };
            let sock_path = xdg_dir.join(format!("eis-{i}"));
            return Self::bind_inner(sock_path, Some(lock_file)).map(Some);
        }
        Ok(None)
    }

    fn bind_inner(path: PathBuf, lock: Option<util::LockFile>) -> io::Result<Self> {
        let listener = UnixListener::bind(&path)?;
        listener.set_nonblocking(true)?;
        let listener = util::UnlinkOnDrop::new(listener, path);
        Ok(Self {
            listener,
            _lock: lock,
        })
    }

    pub fn accept(&self) -> io::Result<Option<Context>> {
        match self.listener.accept() {
            Ok((socket, _)) => Ok(Some(Context::new(socket)?)),
            Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
            Err(e) => Err(e),
        }
    }
}

impl AsFd for Listener {
    fn as_fd(&self) -> BorrowedFd {
        self.listener.as_fd()
    }
}

impl AsRawFd for Listener {
    fn as_raw_fd(&self) -> RawFd {
        self.listener.as_raw_fd()
    }
}

#[derive(Clone, Debug)]
pub struct Context(pub(crate) Backend);

impl AsFd for Context {
    fn as_fd(&self) -> BorrowedFd {
        self.0.as_fd()
    }
}

impl AsRawFd for Context {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_fd().as_raw_fd()
    }
}

impl Context {
    pub fn new(socket: UnixStream) -> io::Result<Self> {
        Ok(Self(Backend::new(socket, false)?))
    }

    /// Read any pending data on socket into buffer
    pub fn read(&self) -> io::Result<usize> {
        self.0.read()
    }

    // XXX iterator
    pub fn pending_request(&self) -> Option<PendingRequestResult<Request>> {
        self.0.pending(Request::parse)
    }

    pub fn handshake(&self) -> handshake::Handshake {
        self.0.object_for_id(0).unwrap().downcast_unchecked()
    }

    pub fn flush(&self) -> rustix::io::Result<()> {
        self.0.flush()
    }
}

#[doc(hidden)]
pub trait Interface: crate::wire::Interface {}