Compare commits
22 Commits
e5259d5849
...
73fbc37841
Author | SHA1 | Date | |
---|---|---|---|
73fbc37841 | |||
479016ab20 | |||
3d157b760c | |||
54a1998d42 | |||
e60b2a3d2f | |||
f052d8630d | |||
8542cf7cbf | |||
d50ea76bdc | |||
2edb0a3f3a | |||
b4a6e4d56d | |||
f22689fcf5 | |||
6bfc7483bc | |||
acfad51ac0 | |||
7efc3a6ea1 | |||
0b553cadc0 | |||
ae01a31104 | |||
795b0ca8d4 | |||
21cc7e3729 | |||
55d147841f | |||
a2c081f219 | |||
8c2348c425 | |||
25e9187826 |
@ -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)
|
||||||
|
@ -279,7 +279,7 @@ static void mount_devfs()
|
|||||||
{
|
{
|
||||||
if (mkdir("/dev", 0755) < 0 && errno != EEXIST) exit(255);
|
if (mkdir("/dev", 0755) < 0 && errno != EEXIST) exit(255);
|
||||||
|
|
||||||
if (mount("/dev", "devfs") < 0) exit(255);
|
if (mount("/dev", "devfs", "devfs") < 0) exit(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -6,15 +6,17 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
{
|
{
|
||||||
StringView target;
|
StringView target;
|
||||||
StringView fstype { "auto" };
|
StringView fstype { "auto" };
|
||||||
|
StringView source;
|
||||||
|
|
||||||
os::ArgumentParser parser;
|
os::ArgumentParser parser;
|
||||||
parser.add_description("Mount a file system.");
|
parser.add_description("Mount a file system.");
|
||||||
parser.add_system_program_info("mount"_sv);
|
parser.add_system_program_info("mount"_sv);
|
||||||
parser.add_positional_argument(target, "mountpoint"_sv, true);
|
parser.add_positional_argument(target, "mountpoint"_sv, true);
|
||||||
|
parser.add_positional_argument(source, "source"_sv, true);
|
||||||
parser.add_value_argument(fstype, 't', "type"_sv, "the file system type to use");
|
parser.add_value_argument(fstype, 't', "type"_sv, "the file system type to use");
|
||||||
parser.parse(argc, argv);
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
if (mount(target.chars(), fstype.chars()) < 0)
|
if (mount(target.chars(), fstype.chars(), source.chars()) < 0)
|
||||||
{
|
{
|
||||||
perror("mount");
|
perror("mount");
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
mkdir -p /tmp
|
mkdir -p /tmp
|
||||||
mount -t tmpfs /tmp
|
mount -t tmpfs /tmp tmpfs
|
||||||
chmod 1777 /tmp
|
chmod 1777 /tmp
|
||||||
|
@ -47,6 +47,7 @@ set(SOURCES
|
|||||||
src/fs/GPT.cpp
|
src/fs/GPT.cpp
|
||||||
src/fs/tmpfs/FileSystem.cpp
|
src/fs/tmpfs/FileSystem.cpp
|
||||||
src/fs/tmpfs/Inode.cpp
|
src/fs/tmpfs/Inode.cpp
|
||||||
|
src/fs/ext2/FileSystem.cpp
|
||||||
src/fs/devices/DeviceRegistry.cpp
|
src/fs/devices/DeviceRegistry.cpp
|
||||||
src/fs/devices/NullDevice.cpp
|
src/fs/devices/NullDevice.cpp
|
||||||
src/fs/devices/ZeroDevice.cpp
|
src/fs/devices/ZeroDevice.cpp
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
28
kernel/src/fs/ext2/FileSystem.cpp
Normal file
28
kernel/src/fs/ext2/FileSystem.cpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#include "fs/ext2/FileSystem.h"
|
||||||
|
|
||||||
|
namespace Ext2
|
||||||
|
{
|
||||||
|
FileSystem::FileSystem()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> FileSystem::find_inode_by_number(ino_t inode)
|
||||||
|
{
|
||||||
|
auto maybe_inode = m_inode_cache.try_get(inode);
|
||||||
|
if (maybe_inode.has_value()) return maybe_inode.value();
|
||||||
|
|
||||||
|
// TODO: Locate the inode's block group descriptor and find it in the block group's inode table.
|
||||||
|
return err(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::FileSystem>> FileSystem::create(SharedPtr<Device> host_device)
|
||||||
|
{
|
||||||
|
SharedPtr<FileSystem> fs = TRY(adopt_shared_if_nonnull(new (std::nothrow) FileSystem()));
|
||||||
|
const usize nread = TRY(host_device->read((u8*)&fs->m_superblock, 1024, 1024));
|
||||||
|
if (nread != 1024) return err(EINVAL); // Source had an invalid superblock.
|
||||||
|
if (fs->m_superblock.signature != EXT2_MAGIC) return err(EINVAL); // Source had an invalid superblock.
|
||||||
|
|
||||||
|
// TODO: Implement basic Ext2 reading, enough to be able to mount a volume.
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
}
|
101
kernel/src/fs/ext2/FileSystem.h
Normal file
101
kernel/src/fs/ext2/FileSystem.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include <luna/HashMap.h>
|
||||||
|
|
||||||
|
#define EXT2_MAGIC 0xef53
|
||||||
|
|
||||||
|
namespace Ext2
|
||||||
|
{
|
||||||
|
struct [[gnu::packed]] Superblock
|
||||||
|
{
|
||||||
|
u32 nr_inodes;
|
||||||
|
u32 nr_blocks;
|
||||||
|
u32 nr_reserved;
|
||||||
|
u32 nr_free_inodes;
|
||||||
|
u32 nr_free_blocks;
|
||||||
|
u32 superblock_block;
|
||||||
|
u32 log_block_size;
|
||||||
|
u32 log_fragment_size;
|
||||||
|
u32 blocks_per_block_group;
|
||||||
|
u32 fragments_per_block_group;
|
||||||
|
u32 inodes_per_block_group;
|
||||||
|
u32 last_mount_time;
|
||||||
|
u32 last_write_time;
|
||||||
|
u16 mounts_since_last_fsck;
|
||||||
|
u16 mounts_allowed_before_fsck;
|
||||||
|
u16 signature;
|
||||||
|
u16 fs_state;
|
||||||
|
u16 error_action;
|
||||||
|
u16 minor_version;
|
||||||
|
u32 last_fsck_time;
|
||||||
|
u32 fsck_time_interval;
|
||||||
|
u32 os_id;
|
||||||
|
u32 major_version;
|
||||||
|
u16 reserved_block_uid;
|
||||||
|
u16 reserved_block_gid;
|
||||||
|
// TODO: Add extended superblock fields.
|
||||||
|
u8 padding[1024 - 84];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Superblock) == 1024);
|
||||||
|
|
||||||
|
class FileSystem : public VFS::FileSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SharedPtr<VFS::Inode> root_inode() const override
|
||||||
|
{
|
||||||
|
return m_root_inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_file_inode() override
|
||||||
|
{
|
||||||
|
return err(EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_dir_inode(SharedPtr<VFS::Inode>) override
|
||||||
|
{
|
||||||
|
return err(EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_device_inode(u32, u32) override
|
||||||
|
{
|
||||||
|
return err(EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> create_symlink_inode(StringView) override
|
||||||
|
{
|
||||||
|
return err(EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> set_mount_dir(SharedPtr<VFS::Inode>) override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result<SharedPtr<VFS::FileSystem>> create(SharedPtr<Device> host_device);
|
||||||
|
|
||||||
|
dev_t host_device_id() const override
|
||||||
|
{
|
||||||
|
return m_host_device_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<VFS::Inode>> find_inode_by_number(ino_t inode);
|
||||||
|
|
||||||
|
virtual ~FileSystem() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileSystem();
|
||||||
|
|
||||||
|
SharedPtr<VFS::Inode> m_root_inode;
|
||||||
|
|
||||||
|
SharedPtr<Device> m_host_device;
|
||||||
|
dev_t m_host_device_id;
|
||||||
|
|
||||||
|
Superblock m_superblock;
|
||||||
|
|
||||||
|
// FIXME: This inode cache will keep all inodes in it alive despite having no other references to it, but we're
|
||||||
|
// not worrying about that as for now the filesystem implementation is read-only.
|
||||||
|
HashMap<ino_t, SharedPtr<VFS::Inode>> m_inode_cache;
|
||||||
|
};
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
@ -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()));
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "fs/VFS.h"
|
#include "fs/VFS.h"
|
||||||
|
#include "fs/ext2/FileSystem.h"
|
||||||
#include "fs/tmpfs/FileSystem.h"
|
#include "fs/tmpfs/FileSystem.h"
|
||||||
#include "memory/MemoryManager.h"
|
#include "memory/MemoryManager.h"
|
||||||
#include "sys/Syscall.h"
|
#include "sys/Syscall.h"
|
||||||
@ -8,15 +9,28 @@ Result<u64> sys_mount(Registers*, SyscallArgs args)
|
|||||||
{
|
{
|
||||||
auto target = TRY(MemoryManager::strdup_from_user(args[0]));
|
auto target = TRY(MemoryManager::strdup_from_user(args[0]));
|
||||||
auto fstype = TRY(MemoryManager::strdup_from_user(args[1]));
|
auto fstype = TRY(MemoryManager::strdup_from_user(args[1]));
|
||||||
|
auto source = TRY(MemoryManager::strdup_from_user(args[2]));
|
||||||
|
|
||||||
auto* current = Scheduler::current();
|
auto* current = Scheduler::current();
|
||||||
if (current->auth.euid != 0) return err(EPERM);
|
if (current->auth.euid != 0) return err(EPERM);
|
||||||
|
|
||||||
|
auto get_source = [current, &source]() -> Result<SharedPtr<Device>> {
|
||||||
|
auto inode = TRY(VFS::resolve_path(source.chars(), current->auth, current->current_directory));
|
||||||
|
if (inode->type() != VFS::InodeType::BlockDevice) return err(ENOTBLK);
|
||||||
|
dev_t device_id = inode->device_id();
|
||||||
|
return TRY(DeviceRegistry::fetch_special_device(luna_dev_major(device_id), luna_dev_minor(device_id)));
|
||||||
|
};
|
||||||
|
|
||||||
SharedPtr<VFS::FileSystem> fs;
|
SharedPtr<VFS::FileSystem> fs;
|
||||||
|
|
||||||
if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create());
|
if (fstype.view() == "tmpfs") fs = TRY(TmpFS::FileSystem::create());
|
||||||
else if (fstype.view() == "devfs")
|
else if (fstype.view() == "devfs")
|
||||||
fs = TRY(DeviceRegistry::create_devfs_instance());
|
fs = TRY(DeviceRegistry::create_devfs_instance());
|
||||||
|
else if (fstype.view() == "ext2")
|
||||||
|
{
|
||||||
|
auto source_device = TRY(get_source());
|
||||||
|
fs = TRY(Ext2::FileSystem::create(source_device));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return err(ENODEV);
|
return err(ENODEV);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -9,7 +9,7 @@ extern "C"
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Mount a file system on target. */
|
/* Mount a file system on target. */
|
||||||
int mount(const char* target, const char* fstype);
|
int mount(const char* target, const char* fstype, const char* source);
|
||||||
|
|
||||||
/* Unmount the file system mounted on target. */
|
/* Unmount the file system mounted on target. */
|
||||||
int umount(const char* target);
|
int umount(const char* target);
|
||||||
|
263
libc/src/scanf.cpp
Normal file
263
libc/src/scanf.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
int mount(const char* target, const char* fstype)
|
int mount(const char* target, const char* fstype, const char* source)
|
||||||
{
|
{
|
||||||
long rc = syscall(SYS_mount, target, fstype);
|
long rc = syscall(SYS_mount, target, fstype, source);
|
||||||
__errno_return(rc, int);
|
__errno_return(rc, int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <luna/Alloc.h>
|
#include <luna/Alloc.h>
|
||||||
#include <luna/Atomic.h>
|
#include <luna/Atomic.h>
|
||||||
|
#include <luna/Hash.h>
|
||||||
#include <luna/OwnedPtr.h>
|
#include <luna/OwnedPtr.h>
|
||||||
#include <luna/Result.h>
|
#include <luna/Result.h>
|
||||||
#include <luna/ScopeGuard.h>
|
#include <luna/ScopeGuard.h>
|
||||||
@ -84,6 +85,11 @@ template <typename T> class SharedPtr
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator==(const SharedPtr<T>& other)
|
||||||
|
{
|
||||||
|
return m_ptr == other.m_ptr && m_ref_count == other.m_ref_count;
|
||||||
|
}
|
||||||
|
|
||||||
T* ptr() const
|
T* ptr() const
|
||||||
{
|
{
|
||||||
return m_ptr;
|
return m_ptr;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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); });
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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++)
|
||||||
{
|
{
|
||||||
|
@ -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
91
tests/libc/TestScanf.cpp
Normal 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 {};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user