Skip to content

Commit

Permalink
merge #47 into openSUSE/libpathrs:main
Browse files Browse the repository at this point in the history
Aleksa Sarai (6):
  flags: remove ResolveFlags::NO_FOLLOW_TRAILING
  capi: add Root::resolve_nofollow wrapper
  flags: move ResolverFlags to flags module
  lib: remove flags::* re-export
  root: add readlink method
  capi: add bindings for Root::readlink

LGTMs: cyphar
  • Loading branch information
cyphar committed Jul 30, 2024
2 parents 87a3526 + b7e1951 commit 0f4b8cc
Show file tree
Hide file tree
Showing 20 changed files with 540 additions and 274 deletions.
26 changes: 24 additions & 2 deletions contrib/bindings/python/pathrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ def proc_open_raw(base, path, flags):
return WrappedFd(fd)

def proc_readlink(base, path):
# TODO: See if we can merge this with Root.readlink.
path = _cstr(path)
linkbuf_size = 128
while True:
Expand Down Expand Up @@ -284,13 +285,34 @@ def open(cls, path):
def from_file(cls, file):
return cls(file)

def resolve(self, path):
def resolve(self, path, follow_trailing=True):
path = _cstr(path)
fd = libpathrs_so.pathrs_resolve(self.fileno(), path)
if follow_trailing:
fd = libpathrs_so.pathrs_resolve(self.fileno(), path)
else:
fd = libpathrs_so.pathrs_resolve_nofollow(self.fileno(), path)
if fd < 0:
raise Error._fetch(fd) or INTERNAL_ERROR
return Handle(fd)

def readlink(self, path):
path = _cstr(path)
linkbuf_size = 128
while True:
linkbuf = _cbuffer(linkbuf_size)
n = libpathrs_so.pathrs_readlink(self.fileno(), path, linkbuf, linkbuf_size)
if n < 0:
raise Error._fetch(n) or INTERNAL_ERROR
elif n <= linkbuf_size:
return ffi.buffer(linkbuf, linkbuf_size)[:n].decode("latin1")
else:
# The contents were truncated. Unlike readlinkat, pathrs returns
# the size of the link when it checked. So use the returned size
# as a basis for the reallocated size (but in order to avoid a DoS
# where a magic-link is growing by a single byte each iteration,
# make sure we are a fair bit larger).
linkbuf_size += n

def creat(self, path, filemode, mode="r", extra_flags=0):
path = _cstr(path)
flags = convert_mode(mode) | extra_flags
Expand Down
34 changes: 34 additions & 0 deletions go-pathrs/libpathrs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ func pathrsResolve(rootFd uintptr, path string) (uintptr, error) {
return uintptr(fd), fetchError(fd)
}

func pathrsResolveNoFollow(rootFd uintptr, path string) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

fd := C.pathrs_resolve_nofollow(C.int(rootFd), cPath)
return uintptr(fd), fetchError(fd)
}

func pathrsReadlink(rootFd uintptr, path string) (string, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))

size := 128
for {
linkBuf := make([]byte, size)
n := C.pathrs_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
switch {
case int(n) < 0:
return "", fetchError(n)
case int(n) <= len(linkBuf):
return string(linkBuf[:int(n)]), nil
default:
// The contents were truncated. Unlike readlinkat, pathrs returns
// the size of the link when it checked. So use the returned size
// as a basis for the reallocated size (but in order to avoid a DoS
// where a magic-link is growing by a single byte each iteration,
// make sure we are a fair bit larger).
size += int(n)
}
}
}

func pathrsCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
Expand Down Expand Up @@ -146,6 +178,8 @@ func pathrsProcOpen(base pathrsProcBase, path string, flags int) (uintptr, error
}

func pathrsProcReadlink(base pathrsProcBase, path string) (string, error) {
// TODO: See if we can unify this code with pathrsReadlink.

cBase := C.pathrs_proc_base_t(base)

cPath := C.CString(path)
Expand Down
27 changes: 27 additions & 0 deletions go-pathrs/root_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func RootFromFile(file *os.File) (*Root, error) {
// Resolve resolves the given path within the Root's directory tree, and return
// a Handle to the resolved path. The path must already exist, otherwise an
// error will occur.
//
// All symlinks (including trailing symlinks) are followed, but they are
// resolved within the rootfs. If you wish to open a handle to the symlink
// itself, use ResolveNoFollow.
func (r *Root) Resolve(path string) (*Handle, error) {
// TODO: Get the actual name of the handle through /proc/self/fd/...
fakeName, err := randName(32)
Expand All @@ -81,6 +85,29 @@ func (r *Root) Resolve(path string) (*Handle, error) {
})
}

// ResolveNoFollow is effectively an O_NOFOLLOW version of Resolve. Their
// behaviour is identical, except that *trailing* symlinks will not be
// followed. If the final component is a trailing symlink, an O_PATH|O_NOFOLLOW
// handle to the symlink itself is returned.
func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
// TODO: Get the actual name of the handle through /proc/self/fd/...
fakeName, err := randName(32)
if err != nil {
return nil, err
}
// Prefix the root.
fakeName = r.inner.Name() + fakeName

return withFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
handleFd, err := pathrsResolveNoFollow(rootFd, path)
if err != nil {
return nil, err
}
handleFile := os.NewFile(uintptr(handleFd), fakeName)
return &Handle{inner: handleFile}, nil
})
}

// Create creates a file within the Root's directory tree at the given path,
// and returns a handle to the file. The provided mode is used for the new file
// (the process's umask applies).
Expand Down
73 changes: 69 additions & 4 deletions include/pathrs.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ int pathrs_reopen(int fd, int flags);
* Resolve the given path within the rootfs referenced by root_fd. The path
* *must already exist*, otherwise an error will occur.
*
* All symlinks (including trailing symlinks) are followed, but they are
* resolved within the rootfs. If you wish to open a handle to the symlink
* itself, use pathrs_resolve_nofollow().
*
* # Return Value
*
* On success, this function returns an O_PATH file descriptor referencing the
Expand All @@ -145,6 +149,66 @@ int pathrs_reopen(int fd, int flags);
*/
int pathrs_resolve(int root_fd, const char *path);

/**
* pathrs_resolve_nofollow() is effectively an O_NOFOLLOW version of
* pathrs_resolve(). Their behaviour is identical, except that *trailing*
* symlinks will not be followed. If the final component is a trailing symlink,
* an O_PATH|O_NOFOLLOW handle to the symlink itself is returned.
*
* # Return Value
*
* On success, this function returns an O_PATH file descriptor referencing the
* resolved path.
*
* If an error occurs, this function will return a negative error code. To
* retrieve information about the error (such as a string describing the error,
* the system errno(7) value associated with the error, etc), use
* pathrs_errorinfo().
*/
int pathrs_resolve_nofollow(int root_fd, const char *path);

/**
* Get the target of a symlink within the rootfs referenced by root_fd.
*
* NOTE: The returned path is not modified to be "safe" outside of the
* root. You should not use this path for doing further path lookups -- use
* pathrs_resolve() instead.
*
* This method is just shorthand for:
*
* ```c
* int linkfd = pathrs_resolve_nofollow(rootfd, path);
* if (linkfd < 0) {
* liberr = fd; // for use with pathrs_errorinfo()
* goto err;
* }
* copied = readlinkat(linkfd, "", linkbuf, linkbuf_size);
* close(linkfd);
* ```
*
* # Return Value
*
* On success, this function copies the symlink contents to `linkbuf` (up to
* `linkbuf_size` bytes) and returns the full size of the symlink path buffer.
* This function will not copy the trailing NUL byte, and the return size does
* not include the NUL byte. A `NULL` `linkbuf` or invalid `linkbuf_size` are
* treated as zero-size buffers.
*
* NOTE: Unlike readlinkat(2), in the case where linkbuf is too small to
* contain the symlink contents, pathrs_readlink() will return *the number of
* bytes it would have copied if the buffer was large enough*. This matches the
* behaviour of pathrs_proc_readlink().
*
* If an error occurs, this function will return a negative error code. To
* retrieve information about the error (such as a string describing the error,
* the system errno(7) value associated with the error, etc), use
* pathrs_errorinfo().
*/
int pathrs_readlink(int root_fd,
const char *path,
char *linkbuf,
size_t linkbuf_size);

/**
* Rename a path within the rootfs referenced by root_fd. The flags argument is
* identical to the renameat2(2) flags that are supported on the system.
Expand Down Expand Up @@ -306,7 +370,7 @@ int pathrs_proc_open(pathrs_proc_base_t base, const char *path, int flags);
* This function is effectively shorthand for
*
* ```c
* fd = pathrs_proc_readlink(base, path, O_PATH|O_NOFOLLOW);
* fd = pathrs_proc_open(base, path, O_PATH|O_NOFOLLOW);
* if (fd < 0) {
* liberr = fd; // for use with pathrs_errorinfo()
* goto err;
Expand All @@ -323,9 +387,10 @@ int pathrs_proc_open(pathrs_proc_base_t base, const char *path, int flags);
* not include the NUL byte. A `NULL` `linkbuf` or invalid `linkbuf_size` are
* treated as zero-size buffers.
*
* NOTE: Unlike `readlinkat(2)`, in the case where `linkbuf` is too small to
* contain the symlink contents, `pathrs_proc_readlink` will return *the number
* of bytes it would have copied if the buffer was large enough*.
* NOTE: Unlike readlinkat(2), in the case where linkbuf is too small to
* contain the symlink contents, pathrs_proc_readlink() will return *the number
* of bytes it would have copied if the buffer was large enough*. This matches
* the behaviour of pathrs_readlink().
*
* If an error occurs, this function will return a negative error code. To
* retrieve information about the error (such as a string describing the error,
Expand Down
75 changes: 74 additions & 1 deletion src/capi/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use std::{
},
};

use libc::{c_char, c_int, c_uint, dev_t};
use libc::{c_char, c_int, c_uint, dev_t, size_t};
use snafu::ResultExt;

/// Open a root handle.
Expand Down Expand Up @@ -106,6 +106,10 @@ pub extern "C" fn pathrs_reopen(fd: RawFd, flags: c_int) -> RawFd {
/// Resolve the given path within the rootfs referenced by root_fd. The path
/// *must already exist*, otherwise an error will occur.
///
/// All symlinks (including trailing symlinks) are followed, but they are
/// resolved within the rootfs. If you wish to open a handle to the symlink
/// itself, use pathrs_resolve_nofollow().
///
/// # Return Value
///
/// On success, this function returns an O_PATH file descriptor referencing the
Expand All @@ -122,6 +126,75 @@ pub extern "C" fn pathrs_resolve(root_fd: RawFd, path: *const c_char) -> RawFd {
})
}

/// pathrs_resolve_nofollow() is effectively an O_NOFOLLOW version of
/// pathrs_resolve(). Their behaviour is identical, except that *trailing*
/// symlinks will not be followed. If the final component is a trailing symlink,
/// an O_PATH|O_NOFOLLOW handle to the symlink itself is returned.
///
/// # Return Value
///
/// On success, this function returns an O_PATH file descriptor referencing the
/// resolved path.
///
/// If an error occurs, this function will return a negative error code. To
/// retrieve information about the error (such as a string describing the error,
/// the system errno(7) value associated with the error, etc), use
/// pathrs_errorinfo().
#[no_mangle]
pub extern "C" fn pathrs_resolve_nofollow(root_fd: RawFd, path: *const c_char) -> RawFd {
ret::with_fd(root_fd, |root: &mut Root| {
root.resolve_nofollow(utils::parse_path(path)?)
})
}

/// Get the target of a symlink within the rootfs referenced by root_fd.
///
/// NOTE: The returned path is not modified to be "safe" outside of the
/// root. You should not use this path for doing further path lookups -- use
/// pathrs_resolve() instead.
///
/// This method is just shorthand for:
///
/// ```c
/// int linkfd = pathrs_resolve_nofollow(rootfd, path);
/// if (linkfd < 0) {
/// liberr = fd; // for use with pathrs_errorinfo()
/// goto err;
/// }
/// copied = readlinkat(linkfd, "", linkbuf, linkbuf_size);
/// close(linkfd);
/// ```
///
/// # Return Value
///
/// On success, this function copies the symlink contents to `linkbuf` (up to
/// `linkbuf_size` bytes) and returns the full size of the symlink path buffer.
/// This function will not copy the trailing NUL byte, and the return size does
/// not include the NUL byte. A `NULL` `linkbuf` or invalid `linkbuf_size` are
/// treated as zero-size buffers.
///
/// NOTE: Unlike readlinkat(2), in the case where linkbuf is too small to
/// contain the symlink contents, pathrs_readlink() will return *the number of
/// bytes it would have copied if the buffer was large enough*. This matches the
/// behaviour of pathrs_proc_readlink().
///
/// If an error occurs, this function will return a negative error code. To
/// retrieve information about the error (such as a string describing the error,
/// the system errno(7) value associated with the error, etc), use
/// pathrs_errorinfo().
#[no_mangle]
pub extern "C" fn pathrs_readlink(
root_fd: RawFd,
path: *const c_char,
linkbuf: *mut c_char,
linkbuf_size: size_t,
) -> RawFd {
ret::with_fd(root_fd, |root: &mut Root| {
let link_target = root.readlink(utils::parse_path(path)?)?;
utils::copy_path_into_buffer(link_target, linkbuf, linkbuf_size)
})
}

/// Rename a path within the rootfs referenced by root_fd. The flags argument is
/// identical to the renameat2(2) flags that are supported on the system.
///
Expand Down
Loading

0 comments on commit 0f4b8cc

Please sign in to comment.