Skip to content

Commit

Permalink
fs+syscall+userland: implement symlink
Browse files Browse the repository at this point in the history
  • Loading branch information
mosmeh committed Jun 23, 2024
1 parent 808f91c commit 62adc61
Show file tree
Hide file tree
Showing 18 changed files with 301 additions and 26 deletions.
1 change: 1 addition & 0 deletions kernel/api/fcntl.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
#define O_CREAT 0x8
#define O_EXCL 0x10
#define O_NONBLOCK 0x100
#define O_NOFOLLOW 0x400
3 changes: 3 additions & 0 deletions kernel/api/sys/limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@

#define ARG_MAX 131072
#define PATH_MAX 4096
#define OPEN_MAX 1024
#define SYMLINK_MAX 255
#define SYMLOOP_MAX 8
3 changes: 3 additions & 0 deletions kernel/api/sys/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
F(link) \
F(listen) \
F(lseek) \
F(lstat) \
F(mkdir) \
F(mknod) \
F(mmap) \
Expand All @@ -38,6 +39,7 @@
F(pipe) \
F(poll) \
F(read) \
F(readlink) \
F(reboot) \
F(rename) \
F(rmdir) \
Expand All @@ -46,6 +48,7 @@
F(shutdown) \
F(socket) \
F(stat) \
F(symlink) \
F(sysconf) \
F(times) \
F(uname) \
Expand Down
1 change: 1 addition & 0 deletions kernel/fs/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <kernel/api/dirent.h>
#include <kernel/api/fcntl.h>
#include <kernel/api/stdio.h>
#include <kernel/api/sys/limits.h>
#include <kernel/api/sys/poll.h>
#include <kernel/lock.h>
#include <kernel/memory/memory.h>
Expand Down
7 changes: 4 additions & 3 deletions kernel/fs/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
#include <kernel/lock.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>

#define PATH_SEPARATOR '/'
#define PATH_SEPARATOR_STR "/"
#define ROOT_DIR PATH_SEPARATOR_STR

#define OPEN_MAX 1024

typedef struct unix_socket unix_socket;
typedef struct multiboot_info multiboot_info_t;
typedef struct multiboot_mod_list multiboot_module_t;
Expand Down Expand Up @@ -120,6 +117,10 @@ dev_t vfs_generate_unnamed_device_number(void);
// The last component of the returned path will have NULL inode in this case.
#define O_ALLOW_NOENT 0x4000

// When combined with O_NOFOLLOW, do not return an error if the last component
// of the path is a symbolic link, and return the symlink itself.
#define O_NOFOLLOW_NOERROR 0x2000

NODISCARD file_description* vfs_open(const char* pathname, int flags,
mode_t mode);
NODISCARD file_description* vfs_open_at(const struct path* base,
Expand Down
99 changes: 90 additions & 9 deletions kernel/fs/vfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "path.h"
#include <common/string.h>
#include <kernel/api/fcntl.h>
#include <kernel/api/sys/limits.h>
#include <kernel/api/sys/sysmacros.h>
#include <kernel/kprintf.h>
#include <kernel/lock.h>
Expand Down Expand Up @@ -157,12 +158,51 @@ static bool is_absolute_path(const char* path) {
return path[0] == PATH_SEPARATOR;
}

struct path* vfs_resolve_path(const char* pathname, int flags) {
return vfs_resolve_path_at(current->cwd, pathname, flags);
static struct path* resolve_path_at(const struct path* base,
const char* pathname, int flags,
unsigned symlink_depth);

static struct path* follow_symlink(const struct path* parent,
struct inode* inode,
const char* rest_pathname, int flags,
unsigned depth) {
ASSERT(S_ISLNK(inode->mode));
ASSERT(depth <= SYMLOOP_MAX);

file_description* desc = inode_open(inode, O_RDONLY, 0);
if (IS_ERR(desc))
return ERR_CAST(desc);

char target[SYMLINK_MAX];
size_t target_len = 0;
while (target_len < SYMLINK_MAX) {
ssize_t nread = file_description_read(desc, target + target_len,
SYMLINK_MAX - target_len);
if (IS_ERR(nread)) {
file_description_close(desc);
return ERR_PTR(nread);
}
if (nread == 0)
break;
target_len += nread;
}
file_description_close(desc);

char* pathname = kmalloc(target_len + 1 + strlen(rest_pathname) + 1);
if (!pathname)
return ERR_PTR(-ENOMEM);
memcpy(pathname, target, target_len);
pathname[target_len] = '/';
strcpy(pathname + target_len + 1, rest_pathname);

struct path* path = resolve_path_at(parent, pathname, flags, depth + 1);
kfree(pathname);
return path;
}

struct path* vfs_resolve_path_at(const struct path* base, const char* pathname,
int flags) {
static struct path* resolve_path_at(const struct path* base,
const char* pathname, int flags,
unsigned symlink_depth) {
struct path* path =
is_absolute_path(pathname) ? vfs_get_root() : path_dup(base);
if (IS_ERR(path))
Expand Down Expand Up @@ -197,19 +237,24 @@ struct path* vfs_resolve_path_at(const struct path* base, const char* pathname,
inode_ref(path->inode);
struct inode* inode = inode_lookup_child(path->inode, component);

bool has_more_components = false;
for (char* p = saved_ptr; p && *p; ++p) {
if (*p != PATH_SEPARATOR) {
has_more_components = true;
break;
}
}

if ((flags & O_ALLOW_NOENT) && PTR_ERR(inode) == -ENOENT) {
const char* next_component =
strtok_r(NULL, PATH_SEPARATOR_STR, &saved_ptr);
if (next_component) {
// This is not the last component.
if (has_more_components) {
path_destroy_recursive(path);
kfree(dup_pathname);
return ERR_PTR(-ENOENT);
}
struct path* joined = path_join(path, NULL, component);
kfree(dup_pathname);
if (IS_ERR(joined))
path_destroy_recursive(path);
kfree(dup_pathname);
return joined;
}

Expand All @@ -221,6 +266,33 @@ struct path* vfs_resolve_path_at(const struct path* base, const char* pathname,

inode = resolve_mounts(inode);

if (S_ISLNK(inode->mode)) {
if (symlink_depth > SYMLOOP_MAX) {
inode_unref(inode);
path_destroy_recursive(path);
kfree(dup_pathname);
return ERR_PTR(-ELOOP);
}

if (has_more_components || !(flags & O_NOFOLLOW)) {
const char* rest_pathname = strtok_r(NULL, "", &saved_ptr);
if (!rest_pathname)
rest_pathname = ".";
struct path* dest = follow_symlink(path, inode, rest_pathname,
flags, symlink_depth);
path_destroy_recursive(path);
kfree(dup_pathname);
return dest;
}

if (!(flags & O_NOFOLLOW_NOERROR)) {
inode_unref(inode);
path_destroy_recursive(path);
kfree(dup_pathname);
return ERR_PTR(-ELOOP);
}
}

struct path* joined = path_join(path, inode, component);
if (IS_ERR(joined)) {
path_destroy_recursive(path);
Expand All @@ -234,6 +306,15 @@ struct path* vfs_resolve_path_at(const struct path* base, const char* pathname,
return path;
}

struct path* vfs_resolve_path(const char* pathname, int flags) {
return vfs_resolve_path_at(current->cwd, pathname, flags);
}

struct path* vfs_resolve_path_at(const struct path* base, const char* pathname,
int flags) {
return resolve_path_at(base, pathname, flags, 0);
}

static struct inode* resolve_special_file(struct inode* inode) {
if (S_ISBLK(inode->mode) || S_ISCHR(inode->mode)) {
struct inode* device = vfs_get_device(inode->rdev);
Expand Down
1 change: 1 addition & 0 deletions kernel/process.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "process.h"
#include "api/signum.h"
#include "api/sys/limits.h"
#include "boot_defs.h"
#include "fs/path.h"
#include "interrupts.h"
Expand Down
87 changes: 86 additions & 1 deletion kernel/syscall/fs.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "syscall.h"
#include <common/string.h>
#include <kernel/api/dirent.h>
#include <kernel/api/fcntl.h>
#include <kernel/api/sys/limits.h>
Expand All @@ -8,7 +9,6 @@
#include <kernel/panic.h>
#include <kernel/process.h>
#include <kernel/safe_string.h>
#include <string.h>

static int copy_pathname_from_user(char* dest, const char* user_src) {
ssize_t pathname_len = strncpy_from_user(dest, user_src, PATH_MAX);
Expand Down Expand Up @@ -55,6 +55,47 @@ ssize_t sys_read(int fd, void* user_buf, size_t count) {
return file_description_read(desc, user_buf, count);
}

ssize_t sys_readlink(const char* user_pathname, char* user_buf, size_t bufsiz) {
char pathname[PATH_MAX];
int rc = copy_pathname_from_user(pathname, user_pathname);
if (IS_ERR(rc))
return rc;

struct path* path =
vfs_resolve_path(pathname, O_NOFOLLOW | O_NOFOLLOW_NOERROR);
if (IS_ERR(path))
return PTR_ERR(path);

struct inode* inode = path_into_inode(path);
if (!S_ISLNK(inode->mode)) {
return -EINVAL;
}

file_description* desc = inode_open(inode, O_RDONLY, 0);
if (IS_ERR(desc))
return PTR_ERR(desc);

bufsiz = MIN(bufsiz, SYMLINK_MAX);

char buf[SYMLINK_MAX];
size_t buf_len = 0;
while (buf_len < bufsiz) {
ssize_t nread = file_description_read(desc, buf + buf_len, bufsiz);
if (IS_ERR(nread)) {
file_description_close(desc);
return nread;
}
if (nread == 0)
break;
buf_len += nread;
}
file_description_close(desc);

if (!copy_to_user(user_buf, buf, buf_len))
return -EFAULT;
return buf_len;
}

ssize_t sys_write(int fd, const void* user_buf, size_t count) {
if (!user_buf || !is_user_range(user_buf, count))
return -EFAULT;
Expand All @@ -78,6 +119,20 @@ off_t sys_lseek(int fd, off_t offset, int whence) {
return file_description_seek(desc, offset, whence);
}

int sys_lstat(const char* user_pathname, struct stat* user_buf) {
char pathname[PATH_MAX];
int rc = copy_pathname_from_user(pathname, user_pathname);
if (IS_ERR(rc))
return rc;
struct stat buf;
rc = vfs_stat(pathname, &buf, O_NOFOLLOW | O_NOFOLLOW_NOERROR);
if (IS_ERR(rc))
return rc;
if (!copy_to_user(user_buf, &buf, sizeof(struct stat)))
return -EFAULT;
return 0;
}

int sys_stat(const char* user_pathname, struct stat* user_buf) {
char pathname[PATH_MAX];
int rc = copy_pathname_from_user(pathname, user_pathname);
Expand All @@ -92,6 +147,36 @@ int sys_stat(const char* user_pathname, struct stat* user_buf) {
return 0;
}

int sys_symlink(const char* user_target, const char* user_linkpath) {
char target[PATH_MAX];
int rc = copy_pathname_from_user(target, user_target);
if (IS_ERR(rc))
return rc;
size_t target_len = strlen(target);
if (target_len > SYMLINK_MAX)
return -ENAMETOOLONG;

char linkpath[PATH_MAX];
rc = copy_pathname_from_user(linkpath, user_linkpath);
if (IS_ERR(rc))
return rc;

struct inode* inode = vfs_create(linkpath, S_IFLNK);
if (IS_ERR(inode))
return PTR_ERR(inode);

file_description* desc = inode_open(inode, O_WRONLY, 0);
if (IS_ERR(desc)) {
inode_unref(inode);
return PTR_ERR(desc);
}
rc = file_description_write(desc, target, target_len);

file_description_close(desc);
inode_unref(inode);
return rc;
}

int sys_ioctl(int fd, int request, void* user_argp) {
if (!is_user_address(user_argp))
return -EFAULT;
Expand Down
1 change: 1 addition & 0 deletions kernel/syscall/syscall.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "syscall.h"
#include <kernel/api/sys/limits.h>
#include <kernel/api/sys/reboot.h>
#include <kernel/api/unistd.h>
#include <kernel/boot_defs.h>
Expand Down
3 changes: 3 additions & 0 deletions kernel/syscall/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ int sys_kill(pid_t pid, int sig);
int sys_link(const char* oldpath, const char* newpath);
int sys_listen(int sockfd, int backlog);
off_t sys_lseek(int fd, off_t offset, int whence);
int sys_lstat(const char* pathname, struct stat* buf);
int sys_mkdir(const char* pathname, mode_t mode);
int sys_mknod(const char* pathname, mode_t mode, dev_t dev);
void* sys_mmap(const mmap_params* params);
Expand All @@ -44,6 +45,7 @@ int sys_open(const char* pathname, int flags, unsigned mode);
int sys_pipe(int pipefd[2]);
int sys_poll(struct pollfd* fds, nfds_t nfds, int timeout);
ssize_t sys_read(int fd, void* buf, size_t count);
ssize_t sys_readlink(const char* pathname, char* buf, size_t bufsiz);
int sys_reboot(int howto);
int sys_rename(const char* oldpath, const char* newpath);
int sys_rmdir(const char* pathname);
Expand All @@ -52,6 +54,7 @@ int sys_setpgid(pid_t pid, pid_t pgid);
int sys_shutdown(int sockfd, int how);
int sys_socket(int domain, int type, int protocol);
int sys_stat(const char* pathname, struct stat* buf);
int sys_symlink(const char* target, const char* linkpath);
long sys_sysconf(int name);
clock_t sys_times(struct tms* buf);
int sys_uname(struct utsname* buf);
Expand Down
1 change: 1 addition & 0 deletions userland/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ BIN_TARGET_NAMES := \
poweroff \
ps \
pwd \
readlink \
reboot \
rm \
rmdir \
Expand Down
1 change: 1 addition & 0 deletions userland/lib/sys/stat.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
#include <kernel/api/sys/stat.h>

int stat(const char* pathname, struct stat* buf);
int lstat(const char* pathname, struct stat* buf);
int mkdir(const char* pathname, mode_t mode);
int mkfifo(const char* pathname, mode_t mode);
Loading

0 comments on commit 62adc61

Please sign in to comment.