Skip to content

Commit 75b1080

Browse files
authored
readlink and readlinkat implementation on RawPOSIX / Wasmtime / glibc (#76)
1 parent 1300b19 commit 75b1080

File tree

10 files changed

+364
-13
lines changed

10 files changed

+364
-13
lines changed

pre-test.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
mkdir -p /home/lind-wasm/src/RawPOSIX/tmp/testfiles/
4+
touch /home/lind-wasm/src/RawPOSIX/tmp/testfiles/readlinkfile.txt
5+
ln -s src/RawPOSIX/tmp/testfiles/readlinkfile.txt src/RawPOSIX/tmp/testfiles/readlinkfile

src/RawPOSIX/src/safeposix/dispatcher.rs

+34
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ const FSYNC_SYSCALL: i32 = 162;
111111
const FDATASYNC_SYSCALL: i32 = 163;
112112
const SYNC_FILE_RANGE: i32 = 164;
113113

114+
const READLINK_SYSCALL: i32 = 165;
115+
const READLINKAT_SYSCALL: i32 = 166;
116+
114117
const WRITEV_SYSCALL: i32 = 170;
115118

116119
const CLONE_SYSCALL: i32 = 171;
@@ -120,6 +123,7 @@ const WAITPID_SYSCALL: i32 = 173;
120123
const NANOSLEEP_TIME64_SYSCALL : i32 = 181;
121124
const CLOCK_GETTIME_SYSCALL : i32 = 191;
122125

126+
123127
use std::ffi::CString;
124128
use std::ffi::CStr;
125129
use super::cage::*;
@@ -1059,6 +1063,36 @@ pub fn lind_syscall_api(
10591063
.waitpid_syscall(pid, &mut status, options)
10601064
}
10611065

1066+
READLINK_SYSCALL => {
1067+
let path_ptr = (start_address + arg1) as *const u8;
1068+
let path = unsafe {
1069+
CStr::from_ptr(path_ptr as *const i8).to_str().unwrap()
1070+
};
1071+
1072+
let buf = (start_address + arg2) as *mut u8;
1073+
1074+
let buflen = arg3 as usize;
1075+
1076+
interface::cagetable_getref(cageid)
1077+
.readlink_syscall(path, buf, buflen)
1078+
}
1079+
1080+
READLINKAT_SYSCALL => {
1081+
let fd = arg1 as i32;
1082+
1083+
let path_ptr = (start_address + arg2) as *const u8;
1084+
let path = unsafe {
1085+
CStr::from_ptr(path_ptr as *const i8).to_str().unwrap()
1086+
};
1087+
1088+
let buf = (start_address + arg3) as *mut u8;
1089+
1090+
let buflen = arg4 as usize;
1091+
1092+
interface::cagetable_getref(cageid)
1093+
.readlinkat_syscall(fd, path, buf, buflen)
1094+
}
1095+
10621096
_ => -1, // Return -1 for unknown syscalls
10631097
};
10641098
ret

src/RawPOSIX/src/safeposix/syscalls/fs_calls.rs

+181
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,187 @@ impl Cage {
370370

371371
}
372372

373+
//------------------------------------READLINK and READLINKAT SYSCALL------------------------------------
374+
/*
375+
* The return value of the readlink syscall indicates the number of bytes written into the buf and -1 if
376+
* error. The contents of the buf represent the file path that the symbolic link points to. Since the file
377+
* path perspectives differ between the user application and the host Linux, the readlink implementation
378+
* requires handling the paths for both the input passed to the Rust kernel libc and the output buffer
379+
* returned by the kernel libc.
380+
*
381+
* For the input path, the transformation is straightforward: we prepend the LIND_ROOT prefix to convert
382+
* the user's relative path into a host-compatible absolute path.
383+
* However, for the output buffer, we need to first verify whether the path written to buf is an absolute
384+
* path. If it is not, we prepend the current working directory to make it absolute. Next, we remove the
385+
* LIND_ROOT prefix to adjust the path to the user's perspective. Finally, we truncate the adjusted result
386+
* to fit within the user-provided buflen, ensuring compliance with the behavior described in the Linux
387+
* readlink man page, which states that truncation is performed silently if the buffer is too small.
388+
*/
389+
pub fn readlink_syscall(
390+
&self,
391+
path: &str,
392+
buf: *mut u8,
393+
buflen: usize,
394+
) -> i32 {
395+
// Convert the path from relative path (lind-wasm perspective) to real kernel path (host kernel
396+
// perspective)
397+
let relpath = normpath(convpath(path), self);
398+
let relative_path = relpath.to_str().unwrap();
399+
let full_path = format!("{}{}", LIND_ROOT, relative_path);
400+
let c_path = CString::new(full_path).unwrap();
401+
402+
// Call libc::readlink to get the original symlink target
403+
let libc_buflen = buflen + LIND_ROOT.len();
404+
let mut libc_buf = vec![0u8; libc_buflen];
405+
let libcret = unsafe {
406+
libc::readlink(c_path.as_ptr(), libc_buf.as_mut_ptr() as *mut c_char, libc_buflen)
407+
};
408+
409+
if libcret < 0 {
410+
let errno = get_errno();
411+
return handle_errno(errno, "readlink");
412+
}
413+
414+
// Convert the result from readlink to a Rust string
415+
let libcbuf_str = unsafe {
416+
CStr::from_ptr(libc_buf.as_ptr() as *const c_char)
417+
}.to_str().unwrap();
418+
419+
// Use libc::getcwd to get the current working directory
420+
let mut cwd_buf = vec![0u8; 4096];
421+
let cwd_ptr = unsafe { libc::getcwd(cwd_buf.as_mut_ptr() as *mut c_char, cwd_buf.len()) };
422+
if cwd_ptr.is_null() {
423+
let errno = get_errno();
424+
return handle_errno(errno, "getcwd");
425+
}
426+
427+
let pwd = unsafe { CStr::from_ptr(cwd_buf.as_ptr() as *const c_char) }
428+
.to_str()
429+
.unwrap();
430+
431+
// Adjust the result to user perspective
432+
// Verify if libcbuf_str starts with the current working directory (pwd)
433+
let adjusted_result = if libcbuf_str.starts_with(pwd) {
434+
libcbuf_str.to_string()
435+
} else {
436+
format!("{}/{}", pwd, libcbuf_str)
437+
};
438+
let new_root = format!("{}/", LIND_ROOT);
439+
let final_result = adjusted_result.strip_prefix(&new_root).unwrap_or(&adjusted_result);
440+
441+
// Check the length and copy the appropriate amount of data to buf
442+
let bytes_to_copy = std::cmp::min(buflen, final_result.len());
443+
unsafe {
444+
std::ptr::copy_nonoverlapping(final_result.as_ptr(), buf, bytes_to_copy);
445+
}
446+
447+
bytes_to_copy as i32
448+
}
449+
450+
/*
451+
* The readlinkat syscall builds upon the readlink syscall, with additional handling for the provided fd.
452+
* There are two main cases to consider:
453+
*
454+
* When fd is the special value AT_FDCWD:
455+
* In this case, we first retrieve the current working directory path. We then append the user-provided path
456+
* to this directory path to create a complete path. After this, the handling is identical to the readlink
457+
* syscall. Therefore, the implementation delegates the underlying work to the readlink syscall.
458+
*
459+
* One notable point is that when fd = AT_FDCWD, there is no need to convert the virtual fd. Due to Rust's
460+
* variable scoping rules and for safety considerations (we must use the predefined fdtable API). This results
461+
* in approximately four lines of repetitive code during the path conversion step. If we plan to optimize
462+
* the implementation in the future, we can consider abstracting this step into a reusable function to avoid
463+
* redundancy.
464+
*
465+
* When fd is a directory fd:
466+
* Handling this case is difficult without access to kernel-space code. In Linux, there is no syscall that
467+
* provides a method to resolve the directory path corresponding to a given dirfd. The Linux kernel handles
468+
* this step by utilizing its internal dentry data structure, which is not accessible from user space.
469+
* Therefore, in the RawPOSIX implementation, we assume that all paths are absolute to simplify the resolution
470+
* process.
471+
*
472+
*/
473+
pub fn readlinkat_syscall(
474+
&self,
475+
virtual_fd: i32,
476+
path: &str,
477+
buf: *mut u8,
478+
buflen: usize,
479+
) -> i32 {
480+
let mut libcret;
481+
let mut path = path.to_string();
482+
let libc_buflen = buflen + LIND_ROOT.len();
483+
let mut libc_buf = vec![0u8; libc_buflen];
484+
if virtual_fd == libc::AT_FDCWD {
485+
// Check if the fd is AT_FDCWD
486+
let cwd_container = self.cwd.read();
487+
path = format!("{}/{}", cwd_container.to_str().unwrap(), path);
488+
// Convert the path from relative path (lind-wasm perspective) to real kernel path (host kernel
489+
// perspective)
490+
let relpath = normpath(convpath(&path), self);
491+
let relative_path = relpath.to_str().unwrap();
492+
let full_path = format!("{}{}", LIND_ROOT, relative_path);
493+
let c_path = CString::new(full_path).unwrap();
494+
495+
libcret = unsafe {
496+
libc::readlink(c_path.as_ptr(), libc_buf.as_mut_ptr() as *mut c_char, libc_buflen)
497+
};
498+
499+
} else {
500+
// Convert the virtual fd into real kernel fd and handle the error case
501+
let wrappedvfd = fdtables::translate_virtual_fd(self.cageid, virtual_fd as u64);
502+
if wrappedvfd.is_err() {
503+
return syscall_error(Errno::EBADF, "readlinkat", "Bad File Descriptor");
504+
}
505+
let vfd = wrappedvfd.unwrap();
506+
// Convert the path from relative path (lind-wasm perspective) to real kernel path (host kernel
507+
// perspective)
508+
let relpath = normpath(convpath(&path), self);
509+
let relative_path = relpath.to_str().unwrap();
510+
let full_path = format!("{}{}", LIND_ROOT, relative_path);
511+
let c_path = CString::new(full_path).unwrap();
512+
513+
libcret = unsafe {
514+
libc::readlinkat(vfd.underfd as i32, c_path.as_ptr(), libc_buf.as_mut_ptr() as *mut c_char, libc_buflen)
515+
};
516+
}
517+
518+
if libcret < 0 {
519+
let errno = get_errno();
520+
return handle_errno(errno, "readlinkat");
521+
}
522+
523+
// Convert the result from readlink to a Rust string
524+
let libcbuf_str = unsafe {
525+
CStr::from_ptr(libc_buf.as_ptr() as *const c_char)
526+
}.to_str().unwrap();
527+
528+
// Use libc::getcwd to get the current working directory
529+
let mut cwd_buf = vec![0u8; 4096];
530+
let cwd_ptr = unsafe { libc::getcwd(cwd_buf.as_mut_ptr() as *mut c_char, cwd_buf.len()) };
531+
if cwd_ptr.is_null() {
532+
let errno = get_errno();
533+
return handle_errno(errno, "getcwd");
534+
}
535+
536+
let pwd = unsafe { CStr::from_ptr(cwd_buf.as_ptr() as *const c_char) }
537+
.to_str()
538+
.unwrap();
539+
540+
// Adjust the result to user perspective
541+
let adjusted_result = format!("{}/{}", pwd, libcbuf_str);
542+
let new_root = format!("{}/", LIND_ROOT);
543+
let final_result = adjusted_result.strip_prefix(&new_root).unwrap_or(&adjusted_result);
544+
545+
// Check the length and copy the appropriate amount of data to buf
546+
let bytes_to_copy = std::cmp::min(buflen, final_result.len());
547+
unsafe {
548+
std::ptr::copy_nonoverlapping(final_result.as_ptr(), buf, bytes_to_copy);
549+
}
550+
551+
bytes_to_copy as i32
552+
}
553+
373554
//------------------------------------WRITE SYSCALL------------------------------------
374555
/*
375556
* Get the kernel fd with provided virtual fd first

src/glibc/include/unistd.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ extern __typeof (getlogin_r) __getlogin_r __nonnull ((1));
2828
//libc_hidden_proto (seteuid)
2929
//libc_hidden_proto (setegid)
3030
//libc_hidden_proto (tcgetpgrp)
31-
//libc_hidden_proto (readlinkat)
3231
//libc_hidden_proto (fsync)
3332
//libc_hidden_proto (fdatasync)
3433

@@ -153,7 +152,8 @@ extern int __symlink (const char *__from, const char *__to);
153152
extern int __symlinkat (const char *__from, int __fd, const char *__to);
154153
extern ssize_t __readlink (const char *__path, char *__buf, size_t __len)
155154
attribute_hidden;
156-
extern ssize_t __readlinkat (int __fd, const char *__file_name, char *__buf, size_t __len);
155+
extern ssize_t __readlinkat (int __fd, const char *__file_name, char *__buf, size_t __len)
156+
attribute_hidden;
157157
extern int __unlink (const char *__name) attribute_hidden;
158158
extern int __unlinkat (int __fd, const char *__name, int __flag);
159159
extern int __gethostname (char *__name, size_t __len) attribute_hidden;

src/glibc/lind_syscall/lind_syscall.c

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ int lind_syscall (unsigned int callnumber, unsigned long long callname, unsigned
3737
if(ret < 0)
3838
{
3939
errno = -ret;
40+
return -1;
4041
}
4142
else
4243
{

src/glibc/sysdeps/unix/syscalls.list

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ profil - profil i:piii __profil profil
4747
ptrace - ptrace i:iiii ptrace
4848
read - read Ci:ibU __libc_read __read read
4949
readlink - readlink i:spU __readlink readlink
50+
readlinkat - readlinkat i:spU __libc_readlinkat readlinkat
5051
readv - readv Ci:ipi __readv readv
5152
reboot - reboot i:i reboot
5253
recv - recv Ci:ibUi __libc_recv recv
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
#include <unistd.h>
22
#include <fcntl.h>
3-
#include <string.h>
3+
#include <errno.h>
4+
#include <sysdep-cancel.h>
5+
#include <syscall-template.h>
46

5-
ssize_t __GI_readlinkat (int __fd, const char *__file_name, char *__buf, size_t __len)
7+
/* Read the contents of the symbolic link PATH into no more than
8+
LEN bytes of BUF. The contents are not null-terminated.
9+
Returns the number of characters read, or -1 for errors. */
10+
/*
11+
* Edit Note:
12+
* In lind-wasm, we have separately implemented readlink and readlinkat, so we modified this part of the code to handle them individually.
13+
*/
14+
ssize_t
15+
__libc_readlinkat (int fd, const char *path, char *buf, size_t len)
616
{
7-
return 0;
17+
return MAKE_SYSCALL(166, "syscall|readlinkat",(uint64_t) fd, (uint64_t) path, (uint64_t)(uintptr_t) buf, (uint64_t) len, NOTUSED, NOTUSED);
818
}
9-
10-
weak_alias(__GI_readlinkat, _readlinkat)
19+
weak_alias(__libc_readlinkat, readlinkat)

src/glibc/sysdeps/unix/sysv/linux/readlink.c

+10-6
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,22 @@
1818

1919
#include <unistd.h>
2020
#include <fcntl.h>
21-
#include <sysdep.h>
21+
#include <sysdep-cancel.h>
22+
#include <syscall-template.h>
23+
#include <errno.h>
2224

2325
/* Read the contents of the symbolic link PATH into no more than
2426
LEN bytes of BUF. The contents are not null-terminated.
2527
Returns the number of characters read, or -1 for errors. */
28+
/*
29+
* Edit Note:
30+
* Linux kernel has two different implementations for `readlink` and `readlinkat` syscall.
31+
* In original glibc implementaion, there was only one entry point and `readlinkat` will be redirected through `readlink`,
32+
* and kernel has different callnums for them so in lind-wasm, we have separately implemented `readlink` and `readlinkat`.
33+
*/
2634
ssize_t
2735
__readlink (const char *path, char *buf, size_t len)
2836
{
29-
#ifdef __NR_readlink
30-
return INLINE_SYSCALL_CALL (readlink, path, buf, len);
31-
#else
32-
return INLINE_SYSCALL_CALL (readlinkat, AT_FDCWD, path, buf, len);
33-
#endif
37+
return MAKE_SYSCALL(165, "syscall|readlink", (uint64_t) path, (uint64_t)(uintptr_t) buf, (uint64_t) len, NOTUSED, NOTUSED, NOTUSED);
3438
}
3539
weak_alias (__readlink, readlink)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <unistd.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <errno.h>
5+
#include <string.h>
6+
7+
const char* VALID_SYMBOLIC_PATH = "testfiles/readlinkfile";
8+
const char* NON_SYMBOLIC_PATH = "testfiles/fstatfile.txt";
9+
const char* NON_EXISTENT_PATH = "testfiles/nonexistent";
10+
11+
void test_readlink() {
12+
char buf[1024];
13+
ssize_t len;
14+
15+
// Test Case 1: Valid symbolic link
16+
printf("\n=== Test Case 1: Valid symbolic link ===\n");
17+
len = readlink(VALID_SYMBOLIC_PATH, buf, sizeof(buf));
18+
if (len != -1) {
19+
buf[len] = '\0'; // Null-terminate the result to print the result
20+
printf("Symbolic link points to: %s\n", buf);
21+
} else {
22+
perror("Test Case 1 failed");
23+
}
24+
25+
// Test Case 2: Path is not a symbolic link
26+
printf("\n=== Test Case 2: Path is not a symbolic link ===\n");
27+
len = readlink(NON_SYMBOLIC_PATH, buf, sizeof(buf));
28+
if (len == -1) {
29+
printf("Expected failure: %s\n", strerror(errno));
30+
} else {
31+
printf("Test Case 2 failed: Unexpectedly succeeded\n");
32+
}
33+
34+
// Test Case 3: Symbolic link with buffer too small
35+
printf("\n=== Test Case 3: Symbolic link with buffer too small ===\n");
36+
len = readlink(VALID_SYMBOLIC_PATH, buf, 5); // Intentionally small buffer
37+
if (len != -1) {
38+
printf("Symbolic link truncated result: %.*s\n", (int)len, buf);
39+
} else {
40+
perror("Test Case 3 failed");
41+
}
42+
43+
// Test Case 4: Non-existent path
44+
printf("\n=== Test Case 4: Non-existent path ===\n");
45+
len = readlink(NON_EXISTENT_PATH, buf, sizeof(buf));
46+
if (len == -1) {
47+
printf("Expected failure: %s\n", strerror(errno));
48+
} else {
49+
printf("Test Case 4 failed: Unexpectedly succeeded\n");
50+
}
51+
}
52+
53+
int main() {
54+
test_readlink();
55+
return 0;
56+
}

0 commit comments

Comments
 (0)