Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

readlink and readlinkat implementation on RawPOSIX / Wasmtime / glibc #76

Merged
merged 26 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pre-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

mkdir -p /home/lind-wasm/src/RawPOSIX/tmp/testfiles/
touch /home/lind-wasm/src/RawPOSIX/tmp/testfiles/readlinkfile.txt
ln -s src/RawPOSIX/tmp/testfiles/readlinkfile.txt src/RawPOSIX/tmp/testfiles/readlinkfile
34 changes: 34 additions & 0 deletions src/RawPOSIX/src/safeposix/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ const FSYNC_SYSCALL: i32 = 162;
const FDATASYNC_SYSCALL: i32 = 163;
const SYNC_FILE_RANGE: i32 = 164;

const READLINK_SYSCALL: i32 = 165;
const READLINKAT_SYSCALL: i32 = 166;

const WRITEV_SYSCALL: i32 = 170;

const CLONE_SYSCALL: i32 = 171;
Expand All @@ -119,6 +122,7 @@ const WAITPID_SYSCALL: i32 = 173;

const NANOSLEEP_TIME64_SYSCALL : i32 = 181;


use std::ffi::CString;
use std::ffi::CStr;
use super::cage::*;
Expand Down Expand Up @@ -1050,6 +1054,36 @@ pub fn lind_syscall_api(
.waitpid_syscall(pid, &mut status, options)
}

READLINK_SYSCALL => {
let path_ptr = (start_address + arg1) as *const u8;
let path = unsafe {
CStr::from_ptr(path_ptr as *const i8).to_str().unwrap()
};

let buf = (start_address + arg2) as *mut u8;

let buflen = arg3 as usize;

interface::cagetable_getref(cageid)
.readlink_syscall(path, buf, buflen)
}

READLINKAT_SYSCALL => {
let fd = arg1 as i32;

let path_ptr = (start_address + arg2) as *const u8;
let path = unsafe {
CStr::from_ptr(path_ptr as *const i8).to_str().unwrap()
};

let buf = (start_address + arg3) as *mut u8;

let buflen = arg4 as usize;

interface::cagetable_getref(cageid)
.readlinkat_syscall(fd, path, buf, buflen)
}

_ => -1, // Return -1 for unknown syscalls
};
ret
Expand Down
181 changes: 181 additions & 0 deletions src/RawPOSIX/src/safeposix/syscalls/fs_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,187 @@ impl Cage {

}

//------------------------------------READLINK and READLINKAT SYSCALL------------------------------------
/*
* The return value of the readlink syscall indicates the number of bytes written into the buf and -1 if
* error. The contents of the buf represent the file path that the symbolic link points to. Since the file
* path perspectives differ between the user application and the host Linux, the readlink implementation
* requires handling the paths for both the input passed to the Rust kernel libc and the output buffer
* returned by the kernel libc.
*
* For the input path, the transformation is straightforward: we prepend the LIND_ROOT prefix to convert
* the user's relative path into a host-compatible absolute path.
* However, for the output buffer, we need to first verify whether the path written to buf is an absolute
* path. If it is not, we prepend the current working directory to make it absolute. Next, we remove the
* LIND_ROOT prefix to adjust the path to the user's perspective. Finally, we truncate the adjusted result
* to fit within the user-provided buflen, ensuring compliance with the behavior described in the Linux
* readlink man page, which states that truncation is performed silently if the buffer is too small.
*/
pub fn readlink_syscall(
&self,
path: &str,
buf: *mut u8,
buflen: usize,
) -> i32 {
// Convert the path from relative path (lind-wasm perspective) to real kernel path (host kernel
// perspective)
let relpath = normpath(convpath(path), self);
let relative_path = relpath.to_str().unwrap();
let full_path = format!("{}{}", LIND_ROOT, relative_path);
let c_path = CString::new(full_path).unwrap();

// Call libc::readlink to get the original symlink target
let libc_buflen = buflen + LIND_ROOT.len();
let mut libc_buf = vec![0u8; libc_buflen];
let libcret = unsafe {
libc::readlink(c_path.as_ptr(), libc_buf.as_mut_ptr() as *mut c_char, libc_buflen)
};

if libcret < 0 {
let errno = get_errno();
return handle_errno(errno, "readlink");
}

// Convert the result from readlink to a Rust string
let libcbuf_str = unsafe {
CStr::from_ptr(libc_buf.as_ptr() as *const c_char)
}.to_str().unwrap();

// Use libc::getcwd to get the current working directory
let mut cwd_buf = vec![0u8; 4096];
let cwd_ptr = unsafe { libc::getcwd(cwd_buf.as_mut_ptr() as *mut c_char, cwd_buf.len()) };
if cwd_ptr.is_null() {
let errno = get_errno();
return handle_errno(errno, "getcwd");
}

let pwd = unsafe { CStr::from_ptr(cwd_buf.as_ptr() as *const c_char) }
.to_str()
.unwrap();

// Adjust the result to user perspective
// Verify if libcbuf_str starts with the current working directory (pwd)
let adjusted_result = if libcbuf_str.starts_with(pwd) {
libcbuf_str.to_string()
} else {
format!("{}/{}", pwd, libcbuf_str)
};
let new_root = format!("{}/", LIND_ROOT);
let final_result = adjusted_result.strip_prefix(&new_root).unwrap_or(&adjusted_result);

// Check the length and copy the appropriate amount of data to buf
let bytes_to_copy = std::cmp::min(buflen, final_result.len());
unsafe {
std::ptr::copy_nonoverlapping(final_result.as_ptr(), buf, bytes_to_copy);
}

bytes_to_copy as i32
}

/*
* The readlinkat syscall builds upon the readlink syscall, with additional handling for the provided fd.
* There are two main cases to consider:
*
* When fd is the special value AT_FDCWD:
* In this case, we first retrieve the current working directory path. We then append the user-provided path
* to this directory path to create a complete path. After this, the handling is identical to the readlink
* syscall. Therefore, the implementation delegates the underlying work to the readlink syscall.
*
* One notable point is that when fd = AT_FDCWD, there is no need to convert the virtual fd. Due to Rust's
* variable scoping rules and for safety considerations (we must use the predefined fdtable API). This results
* in approximately four lines of repetitive code during the path conversion step. If we plan to optimize
* the implementation in the future, we can consider abstracting this step into a reusable function to avoid
* redundancy.
*
* When fd is a directory fd:
* Handling this case is difficult without access to kernel-space code. In Linux, there is no syscall that
* provides a method to resolve the directory path corresponding to a given dirfd. The Linux kernel handles
* this step by utilizing its internal dentry data structure, which is not accessible from user space.
* Therefore, in the RawPOSIX implementation, we assume that all paths are absolute to simplify the resolution
* process.
*
*/
pub fn readlinkat_syscall(
&self,
virtual_fd: i32,
path: &str,
buf: *mut u8,
buflen: usize,
) -> i32 {
let mut libcret;
let mut path = path.to_string();
let libc_buflen = buflen + LIND_ROOT.len();
let mut libc_buf = vec![0u8; libc_buflen];
if virtual_fd == libc::AT_FDCWD {
// Check if the fd is AT_FDCWD
let cwd_container = self.cwd.read();
path = format!("{}/{}", cwd_container.to_str().unwrap(), path);
// Convert the path from relative path (lind-wasm perspective) to real kernel path (host kernel
// perspective)
let relpath = normpath(convpath(&path), self);
let relative_path = relpath.to_str().unwrap();
let full_path = format!("{}{}", LIND_ROOT, relative_path);
let c_path = CString::new(full_path).unwrap();

libcret = unsafe {
libc::readlink(c_path.as_ptr(), libc_buf.as_mut_ptr() as *mut c_char, libc_buflen)
};

} else {
// Convert the virtual fd into real kernel fd and handle the error case
let wrappedvfd = fdtables::translate_virtual_fd(self.cageid, virtual_fd as u64);
if wrappedvfd.is_err() {
return syscall_error(Errno::EBADF, "readlinkat", "Bad File Descriptor");
}
let vfd = wrappedvfd.unwrap();
// Convert the path from relative path (lind-wasm perspective) to real kernel path (host kernel
// perspective)
let relpath = normpath(convpath(&path), self);
let relative_path = relpath.to_str().unwrap();
let full_path = format!("{}{}", LIND_ROOT, relative_path);
let c_path = CString::new(full_path).unwrap();

libcret = unsafe {
libc::readlinkat(vfd.underfd as i32, c_path.as_ptr(), libc_buf.as_mut_ptr() as *mut c_char, libc_buflen)
};
}

if libcret < 0 {
let errno = get_errno();
return handle_errno(errno, "readlinkat");
}

// Convert the result from readlink to a Rust string
let libcbuf_str = unsafe {
CStr::from_ptr(libc_buf.as_ptr() as *const c_char)
}.to_str().unwrap();

// Use libc::getcwd to get the current working directory
let mut cwd_buf = vec![0u8; 4096];
let cwd_ptr = unsafe { libc::getcwd(cwd_buf.as_mut_ptr() as *mut c_char, cwd_buf.len()) };
if cwd_ptr.is_null() {
let errno = get_errno();
return handle_errno(errno, "getcwd");
}

let pwd = unsafe { CStr::from_ptr(cwd_buf.as_ptr() as *const c_char) }
.to_str()
.unwrap();

// Adjust the result to user perspective
let adjusted_result = format!("{}/{}", pwd, libcbuf_str);
let new_root = format!("{}/", LIND_ROOT);
let final_result = adjusted_result.strip_prefix(&new_root).unwrap_or(&adjusted_result);

// Check the length and copy the appropriate amount of data to buf
let bytes_to_copy = std::cmp::min(buflen, final_result.len());
unsafe {
std::ptr::copy_nonoverlapping(final_result.as_ptr(), buf, bytes_to_copy);
}

bytes_to_copy as i32
}

//------------------------------------WRITE SYSCALL------------------------------------
/*
* Get the kernel fd with provided virtual fd first
Expand Down
4 changes: 2 additions & 2 deletions src/glibc/include/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ extern __typeof (getlogin_r) __getlogin_r __nonnull ((1));
//libc_hidden_proto (seteuid)
//libc_hidden_proto (setegid)
//libc_hidden_proto (tcgetpgrp)
//libc_hidden_proto (readlinkat)
//libc_hidden_proto (fsync)
//libc_hidden_proto (fdatasync)

Expand Down Expand Up @@ -153,7 +152,8 @@ extern int __symlink (const char *__from, const char *__to);
extern int __symlinkat (const char *__from, int __fd, const char *__to);
extern ssize_t __readlink (const char *__path, char *__buf, size_t __len)
attribute_hidden;
extern ssize_t __readlinkat (int __fd, const char *__file_name, char *__buf, size_t __len);
extern ssize_t __readlinkat (int __fd, const char *__file_name, char *__buf, size_t __len)
attribute_hidden;
extern int __unlink (const char *__name) attribute_hidden;
extern int __unlinkat (int __fd, const char *__name, int __flag);
extern int __gethostname (char *__name, size_t __len) attribute_hidden;
Expand Down
1 change: 1 addition & 0 deletions src/glibc/lind_syscall/lind_syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ int lind_syscall (unsigned int callnumber, unsigned long long callname, unsigned
if(ret < 0)
{
errno = -ret;
return -1;
}
else
{
Expand Down
1 change: 1 addition & 0 deletions src/glibc/sysdeps/unix/syscalls.list
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ profil - profil i:piii __profil profil
ptrace - ptrace i:iiii ptrace
read - read Ci:ibU __libc_read __read read
readlink - readlink i:spU __readlink readlink
readlinkat - readlinkat i:spU __libc_readlinkat readlinkat
readv - readv Ci:ipi __readv readv
reboot - reboot i:i reboot
recv - recv Ci:ibUi __libc_recv recv
Expand Down
24 changes: 19 additions & 5 deletions src/glibc/sysdeps/unix/sysv/linux/i386/i686/readlinkat.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sysdep-cancel.h>
#include <syscall-template.h>

ssize_t __GI_readlinkat (int __fd, const char *__file_name, char *__buf, size_t __len)
/* Read the contents of the symbolic link PATH into no more than
LEN bytes of BUF. The contents are not null-terminated.
Returns the number of characters read, or -1 for errors. */
/*
* Edit Note:
* In lind-wasm, we have separately implemented readlink and readlinkat, so we modified this part of the code to handle them individually.
*/
ssize_t
__libc_readlinkat (int fd, const char *path, char *buf, size_t len)
{
return 0;
int ret = MAKE_SYSCALL(166, "syscall|readlinkat",(uint64_t) fd, (uint64_t) path, (uint64_t)(uintptr_t) buf, (uint64_t) len, NOTUSED, NOTUSED);
if (ret < 0) {
errno = -ret;
return -1;
}
return ret;
}

weak_alias(__GI_readlinkat, _readlinkat)
weak_alias(__libc_readlinkat, readlinkat)
19 changes: 13 additions & 6 deletions src/glibc/sysdeps/unix/sysv/linux/readlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@

#include <unistd.h>
#include <fcntl.h>
#include <sysdep.h>
#include <sysdep-cancel.h>
#include <syscall-template.h>
#include <errno.h>

/* Read the contents of the symbolic link PATH into no more than
LEN bytes of BUF. The contents are not null-terminated.
Returns the number of characters read, or -1 for errors. */
/*
* Edit Note:
* In lind-wasm, we have separately implemented readlink and readlinkat, so we modified this part of the code to handle them individually.
*/
ssize_t
__readlink (const char *path, char *buf, size_t len)
{
#ifdef __NR_readlink
return INLINE_SYSCALL_CALL (readlink, path, buf, len);
#else
return INLINE_SYSCALL_CALL (readlinkat, AT_FDCWD, path, buf, len);
#endif
int ret = MAKE_SYSCALL(165, "syscall|readlink", (uint64_t) path, (uint64_t)(uintptr_t) buf, (uint64_t) len, NOTUSED, NOTUSED, NOTUSED);
if (ret < 0) {
errno = -ret;
return -1;
}
return ret;
}
weak_alias (__readlink, readlink)
Loading