Compare commits

..

22 Commits

Author SHA1 Message Date
73fbc37841 kernel+libc+apps: Add a source parameter to the mount() system call
All checks were successful
continuous-integration/drone/pr Build is passing
2023-06-20 19:40:58 +00:00
479016ab20 kernel: Add an Ext2 filesystem skeleton 2023-06-20 19:40:58 +00:00
3d157b760c
kernel: Make the stack and loaded program code regions persistent
All checks were successful
continuous-integration/drone/push Build is passing
This way, they can't be modified by mmap() or munmap().
2023-06-19 12:44:49 +02:00
54a1998d42
kernel: Also move children's parent to PID 1 in the common thread exit function
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 12:35:31 +02:00
e60b2a3d2f
kernel: Move thread exit code into a separate common function
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 12:33:25 +02:00
f052d8630d
libos: Use Vector::shallow_copy() in ArgumentParser
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 12:23:56 +02:00
8542cf7cbf
libluna: Fix Vector::shallow_copy() 2023-06-19 12:23:39 +02:00
d50ea76bdc
libc: Make tmpfile() create files in /tmp's filesystem
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 11:52:38 +02:00
2edb0a3f3a
all: Update minor version number to 0.3.0 pre-release
All checks were successful
continuous-integration/drone/push Build is passing
Not a release yet, but we can give it the version number for it.
2023-06-19 11:41:10 +02:00
b4a6e4d56d
libluna/PathParser: Make dirname() and basename() static functions
All checks were successful
continuous-integration/drone/push Build is passing
This avoids creating a PathParser to use them, which allocates memory that won't be used.
2023-06-19 11:21:58 +02:00
f22689fcf5
libc: Add stubs for fflush() and ungetc()
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 10:48:02 +02:00
6bfc7483bc
libc: Add a definition for FILENAME_MAX 2023-06-19 10:47:43 +02:00
acfad51ac0
libc: Add freopen() 2023-06-19 10:46:08 +02:00
7efc3a6ea1
kernel: Show stack traces on page faults + crash the process instead of the whole system on GPFs 2023-06-19 10:45:08 +02:00
0b553cadc0
libluna: Do not fault if vstring_format() is called with buf=nullptr
Instead, just discard the output and return the number of bytes formatted, as mandated by the C standard.
2023-06-19 10:44:33 +02:00
ae01a31104
kernel: Make sure the stack is 16-byte aligned on program startup
This is required by the System V ABI and fixes some movaps-related GPFs in ndisasm.
2023-06-19 10:41:32 +02:00
795b0ca8d4
libc/scanf: Some refactoring
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 01:05:32 +02:00
21cc7e3729
libluna: Rename parse_length() to parse_type() in Format.cpp
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-19 01:00:05 +02:00
55d147841f
libc+tests: Add type modifiers and integer conversion specifiers to scanf() 2023-06-19 00:59:42 +02:00
a2c081f219
libluna: Allow passing a base to scan_(un)signed_integer() 2023-06-19 00:58:02 +02:00
8c2348c425
libc/scanf: Skip whitespace before %%
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-18 23:51:44 +02:00
25e9187826
libc+tests: Add basic support for the scanf family of functions
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-18 23:44:30 +02:00
26 changed files with 575 additions and 114 deletions

View File

@ -5,7 +5,7 @@ set(CMAKE_CXX_COMPILER_WORKS 1)
set(CMAKE_CROSSCOMPILING true) set(CMAKE_CROSSCOMPILING true)
project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.2.0) project(Luna LANGUAGES C CXX ASM ASM_NASM VERSION 0.3.0)
set(LUNA_ROOT ${CMAKE_CURRENT_LIST_DIR}) set(LUNA_ROOT ${CMAKE_CURRENT_LIST_DIR})
set(LUNA_BASE ${CMAKE_CURRENT_LIST_DIR}/base) set(LUNA_BASE ${CMAKE_CURRENT_LIST_DIR}/base)

View File

@ -20,8 +20,7 @@ begin:
if (rc.error() == EEXIST) return {}; if (rc.error() == EEXIST) return {};
if (rc.error() == ENOENT) if (rc.error() == ENOENT)
{ {
PathParser parser = TRY(PathParser::create(path.chars())); auto parent = TRY(PathParser::dirname(path));
auto parent = TRY(parser.dirname());
TRY(mkdir_recursively(parent.view(), (0777 & ~s_umask) | S_IWUSR | S_IXUSR)); TRY(mkdir_recursively(parent.view(), (0777 & ~s_umask) | S_IWUSR | S_IXUSR));

View File

@ -80,32 +80,14 @@ void decode_page_fault_error_code(u64 code)
decode_page_fault_error_code(regs->error); decode_page_fault_error_code(regs->error);
CPU::print_stack_trace_at(regs);
if (!is_in_kernel(regs)) if (!is_in_kernel(regs))
{ {
// FIXME: Kill this process with SIGSEGV once we have signals and all that. // FIXME: Kill this process with SIGSEGV once we have signals and all that.
kerrorln("Current task %zu was terminated because of a page fault", Scheduler::current()->id); kerrorln("Current task %zu was terminated because of a page fault", Scheduler::current()->id);
if (Scheduler::current()->is_kernel) Scheduler::current()->state = ThreadState::Dying; Scheduler::current()->exit_and_signal_parent(127);
else
{
auto* current = Scheduler::current();
auto* parent = current->parent;
if (parent && parent->state == ThreadState::Waiting)
{
auto child = *parent->child_being_waited_for;
if (child == -1 || child == (pid_t)current->id)
{
parent->child_being_waited_for = (pid_t)current->id;
parent->wake_up();
} }
}
current->state = ThreadState::Exited;
}
Scheduler::current()->status = 127;
kernel_yield();
unreachable();
}
CPU::print_stack_trace_at(regs);
CPU::efficient_halt(); CPU::efficient_halt();
} }
@ -118,6 +100,13 @@ void decode_page_fault_error_code(u64 code)
CPU::print_stack_trace_at(regs); CPU::print_stack_trace_at(regs);
if (!is_in_kernel(regs))
{
// FIXME: Kill this process with SIGSEGV once we have signals and all that.
kerrorln("Current task %zu was terminated because of a general protection fault", Scheduler::current()->id);
Scheduler::current()->exit_and_signal_parent(127);
}
CPU::efficient_halt(); CPU::efficient_halt();
} }
@ -302,14 +291,14 @@ namespace CPU
asm volatile("hlt"); asm volatile("hlt");
} }
[[noreturn]] void efficient_halt() // Halt the CPU, using the lowest power possible. On x86-64 we do this using the [[noreturn]] void efficient_halt() // Halt the CPU, using the lowest power possible. On x86-64 we do this using
// "hlt" instruction, which puts the CPU into a low-power idle state until the // the "hlt" instruction, which puts the CPU into a low-power idle state
// next interrupt arrives... and we disable interrupts beforehand. // until the next interrupt arrives... and we disable interrupts beforehand.
{ {
asm volatile("cli"); // Disable interrupts asm volatile("cli"); // Disable interrupts
loop: loop:
asm volatile("hlt"); // Let the cpu rest and pause until the next interrupt arrives... which in this case should asm volatile("hlt"); // Let the cpu rest and pause until the next interrupt arrives... which in this case
// be never (unless an NMI arrives) :) // should be never (unless an NMI arrives) :)
goto loop; // Safeguard: if we ever wake up, start our low-power rest again goto loop; // Safeguard: if we ever wake up, start our low-power rest again
} }

View File

@ -71,14 +71,13 @@ namespace VFS
Result<SharedPtr<Inode>> create_directory(const char* path, Credentials auth, SharedPtr<Inode> working_directory) Result<SharedPtr<Inode>> create_directory(const char* path, Credentials auth, SharedPtr<Inode> working_directory)
{ {
auto parser = TRY(PathParser::create(path)); auto parent_path = TRY(PathParser::dirname(path));
auto parent_path = TRY(parser.dirname());
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory)); auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
if (!can_write(parent_inode, auth)) return err(EACCES); if (!can_write(parent_inode, auth)) return err(EACCES);
auto child_name = TRY(parser.basename()); auto child_name = TRY(PathParser::basename(path));
TRY(validate_filename(child_name.view())); TRY(validate_filename(child_name.view()));
@ -87,14 +86,13 @@ namespace VFS
Result<SharedPtr<Inode>> create_file(const char* path, Credentials auth, SharedPtr<Inode> working_directory) Result<SharedPtr<Inode>> create_file(const char* path, Credentials auth, SharedPtr<Inode> working_directory)
{ {
auto parser = TRY(PathParser::create(path)); auto parent_path = TRY(PathParser::dirname(path));
auto parent_path = TRY(parser.dirname());
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory)); auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
if (!can_write(parent_inode, auth)) return err(EACCES); if (!can_write(parent_inode, auth)) return err(EACCES);
auto child_name = TRY(parser.basename()); auto child_name = TRY(PathParser::basename(path));
TRY(validate_filename(child_name.view())); TRY(validate_filename(child_name.view()));
@ -189,9 +187,8 @@ namespace VFS
Result<void> pivot_root(const char* new_root, const char* put_old, SharedPtr<VFS::Inode> working_directory) Result<void> pivot_root(const char* new_root, const char* put_old, SharedPtr<VFS::Inode> working_directory)
{ {
auto root_parser = TRY(PathParser::create(new_root)); auto new_root_parent = TRY(PathParser::dirname(new_root));
auto new_root_parent = TRY(root_parser.dirname()); auto new_root_path = TRY(PathParser::basename(new_root));
auto new_root_path = TRY(root_parser.basename());
auto new_root_parent_inode = TRY(VFS::resolve_path(new_root_parent.chars(), Credentials {}, working_directory)); auto new_root_parent_inode = TRY(VFS::resolve_path(new_root_parent.chars(), Credentials {}, working_directory));
auto new_root_inode = TRY(new_root_parent_inode->find(new_root_path.chars())); auto new_root_inode = TRY(new_root_parent_inode->find(new_root_path.chars()));
@ -200,9 +197,8 @@ namespace VFS
if (!new_root_inode->is_mountpoint()) return err(EINVAL); if (!new_root_inode->is_mountpoint()) return err(EINVAL);
if (new_root_inode->fs() == g_root_inode->fs()) return err(EBUSY); if (new_root_inode->fs() == g_root_inode->fs()) return err(EBUSY);
auto parser = TRY(PathParser::create(put_old)); auto parent_path = TRY(PathParser::dirname(put_old));
auto parent_path = TRY(parser.dirname()); auto child = TRY(PathParser::basename(put_old));
auto child = TRY(parser.basename());
kdbgln("vfs: Pivoting root from / to %s, using %s as new root", put_old, new_root); kdbgln("vfs: Pivoting root from / to %s, using %s as new root", put_old, new_root);
@ -228,9 +224,8 @@ namespace VFS
Result<void> mount(const char* path, SharedPtr<VFS::FileSystem> fs, Credentials auth, Result<void> mount(const char* path, SharedPtr<VFS::FileSystem> fs, Credentials auth,
SharedPtr<VFS::Inode> working_directory) SharedPtr<VFS::Inode> working_directory)
{ {
auto parser = TRY(PathParser::create(path)); auto parent_path = TRY(PathParser::dirname(path));
auto parent_path = TRY(parser.dirname()); auto child = TRY(PathParser::basename(path));
auto child = TRY(parser.basename());
kinfoln("vfs: Mounting filesystem on target %s", path); kinfoln("vfs: Mounting filesystem on target %s", path);
@ -249,9 +244,8 @@ namespace VFS
Result<void> umount(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory) Result<void> umount(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory)
{ {
auto parser = TRY(PathParser::create(path)); auto parent_path = TRY(PathParser::dirname(path));
auto parent_path = TRY(parser.dirname()); auto child = TRY(PathParser::basename(path));
auto child = TRY(parser.basename());
if (child.view() == "/") return err(EBUSY); if (child.view() == "/") return err(EBUSY);

View File

@ -91,7 +91,7 @@ Result<u64> UserVM::alloc_region(usize count, bool persistent)
return err(ENOMEM); return err(ENOMEM);
} }
Result<bool> UserVM::set_region(u64 address, usize count, bool used) Result<bool> UserVM::set_region(u64 address, usize count, bool used, bool persistent)
{ {
if (address >= VM_END) return err(EINVAL); if (address >= VM_END) return err(EINVAL);
@ -112,6 +112,7 @@ Result<bool> UserVM::set_region(u64 address, usize count, bool used)
if (region->start >= address && region->end <= end) if (region->start >= address && region->end <= end)
{ {
region->used = used; region->used = used;
region->persistent = persistent;
if (region->start == address && region->end == end) if (region->start == address && region->end == end)
{ {
try_merge_region_with_neighbors(region); try_merge_region_with_neighbors(region);
@ -125,6 +126,7 @@ Result<bool> UserVM::set_region(u64 address, usize count, bool used)
auto* middle_region = TRY(split_region(region, address)); auto* middle_region = TRY(split_region(region, address));
TRY(split_region(middle_region, end)); TRY(split_region(middle_region, end));
middle_region->used = used; middle_region->used = used;
middle_region->persistent = persistent;
return true; return true;
} }
@ -133,6 +135,7 @@ Result<bool> UserVM::set_region(u64 address, usize count, bool used)
bool finished = region->end == end; bool finished = region->end == end;
auto* split = TRY(split_region(region, address)); auto* split = TRY(split_region(region, address));
split->used = used; split->used = used;
split->persistent = persistent;
try_merge_region_with_neighbors(split); try_merge_region_with_neighbors(split);
if (!finished) continue; if (!finished) continue;
return true; return true;
@ -142,6 +145,7 @@ Result<bool> UserVM::set_region(u64 address, usize count, bool used)
{ {
TRY(split_region(region, end)); TRY(split_region(region, end));
region->used = used; region->used = used;
region->persistent = persistent;
try_merge_region_with_neighbors(region); try_merge_region_with_neighbors(region);
return true; return true;
} }

View File

@ -21,14 +21,14 @@ class UserVM
Result<u64> alloc_region(usize count, bool persistent = false); Result<u64> alloc_region(usize count, bool persistent = false);
Result<bool> test_and_alloc_region(u64 address, usize count) Result<bool> test_and_alloc_region(u64 address, usize count, bool persistent = false)
{ {
return set_region(address, count, true); return set_region(address, count, true, persistent);
} }
Result<bool> free_region(u64 address, usize count) Result<bool> free_region(u64 address, usize count)
{ {
return set_region(address, count, false); return set_region(address, count, false, false);
} }
static Result<OwnedPtr<UserVM>> try_create(); static Result<OwnedPtr<UserVM>> try_create();
@ -36,7 +36,7 @@ class UserVM
Result<OwnedPtr<UserVM>> clone(); Result<OwnedPtr<UserVM>> clone();
private: private:
Result<bool> set_region(u64 address, usize count, bool used); Result<bool> set_region(u64 address, usize count, bool used, bool persistent);
Result<void> create_default_region(); Result<void> create_default_region();
Result<void> create_null_region(); Result<void> create_null_region();
void try_merge_region_with_neighbors(VMRegion* region); void try_merge_region_with_neighbors(VMRegion* region);

View File

@ -7,25 +7,5 @@ Result<u64> sys_exit(Registers*, SyscallArgs args)
Thread* current = Scheduler::current(); Thread* current = Scheduler::current();
Scheduler::for_each_child(current, [](Thread* child) { current->exit_and_signal_parent(status);
child->parent = Scheduler::init_thread();
return true;
});
auto* parent = current->parent;
if (parent && parent->state == ThreadState::Waiting)
{
auto child = *parent->child_being_waited_for;
if (child == -1 || child == (pid_t)current->id)
{
parent->child_being_waited_for = (pid_t)current->id;
parent->wake_up();
}
}
current->status = status;
current->state = ThreadState::Exited;
kernel_yield();
unreachable();
} }

View File

@ -13,10 +13,8 @@ Result<u64> sys_unlinkat(Registers*, SyscallArgs args)
Thread* current = Scheduler::current(); Thread* current = Scheduler::current();
PathParser parser = TRY(PathParser::create(path.chars())); auto dirname = TRY(PathParser::dirname(path.view()));
auto basename = TRY(PathParser::basename(path.view()));
auto dirname = TRY(parser.dirname());
auto basename = TRY(parser.basename());
if (basename.view() == ".") return err(EINVAL); if (basename.view() == ".") return err(EINVAL);
@ -47,14 +45,13 @@ Result<u64> sys_symlinkat(Registers*, SyscallArgs args)
auto* current = Scheduler::current(); auto* current = Scheduler::current();
auto parser = TRY(PathParser::create(linkpath.chars())); auto parent = TRY(PathParser::dirname(linkpath.view()));
auto parent = TRY(parser.dirname());
auto parent_inode = TRY(current->resolve_atfile(dirfd, parent, false, true)); auto parent_inode = TRY(current->resolve_atfile(dirfd, parent, false, true));
if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES); if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES);
auto child_name = TRY(parser.basename()); auto child_name = TRY(PathParser::basename(linkpath.view()));
TRY(VFS::validate_filename(child_name.view())); TRY(VFS::validate_filename(child_name.view()));
@ -101,8 +98,7 @@ Result<u64> sys_linkat(Registers*, SyscallArgs args)
auto* current = Scheduler::current(); auto* current = Scheduler::current();
auto parser = TRY(PathParser::create(newpath.chars())); auto parent = TRY(PathParser::dirname(newpath.view()));
auto parent = TRY(parser.dirname());
// FIXME: Use AT_SYMLINK_FOLLOW. // FIXME: Use AT_SYMLINK_FOLLOW.
auto target = TRY(current->resolve_atfile(olddirfd, oldpath, flags & AT_EMPTY_PATH, false)); auto target = TRY(current->resolve_atfile(olddirfd, oldpath, flags & AT_EMPTY_PATH, false));
@ -115,7 +111,7 @@ Result<u64> sys_linkat(Registers*, SyscallArgs args)
if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES); if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES);
auto child_name = TRY(parser.basename()); auto child_name = TRY(PathParser::basename(newpath.view()));
TRY(VFS::validate_filename(child_name.view())); TRY(VFS::validate_filename(child_name.view()));

View File

@ -102,9 +102,8 @@ namespace ELFLoader
if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite; if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite;
if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute; if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute;
// FIXME: Set this memory range to persistent so that munmap() cannot remove it.
if (!TRY(vm->test_and_alloc_region( if (!TRY(vm->test_and_alloc_region(
base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE)))) base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), true)))
return err(ENOMEM); return err(ENOMEM);
// Allocate physical memory for the segment // Allocate physical memory for the segment

View File

@ -1,5 +1,6 @@
#include "thread/Thread.h" #include "thread/Thread.h"
#include "memory/MemoryManager.h" #include "memory/MemoryManager.h"
#include "thread/Scheduler.h"
#include <bits/atfile.h> #include <bits/atfile.h>
#include <bits/open-flags.h> #include <bits/open-flags.h>
#include <luna/Alloc.h> #include <luna/Alloc.h>
@ -68,6 +69,33 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
return VFS::resolve_path(path.chars(), this->auth, descriptor->inode, follow_last_symlink); return VFS::resolve_path(path.chars(), this->auth, descriptor->inode, follow_last_symlink);
} }
[[noreturn]] void Thread::exit_and_signal_parent(u8 _status)
{
if (is_kernel) state = ThreadState::Dying;
else
{
Scheduler::for_each_child(this, [](Thread* child) {
child->parent = Scheduler::init_thread();
return true;
});
if (parent && parent->state == ThreadState::Waiting)
{
auto child = *parent->child_being_waited_for;
if (child == -1 || child == (pid_t)id)
{
parent->child_being_waited_for = (pid_t)id;
parent->wake_up();
}
}
state = ThreadState::Exited;
}
status = _status;
kernel_yield();
unreachable();
}
bool FileDescriptor::should_append() bool FileDescriptor::should_append()
{ {
return flags & O_APPEND; return flags & O_APPEND;

View File

@ -99,6 +99,8 @@ struct Thread : public LinkedListNode<Thread>
PageDirectory* directory; PageDirectory* directory;
[[noreturn]] void exit_and_signal_parent(u8 status);
bool is_idle() bool is_idle()
{ {
return state == ThreadState::Idle; return state == ThreadState::Idle;

View File

@ -1,6 +1,7 @@
#include "thread/ThreadImage.h" #include "thread/ThreadImage.h"
#include "memory/MemoryManager.h" #include "memory/MemoryManager.h"
#include "thread/Thread.h" #include "thread/Thread.h"
#include <luna/Alignment.h>
#include <luna/CString.h> #include <luna/CString.h>
static constexpr usize DEFAULT_USER_STACK_PAGES = 6; static constexpr usize DEFAULT_USER_STACK_PAGES = 6;
@ -10,8 +11,7 @@ static Result<void> create_stacks(Stack& user_stack, Stack& kernel_stack, UserVM
{ {
const u64 THREAD_STACK_BASE = 0x10000; const u64 THREAD_STACK_BASE = 0x10000;
// FIXME: Set this memory range to persistent so that munmap() cannot remove it. if (!TRY(vm->test_and_alloc_region(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES, true))) return err(ENOMEM);
if (!TRY(vm->test_and_alloc_region(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES))) return err(ENOMEM);
TRY(MemoryManager::alloc_at_zeroed(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES, TRY(MemoryManager::alloc_at_zeroed(THREAD_STACK_BASE, DEFAULT_USER_STACK_PAGES,
MMU::ReadWrite | MMU::NoExecute | MMU::User)); MMU::ReadWrite | MMU::NoExecute | MMU::User));
@ -122,7 +122,7 @@ void ThreadImage::apply(Thread* thread)
thread->kernel_stack = m_kernel_stack; thread->kernel_stack = m_kernel_stack;
thread->stack = m_user_stack; thread->stack = m_user_stack;
thread->set_sp(m_sp); thread->set_sp(align_down<16>(m_sp));
thread->directory = m_directory; thread->directory = m_directory;

View File

@ -20,6 +20,7 @@ set(SOURCES
src/pwd.cpp src/pwd.cpp
src/grp.cpp src/grp.cpp
src/locale.cpp src/locale.cpp
src/scanf.cpp
src/sys/stat.cpp src/sys/stat.cpp
src/sys/mman.cpp src/sys/mman.cpp
src/sys/wait.cpp src/sys/wait.cpp

View File

@ -27,6 +27,9 @@ extern FILE* stderr;
#define stderr stderr #define stderr stderr
#define BUFSIZ 1024 #define BUFSIZ 1024
#define FILENAME_MAX \
1024 // As Luna does not impose a limit on this, this is the recommended size for character arrays holding a file
// name.
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
@ -41,6 +44,9 @@ extern "C"
/* Bind a stream to a file descriptor. */ /* Bind a stream to a file descriptor. */
FILE* fdopen(int fd, const char* mode); FILE* fdopen(int fd, const char* mode);
/* Change the underlying file and mode of a stream. */
FILE* freopen(const char* path, const char* mode, FILE* stream);
/* Close a file and frees up its stream. */ /* Close a file and frees up its stream. */
int fclose(FILE* stream); int fclose(FILE* stream);
@ -95,6 +101,9 @@ extern "C"
/* Read a character from standard input. */ /* Read a character from standard input. */
int getchar(void); int getchar(void);
/* Push a character back to stream so that it can be read again. */
int ungetc(int c, FILE* stream);
/* Read a line from stream. */ /* Read a line from stream. */
char* fgets(char* buf, size_t size, FILE* stream); char* fgets(char* buf, size_t size, FILE* stream);
@ -122,16 +131,34 @@ extern "C"
int snprintf(char* buf, size_t max, const char* format, ...); int snprintf(char* buf, size_t max, const char* format, ...);
/* Write formatted output into a buffer. */ /* Write formatted output into a buffer. */
int vsprintf(char*, const char*, va_list); int vsprintf(char* buf, const char* format, va_list ap);
/* Write up to max bytes of formatted output into a buffer. */ /* Write up to max bytes of formatted output into a buffer. */
int vsnprintf(char*, size_t, const char*, va_list); int vsnprintf(char* buf, size_t max, const char* format, va_list ap);
/* Write formatted output to standard output. */ /* Write formatted output to standard output. */
int vprintf(const char*, va_list ap); int vprintf(const char* format, va_list ap);
/* Write formatted output to standard output. */ /* Write formatted output to standard output. */
int printf(const char*, ...); int printf(const char* format, ...);
/* Scan formatted input from a string. */
int vsscanf(const char* str, const char* format, va_list ap);
/* Scan formatted input from a string. */
int sscanf(const char* str, const char* format, ...);
/* Scan formatted input from a file. */
int vfscanf(FILE* stream, const char* format, va_list ap);
/* Scan formatted input from a file. */
int fscanf(FILE* stream, const char* format, ...);
/* Scan formatted input from standard input. */
int vscanf(const char* format, va_list ap);
/* Scan formatted input from standard input. */
int scanf(const char* format, ...);
/* Write a string followed by a newline to standard output. */ /* Write a string followed by a newline to standard output. */
int puts(const char* s); int puts(const char* s);

263
libc/src/scanf.cpp Normal file
View File

@ -0,0 +1,263 @@
#include <errno.h>
#include <luna/CType.h>
#include <luna/NumberParsing.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FLAG_DISCARD (1 << 0)
#define FLAG_ALLOC (1 << 1)
#define FLAG_WIDTH (1 << 2)
#define FLAG_LONG (1 << 3)
#define FLAG_LONG_LONG (1 << 4)
#define FLAG_SHORT (1 << 5)
#define FLAG_CHAR (1 << 6)
static int parse_flags(const char** format)
{
int result = 0;
while (true)
{
switch (**format)
{
case '*':
result |= FLAG_DISCARD;
(*format)++;
break;
case 'm':
result |= FLAG_ALLOC;
(*format)++;
break;
default: return result;
}
}
}
static size_t parse_width(const char** format, int& flags)
{
size_t result = 0;
if (_isdigit(**format))
{
result = scan_unsigned_integer(format);
flags |= FLAG_WIDTH;
}
return result;
}
static void parse_type(const char** format, int& flags)
{
// FIXME: Support %j (intmax_t/uintmax_t)
switch (**format)
{
case 'h':
flags |= FLAG_SHORT;
(*format)++;
if (**format == 'h')
{
flags |= FLAG_CHAR;
(*format)++;
}
break;
case 'l':
flags |= FLAG_LONG;
(*format)++;
if (**format == 'l')
{
flags |= FLAG_LONG_LONG;
(*format)++;
}
break;
case 't':
flags |= (sizeof(ptrdiff_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
case 'z':
flags |= (sizeof(size_t) == sizeof(long)) ? FLAG_LONG : FLAG_LONG_LONG;
(*format)++;
break;
default: break;
}
}
static void write_parsed_signed_integer(ssize_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, signed long long*) = (signed long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, signed long*) = (signed long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, signed int*) = (signed short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, signed int*) = (signed char)value;
else
*va_arg(ap, signed int*) = (signed int)value;
}
static void write_parsed_unsigned_integer(size_t value, int flags, va_list ap)
{
if (flags & FLAG_LONG_LONG) *va_arg(ap, unsigned long long*) = (unsigned long long)value;
else if (flags & FLAG_LONG)
*va_arg(ap, unsigned long*) = (unsigned long)value;
else if (flags & FLAG_SHORT)
*va_arg(ap, unsigned int*) = (unsigned short)value;
else if (flags & FLAG_CHAR)
*va_arg(ap, unsigned int*) = (unsigned char)value;
else
*va_arg(ap, unsigned int*) = (unsigned int)value;
}
#define WHITESPACE_CHARACTERS " \t\f\r\n\v"
static void skip_whitespace(const char** str)
{
*str += strspn(*str, WHITESPACE_CHARACTERS);
}
extern "C"
{
int vsscanf(const char* str, const char* format, va_list ap)
{
int parsed = 0;
const char* s = str; // Keep a pointer to the beginning of the string for %n
if (*str == 0) return EOF;
while (*format)
{
if (*format != '%')
{
normal:
if (!_isspace(*format))
{
if (*str != *format) return parsed;
str++;
format++;
if (*str == 0) return parsed;
continue;
}
skip_whitespace(&format);
skip_whitespace(&str);
if (*str == 0) return parsed;
continue;
}
else
{
format++;
if (*format == '%')
{
skip_whitespace(&str);
goto normal;
}
int flags = parse_flags(&format);
size_t width = parse_width(&format, flags);
parse_type(&format, flags);
char specifier = *format++;
if (!specifier) return parsed;
switch (specifier)
{
case 's': {
skip_whitespace(&str);
size_t chars = strcspn(str, WHITESPACE_CHARACTERS);
if (!chars) return parsed;
if ((flags & FLAG_WIDTH) && chars > width) chars = width;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc(chars + 1);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, chars);
ptr[chars] = 0;
}
str += chars;
parsed++;
break;
}
case 'c': {
if (strlen(str) < width) return parsed;
if (!(flags & FLAG_WIDTH)) width = 1;
if (!(flags & FLAG_DISCARD))
{
char* ptr;
if (flags & FLAG_ALLOC)
{
ptr = (char*)malloc(width);
if (!ptr) return parsed;
*va_arg(ap, char**) = ptr;
}
else
ptr = va_arg(ap, char*);
memcpy(ptr, str, width);
}
str += width;
parsed++;
break;
}
case 'd': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'i': {
skip_whitespace(&str);
ssize_t value = scan_signed_integer(&str, 0);
if (!(flags & FLAG_DISCARD)) write_parsed_signed_integer(value, flags, ap);
parsed++;
break;
}
case 'o': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 8);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'u': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 10);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'X':
case 'x': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) write_parsed_unsigned_integer(value, flags, ap);
parsed++;
break;
}
case 'p': {
skip_whitespace(&str);
size_t value = scan_unsigned_integer(&str, 16);
if (!(flags & FLAG_DISCARD)) *va_arg(ap, void**) = (void*)value;
parsed++;
break;
}
case 'n': {
if (!(flags & FLAG_DISCARD)) *va_arg(ap, int*) = (int)(str - s);
break;
}
default: {
fprintf(stderr, "vsscanf: unknown conversion specifier: %%%c\n", specifier);
return parsed;
}
}
}
}
return parsed;
}
}

View File

@ -11,6 +11,13 @@ FILE* stdin = nullptr;
FILE* stderr = nullptr; FILE* stderr = nullptr;
FILE* stdout = nullptr; FILE* stdout = nullptr;
static const char* read_tmpdir()
{
const char* tmpdir = getenv("TMPDIR");
if (!tmpdir) return "/tmp";
return tmpdir;
}
static int fopen_parse_mode(const char* mode) static int fopen_parse_mode(const char* mode)
{ {
int result = 0; int result = 0;
@ -46,6 +53,12 @@ static int fdopen_check_compatible_mode(int fd, int new_flags)
extern "C" extern "C"
{ {
int fflush(FILE*)
{
// FIXME: Files are not buffered right now.
return 0;
}
FILE* fopen(const char* path, const char* mode) FILE* fopen(const char* path, const char* mode)
{ {
int flags; int flags;
@ -85,6 +98,25 @@ extern "C"
return f; return f;
} }
FILE* freopen(const char* path, const char* mode, FILE* stream)
{
int flags;
if ((flags = fopen_parse_mode(mode)) < 0) return nullptr;
close(stream->_fd);
if (!path) { fail("FIXME: freopen() called with path=nullptr"); }
int fd = open(path, flags, 0666);
if (fd < 0) { return nullptr; }
stream->_fd = fd;
clearerr(stream);
return stream;
}
int fclose(FILE* stream) int fclose(FILE* stream)
{ {
if (close(stream->_fd) < 0) return EOF; if (close(stream->_fd) < 0) return EOF;
@ -381,6 +413,54 @@ extern "C"
return rc; return rc;
} }
int sscanf(const char* str, const char* format, ...)
{
va_list ap;
va_start(ap, format);
int rc = vsscanf(str, format, ap);
va_end(ap);
return rc;
}
int vfscanf(FILE* stream, const char* format, va_list ap)
{
char buf[BUFSIZ];
if (!fgets(buf, sizeof(buf), stream)) return EOF;
return vsscanf(buf, format, ap);
}
int fscanf(FILE* stream, const char* format, ...)
{
va_list ap;
va_start(ap, format);
int rc = vfscanf(stream, format, ap);
va_end(ap);
return rc;
}
int vscanf(const char* format, va_list ap)
{
return vfscanf(stdin, format, ap);
}
int scanf(const char* format, ...)
{
va_list ap;
va_start(ap, format);
int rc = vfscanf(stdin, format, ap);
va_end(ap);
return rc;
}
int puts(const char* s) int puts(const char* s)
{ {
if (fputs(s, stdout) < 0) return -1; if (fputs(s, stdout) < 0) return -1;
@ -404,12 +484,16 @@ extern "C"
FILE* tmpfile() FILE* tmpfile()
{ {
// FIXME: use /tmp as the directory when the tmpfs is mounted only there. int fd = open(read_tmpdir(), O_RDWR | O_TMPFILE, 0600);
int fd = open("/", O_RDWR | O_TMPFILE, 0600);
if (fd < 0) return nullptr; if (fd < 0) return nullptr;
FILE* f = fdopen(fd, "w+b"); FILE* f = fdopen(fd, "w+b");
if (!f) close(fd); if (!f) close(fd);
return f; return f;
} }
int ungetc(int, FILE*)
{
fail("FIXME: ungetc: not implemented");
}
} }

View File

@ -2,10 +2,10 @@
#include <luna/Types.h> #include <luna/Types.h>
// Parse an unsigned integer and advance *str to point to the first non-digit character after the number. // Parse an unsigned integer and advance *str to point to the first non-digit character after the number.
usize scan_unsigned_integer(const char** str); usize scan_unsigned_integer(const char** str, int base = 10);
// Parse a signed integer and advance *str to point to the first non-digit character after the number. // Parse a signed integer and advance *str to point to the first non-digit character after the number.
isize scan_signed_integer(const char** str); isize scan_signed_integer(const char** str, int base = 10);
// Parse an unsigned integer, similar to strtoull(). // Parse an unsigned integer, similar to strtoull().
usize parse_unsigned_integer(const char* str, const char** endptr, int base); usize parse_unsigned_integer(const char* str, const char** endptr, int base);

View File

@ -37,8 +37,8 @@ class PathParser
return m_already_called_next ? (bool)m_strtok_saved_state : is_not_delim(*m_copy); return m_already_called_next ? (bool)m_strtok_saved_state : is_not_delim(*m_copy);
} }
Result<String> basename(); static Result<String> basename(StringView path);
Result<String> dirname(); static Result<String> dirname(StringView path);
Option<const char*> next(); Option<const char*> next();

View File

@ -212,7 +212,7 @@ template <typename T> class Vector
{ {
Vector<T> other; Vector<T> other;
TRY(other.try_reserve(m_capacity)); TRY(other.try_reserve(m_capacity));
memcpy(other.m_data, m_data, m_size); memcpy(other.m_data, m_data, m_size * sizeof(T));
other.m_size = m_size; other.m_size = m_size;
return other; return other;
} }

View File

@ -150,7 +150,7 @@ static usize parse_precision(const char** format, flags_t& flags, va_list ap)
return result; return result;
} }
static void parse_length(const char** format, flags_t& flags) static void parse_type(const char** format, flags_t& flags)
{ {
// FIXME: Support %j (intmax_t/uintmax_t) // FIXME: Support %j (intmax_t/uintmax_t)
switch (**format) switch (**format)
@ -415,7 +415,7 @@ Result<usize> cstyle_format(const char* format, callback_t callback, void* arg,
flags_t flags = parse_flags(&format); flags_t flags = parse_flags(&format);
const usize width = parse_width(&format, flags, ap); const usize width = parse_width(&format, flags, ap);
usize precision = parse_precision(&format, flags, ap); usize precision = parse_precision(&format, flags, ap);
parse_length(&format, flags); parse_type(&format, flags);
conv_state vstate = { flags, width, precision }; conv_state vstate = { flags, width, precision };
@ -503,15 +503,18 @@ usize vstring_format(char* buf, usize max, const char* format, va_list ap)
[](char c, void* arg) -> Result<void> { [](char c, void* arg) -> Result<void> {
StringFormatInfo* info_arg = (StringFormatInfo*)arg; StringFormatInfo* info_arg = (StringFormatInfo*)arg;
if (!info_arg->remaining) return {}; if (!info_arg->remaining) return {};
if (info_arg->buffer)
{
*(info_arg->buffer) = c; *(info_arg->buffer) = c;
info_arg->buffer++; info_arg->buffer++;
}
info_arg->remaining--; info_arg->remaining--;
return {}; return {};
}, },
&info, ap) &info, ap)
.value(); .value();
*(info.buffer) = 0; if (info.buffer) *(info.buffer) = 0;
return result; return result;
} }

View File

@ -89,12 +89,12 @@ isize parse_signed_integer(const char* str, const char** endptr, int base)
return negative ? -(isize)rc : (isize)rc; return negative ? -(isize)rc : (isize)rc;
} }
usize scan_unsigned_integer(const char** str) usize scan_unsigned_integer(const char** str, int base)
{ {
return parse_unsigned_integer(*str, str, 10); return parse_unsigned_integer(*str, str, base);
} }
isize scan_signed_integer(const char** str) isize scan_signed_integer(const char** str, int base)
{ {
return parse_signed_integer(*str, str, 10); return parse_signed_integer(*str, str, base);
} }

View File

@ -35,9 +35,9 @@ Option<const char*> PathParser::next()
return result; return result;
} }
Result<String> PathParser::basename() Result<String> PathParser::basename(StringView path)
{ {
char* copy = strdup(m_original); char* copy = strdup(path.chars());
if (!copy) return err(ENOMEM); if (!copy) return err(ENOMEM);
auto guard = make_scope_guard([copy] { free_impl(copy); }); auto guard = make_scope_guard([copy] { free_impl(copy); });
@ -48,9 +48,9 @@ Result<String> PathParser::basename()
return String::from_cstring(result); return String::from_cstring(result);
} }
Result<String> PathParser::dirname() Result<String> PathParser::dirname(StringView path)
{ {
char* copy = strdup(m_original); char* copy = strdup(path.chars());
if (!copy) return err(ENOMEM); if (!copy) return err(ENOMEM);
auto guard = make_scope_guard([copy] { free_impl(copy); }); auto guard = make_scope_guard([copy] { free_impl(copy); });

View File

@ -6,5 +6,5 @@ Stack::Stack(u64 base, usize bytes) : m_base(base), m_bytes(bytes)
u64 Stack::top() const u64 Stack::top() const
{ {
return (m_base + m_bytes) - sizeof(void*); return (m_base + m_bytes) - 16;
} }

View File

@ -118,7 +118,7 @@ namespace os
bool is_still_parsing_flags = true; bool is_still_parsing_flags = true;
Vector<PositionalArgument> positional_args = TRY(m_positional_args.deep_copy()); Vector<PositionalArgument> positional_args = TRY(m_positional_args.shallow_copy());
for (int i = 1; i < argc; i++) for (int i = 1; i < argc; i++)
{ {

View File

@ -19,6 +19,7 @@ luna_test(libluna/TestUtf8.cpp TestUtf8)
luna_test(libluna/TestFormat.cpp TestFormat) luna_test(libluna/TestFormat.cpp TestFormat)
luna_test(libluna/TestHashTable.cpp TestHashTable) luna_test(libluna/TestHashTable.cpp TestHashTable)
luna_test(libluna/TestCPath.cpp TestCPath) luna_test(libluna/TestCPath.cpp TestCPath)
luna_test(libc/TestScanf.cpp TestScanf)
luna_app(run-tests.cpp run-tests) luna_app(run-tests.cpp run-tests)
endif() endif()

91
tests/libc/TestScanf.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <stdio.h>
#include <string.h>
#include <test.h>
// FIXME: Add more tests.
TestResult test_basic_scanf()
{
char hello[21];
char world[21];
int parsed = sscanf("hello world", "%20s %20s", hello, world);
validate(parsed == 2);
validate(!strcmp(hello, "hello"));
validate(!strcmp(world, "world"));
test_success;
}
TestResult test_incomplete_scanf()
{
char hello[21];
char world[21];
int parsed = sscanf("hello ", "%20s %20s", hello, world);
validate(parsed == 1);
validate(!strcmp(hello, "hello"));
test_success;
}
TestResult test_integer_scanf()
{
int hour;
int min;
int parsed = sscanf("23:59", "%d:%d", &hour, &min);
validate(parsed == 2);
validate(hour == 23);
validate(min == 59);
test_success;
}
TestResult test_integer_auto_base_scanf()
{
int a;
int b;
int c;
int parsed = sscanf("65, \t0x23, 0755", "%i, %i, %i", &a, &b, &c);
validate(parsed == 3);
validate(a == 65);
validate(b == 0x23);
validate(c == 0755);
test_success;
}
TestResult test_scanf_characters_consumed()
{
int hour;
int min;
int nr_chars;
int parsed = sscanf("23:59", "%d:%d%n", &hour, &min, &nr_chars);
validate(parsed == 2);
validate(hour == 23);
validate(min == 59);
validate(nr_chars == 5);
test_success;
}
Result<void> test_main()
{
test_prelude;
run_test(test_basic_scanf);
run_test(test_incomplete_scanf);
run_test(test_integer_scanf);
run_test(test_integer_auto_base_scanf);
run_test(test_scanf_characters_consumed);
return {};
}