fslock/
unix.rs

1use crate::{EitherOsStr, IntoOsString, ToOsStr};
2use core::{fmt, mem::transmute, ptr::NonNull, slice, str};
3
4#[cfg(feature = "std")]
5use std::{ffi, os::unix::ffi::OsStrExt};
6
7#[cfg(not(feature = "std"))]
8extern "C" {
9    /// Yeah, I had to copy this from std
10    #[cfg(not(target_os = "dragonfly"))]
11    #[cfg_attr(
12        any(
13            target_os = "linux",
14            target_os = "emscripten",
15            target_os = "fuchsia",
16            target_os = "l4re"
17        ),
18        link_name = "__errno_location"
19    )]
20    #[cfg_attr(
21        any(
22            target_os = "netbsd",
23            target_os = "openbsd",
24            target_os = "android",
25            target_os = "redox",
26            target_env = "newlib"
27        ),
28        link_name = "__errno"
29    )]
30    #[cfg_attr(target_os = "solaris", link_name = "___errno")]
31    #[cfg_attr(
32        any(target_os = "macos", target_os = "ios", target_os = "freebsd"),
33        link_name = "__error"
34    )]
35    #[cfg_attr(target_os = "haiku", link_name = "_errnop")]
36    fn errno_location() -> *mut libc::c_int;
37}
38
39#[cfg(not(feature = "std"))]
40fn errno() -> libc::c_int {
41    unsafe { *errno_location() }
42}
43
44#[cfg(feature = "std")]
45fn errno() -> libc::c_int {
46    Error::last_os_error().raw_os_error().unwrap_or(0) as libc::c_int
47}
48
49/// A type representing file descriptor on Unix.
50pub type FileDesc = libc::c_int;
51
52/// A type representing Process ID on Unix.
53pub type Pid = libc::pid_t;
54
55#[cfg(feature = "std")]
56/// An IO error.
57pub type Error = std::io::Error;
58
59#[cfg(not(feature = "std"))]
60#[derive(Debug)]
61/// An IO error. Without std, you can only get a message or an OS error code.
62pub struct Error {
63    code: i32,
64}
65
66#[cfg(not(feature = "std"))]
67impl Error {
68    /// Creates an error from a raw OS error code.
69    pub fn from_raw_os_error(code: i32) -> Self {
70        Self { code }
71    }
72
73    /// Creates an error from the last OS error code.
74    pub fn last_os_error() -> Error {
75        Self::from_raw_os_error(errno() as i32)
76    }
77
78    /// Raw OS error code. Returns option for compatibility with std.
79    pub fn raw_os_error(&self) -> Option<i32> {
80        Some(self.code)
81    }
82}
83
84#[cfg(not(feature = "std"))]
85impl fmt::Display for Error {
86    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
87        let msg_ptr = unsafe { libc::strerror(self.code as libc::c_int) };
88        let len = unsafe { libc::strlen(msg_ptr) };
89        let slice = unsafe { slice::from_raw_parts(msg_ptr, len) };
90        write!(fmt, "{}", unsafe { OsStr::from_slice(slice) })?;
91        Ok(())
92    }
93}
94
95/// Owned allocation of an OS-native string.
96pub struct OsString {
97    alloc: NonNull<libc::c_char>,
98    /// Length without the nul-byte.
99    len: usize,
100}
101
102impl Drop for OsString {
103    fn drop(&mut self) {
104        let ptr = self.alloc.as_ptr() as *mut libc::c_void;
105        unsafe { libc::free(ptr) }
106    }
107}
108
109impl AsRef<OsStr> for OsString {
110    fn as_ref(&self) -> &OsStr {
111        unsafe {
112            OsStr::from_slice(slice::from_raw_parts(
113                self.alloc.as_ptr(),
114                self.len,
115            ))
116        }
117    }
118}
119
120/// Borrowed allocation of an OS-native string.
121#[repr(transparent)]
122pub struct OsStr {
123    bytes: [libc::c_char],
124}
125
126impl OsStr {
127    /// Unsafe cause sequence needs to end with 0.
128    unsafe fn from_slice(slice: &[libc::c_char]) -> &Self {
129        transmute(slice)
130    }
131}
132
133impl fmt::Debug for OsStr {
134    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
135        let mut first = false;
136        write!(fmt, "[")?;
137
138        for &signed in &self.bytes {
139            let byte = signed as u8;
140            if first {
141                first = false;
142            } else {
143                write!(fmt, ", ")?;
144            }
145            if byte.is_ascii() {
146                write!(fmt, "{:?}", char::from(byte))?;
147            } else {
148                write!(fmt, "'\\x{:x}'", byte)?;
149            }
150        }
151
152        write!(fmt, "]")?;
153        Ok(())
154    }
155}
156
157impl fmt::Display for OsStr {
158    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
159        let ptr = self.bytes.as_ptr();
160        let len = self.bytes.len();
161        let slice = unsafe { slice::from_raw_parts(ptr as _, len) };
162
163        let mut sub = slice;
164
165        while sub.len() > 0 {
166            match str::from_utf8(sub) {
167                Ok(string) => {
168                    write!(fmt, "{}", string)?;
169                    sub = &[];
170                },
171                Err(err) => {
172                    let string = str::from_utf8(&sub[.. err.valid_up_to()])
173                        .expect("Inconsistent utf8 error");
174                    write!(fmt, "{}�", string,)?;
175
176                    sub = &sub[err.valid_up_to() + 1 ..];
177                },
178            }
179        }
180
181        Ok(())
182    }
183}
184
185impl<'str> IntoOsString for &'str OsStr {
186    fn into_os_string(self) -> Result<OsString, Error> {
187        let len = self.bytes.len();
188        let alloc = unsafe { libc::malloc(len + 1) };
189        let alloc = match NonNull::new(alloc as *mut libc::c_char) {
190            Some(alloc) => alloc,
191            None => {
192                return Err(Error::last_os_error());
193            },
194        };
195        unsafe {
196            libc::memcpy(
197                alloc.as_ptr() as *mut libc::c_void,
198                self.bytes.as_ptr() as *const libc::c_void,
199                len + 1,
200            );
201        }
202
203        Ok(OsString { alloc, len })
204    }
205}
206
207impl ToOsStr for str {
208    fn to_os_str(&self) -> Result<EitherOsStr, Error> {
209        make_os_str(self.as_bytes())
210    }
211}
212
213#[cfg(feature = "std")]
214impl ToOsStr for ffi::OsStr {
215    fn to_os_str(&self) -> Result<EitherOsStr, Error> {
216        make_os_str(self.as_bytes())
217    }
218}
219
220/// Path must not contain a nul-byte in the middle, but a nul-byte in the end
221/// (and only in the end) is allowed, which in this case no extra allocation
222/// will be made. Otherwise, an extra allocation is made.
223fn make_os_str(slice: &[u8]) -> Result<EitherOsStr, Error> {
224    if let Some((&last, init)) = slice.split_last() {
225        if init.contains(&0) {
226            panic!("Path to file cannot contain nul-byte in the middle");
227        }
228        if last == 0 {
229            let str = unsafe { OsStr::from_slice(transmute(slice)) };
230            return Ok(EitherOsStr::Borrowed(str));
231        }
232    }
233
234    let alloc = unsafe { libc::malloc(slice.len() + 1) };
235    let alloc = match NonNull::new(alloc as *mut libc::c_char) {
236        Some(alloc) => alloc,
237        None => {
238            return Err(Error::last_os_error());
239        },
240    };
241    unsafe {
242        libc::memcpy(
243            alloc.as_ptr() as *mut libc::c_void,
244            slice.as_ptr() as *const libc::c_void,
245            slice.len(),
246        );
247        *alloc.as_ptr().add(slice.len()) = 0;
248    }
249
250    Ok(EitherOsStr::Owned(OsString { alloc, len: slice.len() }))
251}
252
253/// Returns the ID of the current process.
254pub fn pid() -> Pid {
255    unsafe { libc::getpid() }
256}
257
258/// Opens a file with only purpose of locking it. Creates it if it does not
259/// exist. Path must not contain a nul-byte in the middle, but a nul-byte in the
260/// end (and only in the end) is allowed, which in this case no extra allocation
261/// will be made. Otherwise, an extra allocation is made.
262pub fn open(path: &OsStr) -> Result<FileDesc, Error> {
263    let fd = unsafe {
264        libc::open(
265            path.bytes.as_ptr(),
266            libc::O_RDWR | libc::O_CLOEXEC | libc::O_CREAT,
267            (libc::S_IRUSR | libc::S_IWUSR | libc::S_IRGRP | libc::S_IROTH)
268                as libc::c_int,
269        )
270    };
271
272    if fd >= 0 {
273        Ok(fd)
274    } else {
275        Err(Error::last_os_error())
276    }
277}
278
279/// Writes data into the given open file.
280pub fn write(fd: FileDesc, mut bytes: &[u8]) -> Result<(), Error> {
281    while bytes.len() > 0 {
282        let written = unsafe {
283            libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len())
284        };
285        if written < 0 && errno() != libc::EAGAIN {
286            return Err(Error::last_os_error());
287        }
288        bytes = &bytes[written as usize ..];
289    }
290
291    Ok(())
292}
293
294pub fn fsync(fd: FileDesc) -> Result<(), Error> {
295    let result = unsafe { libc::fsync(fd) };
296
297    if result >= 0 {
298        Ok(())
299    } else {
300        Err(Error::last_os_error())
301    }
302}
303
304/// Truncates the file referenced by the given file descriptor and seeks it to
305/// the start.
306pub fn truncate(fd: FileDesc) -> Result<(), Error> {
307    let res = unsafe { libc::lseek(fd, 0, libc::SEEK_SET) };
308    if res < 0 {
309        return Err(Error::last_os_error());
310    }
311
312    let res = unsafe { libc::ftruncate(fd, 0) };
313    if res < 0 {
314        Err(Error::last_os_error())
315    } else {
316        Ok(())
317    }
318}
319
320/// Tries to lock a file and blocks until it is possible to lock.
321pub fn lock(fd: FileDesc) -> Result<(), Error> {
322    let res = unsafe { libc::flock(fd, libc::LOCK_EX) };
323    if res >= 0 {
324        Ok(())
325    } else {
326        Err(Error::last_os_error())
327    }
328}
329
330/// Tries to lock a file but returns as soon as possible if already locked.
331pub fn try_lock(fd: FileDesc) -> Result<bool, Error> {
332    let res = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
333    if res >= 0 {
334        Ok(true)
335    } else {
336        let err = errno();
337        if err == libc::EWOULDBLOCK || err == libc::EINTR {
338            Ok(false)
339        } else {
340            Err(Error::from_raw_os_error(err as i32))
341        }
342    }
343}
344
345/// Unlocks the file.
346pub fn unlock(fd: FileDesc) -> Result<(), Error> {
347    let res = unsafe { libc::flock(fd, libc::LOCK_UN) };
348    if res >= 0 {
349        Ok(())
350    } else {
351        Err(Error::last_os_error())
352    }
353}
354
355/// Closes the file.
356pub fn close(fd: FileDesc) {
357    unsafe { libc::close(fd) };
358}