Compare commits
31 Commits
9de4b392a2
...
72b8ebe02c
Author | SHA1 | Date | |
---|---|---|---|
72b8ebe02c | |||
72e798cedb | |||
7593947c33 | |||
93922932fa | |||
a3beaa4d53 | |||
bb0db450b3 | |||
a0fa1f2cfd | |||
2fa11a5ae3 | |||
cc8450751c | |||
3762d3f959 | |||
a99c5e325d | |||
82db0e39ea | |||
46c45068e0 | |||
cfcde5af55 | |||
268252c89e | |||
5d16754632 | |||
6307b01689 | |||
e118c9ea0d | |||
ee691bbb0f | |||
739950e8f0 | |||
3a84127fd6 | |||
e8507d23ee | |||
d9a1e8a980 | |||
7efc6dc985 | |||
beeafb73e6 | |||
f0caf010bf | |||
d589834eb7 | |||
da1439126a | |||
e4e501ecfe | |||
a1bf4dafbe | |||
bc07cc94cb |
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -13,5 +13,16 @@
|
|||||||
"files.trimFinalNewlines": true,
|
"files.trimFinalNewlines": true,
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"git.inputValidationLength": 72,
|
"git.inputValidationLength": 72,
|
||||||
"git.inputValidationSubjectLength": 72
|
"git.inputValidationSubjectLength": 72,
|
||||||
|
"doxdocgen.file.fileOrder": [
|
||||||
|
"file",
|
||||||
|
"author",
|
||||||
|
"brief",
|
||||||
|
"empty",
|
||||||
|
"copyright",
|
||||||
|
"empty"
|
||||||
|
],
|
||||||
|
"doxdocgen.file.copyrightTag": [
|
||||||
|
"@copyright Copyright (c) {year}, the Luna authors."
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <sys/sysmacros.h>
|
#include <sys/sysmacros.h>
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
FILE* g_init_log;
|
FILE* g_init_log;
|
||||||
@ -97,8 +96,7 @@ static Result<void> try_start_service(Service& service)
|
|||||||
fprintf(g_init_log, "[init] waiting for child process %d to finish\n", pid);
|
fprintf(g_init_log, "[init] waiting for child process %d to finish\n", pid);
|
||||||
|
|
||||||
int status;
|
int status;
|
||||||
pid_t id = waitpid(pid, &status, 0);
|
TRY(os::Process::wait(pid, &status));
|
||||||
if (id < 0) { return err(errno); }
|
|
||||||
|
|
||||||
fprintf(g_init_log, "[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
|
fprintf(g_init_log, "[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
|
||||||
}
|
}
|
||||||
@ -312,7 +310,10 @@ int main()
|
|||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
pid_t child = wait(&status);
|
auto rc = os::Process::wait(os::Process::ANY_CHILD, &status);
|
||||||
|
if (rc.has_error()) continue;
|
||||||
|
|
||||||
|
pid_t child = rc.release_value();
|
||||||
|
|
||||||
for (auto& service : g_services)
|
for (auto& service : g_services)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
using os::File;
|
using os::File;
|
||||||
@ -118,11 +117,7 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
|
|
||||||
if (child == 0) { TRY(execute_command(cmd.view())); }
|
if (child == 0) { TRY(execute_command(cmd.view())); }
|
||||||
|
|
||||||
if (waitpid(child, NULL, 0) < 0)
|
TRY(os::Process::wait(child, nullptr));
|
||||||
{
|
|
||||||
perror("waitpid");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
Result<int> luna_main(int argc, char** argv)
|
Result<int> luna_main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
@ -23,11 +22,7 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
unreachable();
|
unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitpid(pid, nullptr, 0) < 0)
|
TRY(os::Process::wait(pid, nullptr));
|
||||||
{
|
|
||||||
perror("waitpid");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct rusage usage;
|
struct rusage usage;
|
||||||
if (getrusage(RUSAGE_CHILDREN, &usage) < 0)
|
if (getrusage(RUSAGE_CHILDREN, &usage) < 0)
|
||||||
|
@ -43,6 +43,7 @@ set(SOURCES
|
|||||||
src/fs/VFS.cpp
|
src/fs/VFS.cpp
|
||||||
src/fs/Pipe.cpp
|
src/fs/Pipe.cpp
|
||||||
src/fs/Mount.cpp
|
src/fs/Mount.cpp
|
||||||
|
src/fs/MBR.cpp
|
||||||
src/fs/tmpfs/FileSystem.cpp
|
src/fs/tmpfs/FileSystem.cpp
|
||||||
src/fs/devices/DeviceRegistry.cpp
|
src/fs/devices/DeviceRegistry.cpp
|
||||||
src/fs/devices/NullDevice.cpp
|
src/fs/devices/NullDevice.cpp
|
||||||
@ -65,6 +66,7 @@ if("${LUNA_ARCH}" MATCHES "x86_64")
|
|||||||
src/arch/x86_64/Thread.cpp
|
src/arch/x86_64/Thread.cpp
|
||||||
src/arch/x86_64/PCI.cpp
|
src/arch/x86_64/PCI.cpp
|
||||||
src/arch/x86_64/Keyboard.cpp
|
src/arch/x86_64/Keyboard.cpp
|
||||||
|
src/arch/x86_64/disk/ATA.cpp
|
||||||
src/arch/x86_64/init/GDT.cpp
|
src/arch/x86_64/init/GDT.cpp
|
||||||
src/arch/x86_64/init/IDT.cpp
|
src/arch/x86_64/init/IDT.cpp
|
||||||
src/arch/x86_64/init/PIC.cpp
|
src/arch/x86_64/init/PIC.cpp
|
||||||
|
@ -22,6 +22,9 @@ namespace CPU
|
|||||||
void disable_interrupts();
|
void disable_interrupts();
|
||||||
void wait_for_interrupt();
|
void wait_for_interrupt();
|
||||||
|
|
||||||
|
bool save_interrupts();
|
||||||
|
void restore_interrupts(bool saved);
|
||||||
|
|
||||||
void get_stack_trace(void (*callback)(u64, void*), void* arg);
|
void get_stack_trace(void (*callback)(u64, void*), void* arg);
|
||||||
void print_stack_trace();
|
void print_stack_trace();
|
||||||
void get_stack_trace_at(Registers* regs, void (*callback)(u64, void*), void* arg);
|
void get_stack_trace_at(Registers* regs, void (*callback)(u64, void*), void* arg);
|
||||||
@ -29,5 +32,8 @@ namespace CPU
|
|||||||
|
|
||||||
u16 get_processor_id();
|
u16 get_processor_id();
|
||||||
|
|
||||||
|
bool register_interrupt(u8 interrupt, void (*handler)(Registers*, void*), void* context);
|
||||||
|
void sync_interrupts();
|
||||||
|
|
||||||
void pause();
|
void pause();
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,10 @@ struct ScanInfo
|
|||||||
|
|
||||||
namespace PCI
|
namespace PCI
|
||||||
{
|
{
|
||||||
|
BAR::BAR(u32 raw) : m_raw(raw)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Device::ID read_id(const Device::Address& address)
|
Device::ID read_id(const Device::Address& address)
|
||||||
{
|
{
|
||||||
const u16 vendor = read16(address, Field::VendorID);
|
const u16 vendor = read16(address, Field::VendorID);
|
||||||
@ -108,4 +112,13 @@ namespace PCI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BAR Device::getBAR(u8 index) const
|
||||||
|
{
|
||||||
|
check(index < 6);
|
||||||
|
|
||||||
|
u32 raw = read32(address, 0x10 + (index * 4));
|
||||||
|
|
||||||
|
return { raw };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,67 @@ namespace PCI
|
|||||||
|
|
||||||
HeaderType = 0x0e,
|
HeaderType = 0x0e,
|
||||||
|
|
||||||
SecondaryBus = 0x19
|
BAR0 = 0x10,
|
||||||
|
BAR1 = 0x14,
|
||||||
|
BAR2 = 0x18,
|
||||||
|
BAR3 = 0x1c,
|
||||||
|
|
||||||
|
SecondaryBus = 0x19,
|
||||||
|
|
||||||
|
InterruptLine = 0x3c,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CommandField : u16
|
||||||
|
{
|
||||||
|
CMD_IO_SPACE = 1 << 0,
|
||||||
|
CMD_MEMORY_SPACE = 1 << 1,
|
||||||
|
CMD_BUS_MASTER = 1 << 2,
|
||||||
|
CMD_SPECIAL_CYCLES = 1 << 3,
|
||||||
|
CMD_MEMORY_WRITE_AND_INVALIDATE = 1 << 4,
|
||||||
|
CMD_VGA_PALETTE_SNOOP = 1 << 5,
|
||||||
|
CMD_PARITY_ERROR_RESPONSE = 1 << 6,
|
||||||
|
CMD_SERR = 1 << 8,
|
||||||
|
CMD_FAST_BACK_TO_BACK = 1 << 9,
|
||||||
|
CMD_INTERRUPT_DISABLE = 1 << 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BAR
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BAR(u32 raw);
|
||||||
|
|
||||||
|
bool is_iospace()
|
||||||
|
{
|
||||||
|
return m_raw & 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_memory_space()
|
||||||
|
{
|
||||||
|
return !is_iospace();
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 port()
|
||||||
|
{
|
||||||
|
return (u16)(m_raw & 0xfffffffc);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 type()
|
||||||
|
{
|
||||||
|
return (m_raw >> 1) & 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_prefetchable()
|
||||||
|
{
|
||||||
|
return m_raw & (1 << 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 address_32bit()
|
||||||
|
{
|
||||||
|
return m_raw & 0xFFFFFFF0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u32 m_raw;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Device
|
struct Device
|
||||||
@ -42,6 +102,8 @@ namespace PCI
|
|||||||
u32 function;
|
u32 function;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BAR getBAR(u8 index) const;
|
||||||
|
|
||||||
ID id;
|
ID id;
|
||||||
Type type;
|
Type type;
|
||||||
Address address;
|
Address address;
|
||||||
|
@ -188,4 +188,18 @@ ISR_ERROR 21 ; control-protection exception (#CP)
|
|||||||
; ISR 22-31 reserved
|
; ISR 22-31 reserved
|
||||||
IRQ 32, 0 ; timer interrupt
|
IRQ 32, 0 ; timer interrupt
|
||||||
IRQ 33, 1 ; keyboard interrupt
|
IRQ 33, 1 ; keyboard interrupt
|
||||||
|
IRQ 34, 2
|
||||||
|
IRQ 35, 3
|
||||||
|
IRQ 36, 4
|
||||||
|
IRQ 37, 5
|
||||||
|
IRQ 38, 6
|
||||||
|
IRQ 39, 7
|
||||||
|
IRQ 40, 8
|
||||||
|
IRQ 41, 9
|
||||||
|
IRQ 42, 10
|
||||||
|
IRQ 43, 11
|
||||||
|
IRQ 44, 12
|
||||||
|
IRQ 45, 13
|
||||||
|
IRQ 46, 14
|
||||||
|
IRQ 47, 15
|
||||||
ISR 66 ; system call
|
ISR 66 ; system call
|
||||||
|
@ -22,12 +22,23 @@ extern "C" void enable_nx();
|
|||||||
|
|
||||||
extern void setup_gdt();
|
extern void setup_gdt();
|
||||||
extern void remap_pic();
|
extern void remap_pic();
|
||||||
|
extern void change_pic_masks(u8 pic1_mask, u8 pic2_mask);
|
||||||
extern void pic_eoi(unsigned char irq);
|
extern void pic_eoi(unsigned char irq);
|
||||||
extern void pic_eoi(Registers* regs);
|
extern void pic_eoi(Registers* regs);
|
||||||
extern void setup_idt();
|
extern void setup_idt();
|
||||||
|
|
||||||
static Thread* g_io_thread;
|
static Thread* g_io_thread;
|
||||||
|
|
||||||
|
typedef void (*interrupt_handler_t)(Registers*, void*);
|
||||||
|
|
||||||
|
struct InterruptHandler
|
||||||
|
{
|
||||||
|
interrupt_handler_t function;
|
||||||
|
void* context;
|
||||||
|
};
|
||||||
|
|
||||||
|
static InterruptHandler irq_handlers[16];
|
||||||
|
|
||||||
void FPData::save()
|
void FPData::save()
|
||||||
{
|
{
|
||||||
asm volatile("fxsave (%0)" : : "r"(m_data));
|
asm volatile("fxsave (%0)" : : "r"(m_data));
|
||||||
@ -150,21 +161,34 @@ void io_thread()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void timer_interrupt(Registers* regs, void*)
|
||||||
|
{
|
||||||
|
Timer::tick();
|
||||||
|
if (should_invoke_scheduler()) Scheduler::invoke(regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void keyboard_interrupt(Registers*, void*)
|
||||||
|
{
|
||||||
|
u8 scancode = IO::inb(0x60);
|
||||||
|
scancode_queue.try_push(scancode);
|
||||||
|
g_io_thread->wake_up();
|
||||||
|
}
|
||||||
|
|
||||||
// Called from _asm_interrupt_entry
|
// Called from _asm_interrupt_entry
|
||||||
extern "C" void arch_interrupt_entry(Registers* regs)
|
extern "C" void arch_interrupt_entry(Registers* regs)
|
||||||
{
|
{
|
||||||
if (regs->isr < 32) handle_x86_exception(regs);
|
if (regs->isr < 32) handle_x86_exception(regs);
|
||||||
else if (regs->isr == 32) // Timer interrupt
|
else if (regs->isr >= 32 && regs->isr < 48) // IRQ from the PIC
|
||||||
{
|
{
|
||||||
Timer::tick();
|
u64 irq = regs->irq;
|
||||||
if (should_invoke_scheduler()) Scheduler::invoke(regs);
|
auto handler = irq_handlers[irq];
|
||||||
pic_eoi(regs);
|
if (!handler.function)
|
||||||
}
|
{
|
||||||
else if (regs->isr == 33) // Keyboard interrupt
|
kwarnln("Unhandled IRQ catched! Halting.");
|
||||||
{
|
CPU::efficient_halt();
|
||||||
u8 scancode = IO::inb(0x60);
|
}
|
||||||
scancode_queue.try_push(scancode);
|
|
||||||
g_io_thread->wake_up();
|
handler.function(regs, handler.context);
|
||||||
pic_eoi(regs);
|
pic_eoi(regs);
|
||||||
}
|
}
|
||||||
else if (regs->isr == 66) // System call
|
else if (regs->isr == 66) // System call
|
||||||
@ -174,7 +198,7 @@ extern "C" void arch_interrupt_entry(Registers* regs)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
kwarnln("IRQ catched! Halting.");
|
kwarnln("Unhandled interrupt catched! Halting.");
|
||||||
CPU::efficient_halt();
|
CPU::efficient_halt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,6 +257,10 @@ namespace CPU
|
|||||||
kwarnln("not setting the NX bit as it is unsupported");
|
kwarnln("not setting the NX bit as it is unsupported");
|
||||||
setup_gdt();
|
setup_gdt();
|
||||||
setup_idt();
|
setup_idt();
|
||||||
|
|
||||||
|
memset(irq_handlers, 0, sizeof(irq_handlers));
|
||||||
|
register_interrupt(0, timer_interrupt, nullptr);
|
||||||
|
register_interrupt(1, keyboard_interrupt, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void platform_finish_init()
|
void platform_finish_init()
|
||||||
@ -241,6 +269,8 @@ namespace CPU
|
|||||||
.expect_value("Could not create the IO background thread!");
|
.expect_value("Could not create the IO background thread!");
|
||||||
|
|
||||||
remap_pic();
|
remap_pic();
|
||||||
|
|
||||||
|
sync_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void enable_interrupts()
|
void enable_interrupts()
|
||||||
@ -253,6 +283,20 @@ namespace CPU
|
|||||||
asm volatile("cli");
|
asm volatile("cli");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool save_interrupts()
|
||||||
|
{
|
||||||
|
u64 flags;
|
||||||
|
asm volatile("pushfq; pop %0" : "=r"(flags));
|
||||||
|
return flags & 0x200;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore_interrupts(bool saved)
|
||||||
|
{
|
||||||
|
if (saved) enable_interrupts();
|
||||||
|
else
|
||||||
|
disable_interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
void wait_for_interrupt()
|
void wait_for_interrupt()
|
||||||
{
|
{
|
||||||
asm volatile("hlt");
|
asm volatile("hlt");
|
||||||
@ -353,6 +397,35 @@ namespace CPU
|
|||||||
__get_cpuid(1, &unused, &ebx, &unused, &unused);
|
__get_cpuid(1, &unused, &ebx, &unused, &unused);
|
||||||
return (u16)(ebx >> 24);
|
return (u16)(ebx >> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool register_interrupt(u8 interrupt, interrupt_handler_t handler, void* context)
|
||||||
|
{
|
||||||
|
if (irq_handlers[interrupt].function) return false;
|
||||||
|
|
||||||
|
irq_handlers[interrupt] = { handler, context };
|
||||||
|
|
||||||
|
sync_interrupts();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync_interrupts()
|
||||||
|
{
|
||||||
|
u8 pic1_mask, pic2_mask;
|
||||||
|
pic1_mask = pic2_mask = 0b11111111;
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (irq_handlers[i].function) pic1_mask &= (u8)(~(1 << i));
|
||||||
|
if (irq_handlers[i + 8].function) pic2_mask &= (u8)(~(1 << i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pic2_mask != 0b11111111) pic1_mask &= 0b11111011;
|
||||||
|
|
||||||
|
auto val = CPU::save_interrupts();
|
||||||
|
CPU::disable_interrupts();
|
||||||
|
change_pic_masks(pic1_mask, pic2_mask);
|
||||||
|
CPU::restore_interrupts(val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// called by kernel_yield
|
// called by kernel_yield
|
||||||
|
@ -5,7 +5,11 @@ struct Registers // Saved CPU registers for x86-64
|
|||||||
{
|
{
|
||||||
u64 r15, r14, r13, r12, r11, r10, r9, r8;
|
u64 r15, r14, r13, r12, r11, r10, r9, r8;
|
||||||
u64 rbp, rdi, rsi, rdx, rcx, rbx, rax;
|
u64 rbp, rdi, rsi, rdx, rcx, rbx, rax;
|
||||||
u64 isr, error;
|
u64 isr;
|
||||||
|
union {
|
||||||
|
u64 error;
|
||||||
|
u64 irq;
|
||||||
|
};
|
||||||
u64 rip, cs, rflags, rsp, ss;
|
u64 rip, cs, rflags, rsp, ss;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,19 +33,32 @@ namespace PCI
|
|||||||
|
|
||||||
void write8(const Device::Address& address, u32 field, u8 value)
|
void write8(const Device::Address& address, u32 field, u8 value)
|
||||||
{
|
{
|
||||||
ignore(address, field, value);
|
u8 offset = (u8)(field & ~0x3);
|
||||||
todo();
|
union {
|
||||||
|
u8 split[4];
|
||||||
|
u32 full;
|
||||||
|
};
|
||||||
|
full = read32(address, offset);
|
||||||
|
split[(field & 0x3)] = value;
|
||||||
|
write32(address, offset, full);
|
||||||
}
|
}
|
||||||
|
|
||||||
void write16(const Device::Address& address, u32 field, u8 value)
|
void write16(const Device::Address& address, u32 field, u16 value)
|
||||||
{
|
{
|
||||||
ignore(address, field, value);
|
u8 offset = (u8)(field & ~0x3);
|
||||||
todo();
|
union {
|
||||||
|
u8 split[4];
|
||||||
|
u32 full;
|
||||||
|
};
|
||||||
|
full = read32(address, offset);
|
||||||
|
split[(field & 0x3)] = (u8)(value >> 8);
|
||||||
|
split[(field & 0x3) + 1] = (u8)(value & 0xff);
|
||||||
|
write32(address, offset, full);
|
||||||
}
|
}
|
||||||
|
|
||||||
void write32(const Device::Address& address, u32 field, u8 value)
|
void write32(const Device::Address& address, u32 field, u32 value)
|
||||||
{
|
{
|
||||||
ignore(address, field, value);
|
IO::outl(PCI_ADDRESS_PORT, make_pci_address(address, field));
|
||||||
todo();
|
IO::outl(PCI_VALUE_PORT, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
787
kernel/src/arch/x86_64/disk/ATA.cpp
Normal file
787
kernel/src/arch/x86_64/disk/ATA.cpp
Normal file
@ -0,0 +1,787 @@
|
|||||||
|
#include "arch/x86_64/disk/ATA.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "arch/Serial.h"
|
||||||
|
#include "arch/Timer.h"
|
||||||
|
#include "arch/x86_64/IO.h"
|
||||||
|
#include "fs/MBR.h"
|
||||||
|
#include "memory/MemoryManager.h"
|
||||||
|
#include <luna/Alignment.h>
|
||||||
|
#include <luna/CType.h>
|
||||||
|
#include <luna/SafeArithmetic.h>
|
||||||
|
#include <luna/Vector.h>
|
||||||
|
|
||||||
|
SharedPtr<ATA::Controller> g_controller;
|
||||||
|
|
||||||
|
static void irq_handler(Registers* regs, void* ctx)
|
||||||
|
{
|
||||||
|
((ATA::Controller*)ctx)->irq_handler(regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static usize copy_ata_string(char* out, u16* in, usize size)
|
||||||
|
{
|
||||||
|
for (usize i = 0; i < size; i += 2)
|
||||||
|
{
|
||||||
|
u16 val = in[i / 2];
|
||||||
|
out[i] = (u8)(val >> 8);
|
||||||
|
out[i + 1] = (u8)(val & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
out[size + 1] = '\0';
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ATA
|
||||||
|
{
|
||||||
|
Result<void> Controller::scan()
|
||||||
|
{
|
||||||
|
// FIXME: Propagate errors.
|
||||||
|
PCI::scan(
|
||||||
|
[](const PCI::Device& device) {
|
||||||
|
if (!g_controller)
|
||||||
|
{
|
||||||
|
auto controller = adopt_shared_if_nonnull(new (std::nothrow) Controller(device)).release_value();
|
||||||
|
kinfoln("ata: Found ATA controller on PCI bus (%x:%x:%x)", device.address.bus,
|
||||||
|
device.address.function, device.address.slot);
|
||||||
|
|
||||||
|
if (controller->initialize()) g_controller = controller;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ .klass = 1, .subclass = 1 });
|
||||||
|
|
||||||
|
if (!g_controller) kwarnln("ata: No ATA controller found.");
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Controller::initialize()
|
||||||
|
{
|
||||||
|
u16 command_old = PCI::read16(m_device.address, PCI::Command);
|
||||||
|
u16 command_new = command_old;
|
||||||
|
|
||||||
|
command_new &= ~PCI::CMD_INTERRUPT_DISABLE;
|
||||||
|
command_new |= PCI::CMD_IO_SPACE;
|
||||||
|
command_new |= PCI::CMD_BUS_MASTER;
|
||||||
|
|
||||||
|
if (command_new != command_old) PCI::write16(m_device.address, PCI::Command, command_new);
|
||||||
|
|
||||||
|
if (!m_primary_channel.initialize()) return false;
|
||||||
|
|
||||||
|
return m_secondary_channel.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::irq_handler(Registers* regs)
|
||||||
|
{
|
||||||
|
if (regs->irq == m_primary_channel.irq_line()) m_primary_channel.irq_handler(regs);
|
||||||
|
if (regs->irq == m_secondary_channel.irq_line()) m_secondary_channel.irq_handler(regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller::Controller(const PCI::Device& device)
|
||||||
|
: m_device(device), m_primary_channel(this, 0, {}), m_secondary_channel(this, 1, {})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel::Channel(Controller* controller, u8 channel_index, Badge<Controller>)
|
||||||
|
: m_controller(controller), m_channel_index(channel_index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 Channel::read_register(Register reg)
|
||||||
|
{
|
||||||
|
return IO::inb(m_io_base + (u16)reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 Channel::read_data()
|
||||||
|
{
|
||||||
|
return IO::inw(m_io_base + (u16)Register::Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::write_data(u16 value)
|
||||||
|
{
|
||||||
|
IO::outw(m_io_base + (u16)Register::Data, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::write_register(Register reg, u8 value)
|
||||||
|
{
|
||||||
|
IO::outb(m_io_base + (u16)reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 Channel::read_control(ControlRegister reg)
|
||||||
|
{
|
||||||
|
return IO::inb(m_control_base + (u16)reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::write_control(ControlRegister reg, u8 value)
|
||||||
|
{
|
||||||
|
IO::outb(m_control_base + (u16)reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 Channel::read_bm(BusmasterRegister reg)
|
||||||
|
{
|
||||||
|
return IO::inb(m_busmaster_base + (u16)reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::write_bm(BusmasterRegister reg, u8 value)
|
||||||
|
{
|
||||||
|
IO::outb(m_busmaster_base + (u16)reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 Channel::read_prdt_address()
|
||||||
|
{
|
||||||
|
return IO::inl(m_busmaster_base + (u16)BusmasterRegister::PRDTAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::write_prdt_address(u32 value)
|
||||||
|
{
|
||||||
|
IO::outl(m_busmaster_base + (u16)BusmasterRegister::PRDTAddress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::delay_400ns()
|
||||||
|
{
|
||||||
|
// FIXME: We should use kernel_sleep(), but it doesn't support nanosecond granularity.
|
||||||
|
for (int i = 0; i < 14; i++) { [[maybe_unused]] volatile u8 val = read_control(ControlRegister::AltStatus); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::select(u8 drive)
|
||||||
|
{
|
||||||
|
if (drive == m_current_drive) return;
|
||||||
|
|
||||||
|
u8 value = (drive << 4) | 0xa0;
|
||||||
|
write_register(Register::DriveSelect, value);
|
||||||
|
|
||||||
|
delay_400ns();
|
||||||
|
|
||||||
|
m_current_drive = drive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::irq_handler(Registers*)
|
||||||
|
{
|
||||||
|
if (!(read_bm(BusmasterRegister::Status) & BMS_IRQPending)) return;
|
||||||
|
|
||||||
|
if (m_current_drive < 2 && m_drives[m_current_drive]) m_drives[m_current_drive]->irq_handler();
|
||||||
|
|
||||||
|
m_irq_called = true;
|
||||||
|
|
||||||
|
if (m_thread) m_thread->wake_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::prepare_for_irq()
|
||||||
|
{
|
||||||
|
m_thread = Scheduler::current();
|
||||||
|
m_irq_called = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::wait_for_irq()
|
||||||
|
{
|
||||||
|
if (!m_irq_called) kernel_wait_for_event();
|
||||||
|
|
||||||
|
m_irq_called = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Channel::wait_for_irq_or_timeout(u64 timeout)
|
||||||
|
{
|
||||||
|
if (!m_irq_called)
|
||||||
|
{
|
||||||
|
kernel_sleep(timeout);
|
||||||
|
m_irq_called = false;
|
||||||
|
return m_thread->sleep_ticks_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_irq_called = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout)
|
||||||
|
{
|
||||||
|
u64 begin = Timer::ticks_ms();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
u8 reg_value = reg == Register::Status ? read_control(ControlRegister::AltStatus) : read_register(reg);
|
||||||
|
if (reg_value & value) return true;
|
||||||
|
if ((Timer::ticks_ms() - begin) >= timeout) return false;
|
||||||
|
kernel_sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Channel::wait_for_reg_clear(Register reg, u8 value, u64 timeout)
|
||||||
|
{
|
||||||
|
u64 begin = Timer::ticks_ms();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
u8 reg_value = reg == Register::Status ? read_control(ControlRegister::AltStatus) : read_register(reg);
|
||||||
|
if ((reg_value & value) == 0) return true;
|
||||||
|
if ((Timer::ticks_ms() - begin) >= timeout) return false;
|
||||||
|
kernel_sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Channel::wait_until_ready()
|
||||||
|
{
|
||||||
|
if (!wait_for_reg_clear(Register::Status, SR_Busy, 1000))
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d:%d timed out (BSY)", m_channel_index, m_current_drive);
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wait_for_reg_set(Register::Status, SR_DataRequestReady | SR_Error, 1000))
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d:%d timed out (DRQ)", m_channel_index, m_current_drive);
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 status = read_control(ControlRegister::AltStatus);
|
||||||
|
if (status & SR_Error)
|
||||||
|
{
|
||||||
|
kwarnln("ata: An error occurred in drive %d:%d while waiting for data to become available", m_channel_index,
|
||||||
|
m_current_drive);
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Channel::initialize()
|
||||||
|
{
|
||||||
|
int offset = m_channel_index ? 2 : 0;
|
||||||
|
m_is_pci_native_mode = m_controller->device().type.prog_if & (1 << offset);
|
||||||
|
|
||||||
|
u16 control_port_base_address;
|
||||||
|
u16 io_base_address;
|
||||||
|
|
||||||
|
if (m_is_pci_native_mode)
|
||||||
|
{
|
||||||
|
auto io_base = m_controller->device().getBAR(m_channel_index ? 2 : 0);
|
||||||
|
if (!io_base.is_iospace())
|
||||||
|
{
|
||||||
|
kwarnln("ata: Channel %d's IO base BAR is not in IO space", m_channel_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
io_base_address = io_base.port();
|
||||||
|
|
||||||
|
auto io_control = m_controller->device().getBAR(m_channel_index ? 3 : 1);
|
||||||
|
if (!io_control.is_iospace())
|
||||||
|
{
|
||||||
|
kwarnln("ata: Channel %d's control base BAR is not in IO space", m_channel_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
control_port_base_address = io_control.port() + 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io_base_address = m_channel_index ? 0x170 : 0x1f0;
|
||||||
|
control_port_base_address = m_channel_index ? 0x376 : 0x3f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_io_base = io_base_address;
|
||||||
|
m_control_base = control_port_base_address;
|
||||||
|
|
||||||
|
auto io_busmaster = m_controller->device().getBAR(4);
|
||||||
|
if (!io_busmaster.is_iospace())
|
||||||
|
{
|
||||||
|
kwarnln("ata: Channel %d's busmaster base BAR is not in IO space", m_channel_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_busmaster_base = io_busmaster.port() + (u16)(m_channel_index * 8u);
|
||||||
|
|
||||||
|
if (m_is_pci_native_mode) m_interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine);
|
||||||
|
else
|
||||||
|
m_interrupt_line = m_channel_index ? 15 : 14;
|
||||||
|
|
||||||
|
write_control(ControlRegister::DeviceControl, 0);
|
||||||
|
|
||||||
|
for (u8 drive = 0; drive < 2; drive++)
|
||||||
|
{
|
||||||
|
ScopedKMutexLock<100> lock(m_lock);
|
||||||
|
|
||||||
|
select(drive);
|
||||||
|
|
||||||
|
if (read_register(Register::Status) == 0)
|
||||||
|
{
|
||||||
|
// No drive on this slot.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kinfoln("ata: Channel %d has a drive on slot %d!", m_channel_index, drive);
|
||||||
|
|
||||||
|
auto rc = adopt_shared_if_nonnull(new (std::nothrow) Drive(this, drive, {}));
|
||||||
|
if (rc.has_error())
|
||||||
|
{
|
||||||
|
kinfoln("ata: Failed to create drive object: %s", rc.error_string());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_drives[drive] = rc.release_value();
|
||||||
|
|
||||||
|
if (!m_drives[drive]->initialize())
|
||||||
|
{
|
||||||
|
m_drives[drive] = {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CPU::register_interrupt(m_interrupt_line, ::irq_handler, m_controller);
|
||||||
|
|
||||||
|
for (u8 drive = 0; drive < 2; drive++)
|
||||||
|
{
|
||||||
|
if (m_drives[drive])
|
||||||
|
{
|
||||||
|
if (!m_drives[drive]->post_initialize())
|
||||||
|
{
|
||||||
|
m_drives[drive] = {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rc = ATADevice::create(m_drives[drive]);
|
||||||
|
|
||||||
|
if (rc.has_error())
|
||||||
|
{
|
||||||
|
kwarnln("ata: Failed to register ATA drive %d:%d in DeviceRegistry", m_channel_index, drive);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto device = rc.release_value();
|
||||||
|
MBR::identify(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Drive::Drive(Channel* channel, u8 drive_index, Badge<Channel>) : m_channel(channel), m_drive_index(drive_index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Drive::identify_ata()
|
||||||
|
{
|
||||||
|
m_channel->write_register(Register::Command, m_is_atapi ? CMD_Identify_Packet : CMD_Identify);
|
||||||
|
|
||||||
|
m_channel->delay_400ns();
|
||||||
|
|
||||||
|
if (!m_channel->wait_for_reg_clear(Register::Status, SR_Busy, 1000))
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d timed out clearing SR_Busy (waited for 1000 ms)", m_drive_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_channel->read_register(Register::Status) & SR_Error)
|
||||||
|
{
|
||||||
|
u8 lbam = m_channel->read_register(Register::LBAMiddle);
|
||||||
|
u8 lbah = m_channel->read_register(Register::LBAHigh);
|
||||||
|
|
||||||
|
if ((lbam == 0x14 && lbah == 0xeb) || (lbam == 0x69 && lbah == 0x96))
|
||||||
|
{
|
||||||
|
if (!m_is_atapi)
|
||||||
|
{
|
||||||
|
kinfoln("ata: Drive %d is ATAPI, sending IDENTIFY_PACKET command", m_drive_index);
|
||||||
|
m_is_atapi = true;
|
||||||
|
return identify_ata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kwarnln("ata: IDENTIFY command for drive %d returned error", m_drive_index);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_channel->wait_for_reg_set(Register::Status, SR_DataRequestReady | SR_Error, 1000))
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d timed out setting SR_DataRequestReady (waited for 1000 ms)", m_drive_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 status = m_channel->read_register(Register::Status);
|
||||||
|
if (status & SR_Error)
|
||||||
|
{
|
||||||
|
kwarnln("ata: IDENTIFY command for drive %d returned error", m_drive_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (usize i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
u16 data = m_channel->read_data();
|
||||||
|
m_identify_words[i] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Drive::initialize()
|
||||||
|
{
|
||||||
|
m_channel->select(m_drive_index);
|
||||||
|
|
||||||
|
m_channel->write_register(Register::SectorCount, 0);
|
||||||
|
m_channel->write_register(Register::LBALow, 0);
|
||||||
|
m_channel->write_register(Register::LBAMiddle, 0);
|
||||||
|
m_channel->write_register(Register::LBAHigh, 0);
|
||||||
|
|
||||||
|
if (!identify_ata()) return false;
|
||||||
|
|
||||||
|
m_serial.set_length(copy_ata_string(m_serial.data(), &m_identify_words[10], SERIAL_LEN));
|
||||||
|
m_revision.set_length(copy_ata_string(m_revision.data(), &m_identify_words[23], REVISION_LEN));
|
||||||
|
m_model.set_length(copy_ata_string(m_model.data(), &m_identify_words[27], MODEL_LEN));
|
||||||
|
|
||||||
|
m_serial.trim(" ");
|
||||||
|
m_revision.trim(" ");
|
||||||
|
m_model.trim(" ");
|
||||||
|
|
||||||
|
kinfoln("ata: Drive IDENTIFY returned serial='%s', revision='%s' and model='%s'", m_serial.chars(),
|
||||||
|
m_revision.chars(), m_model.chars());
|
||||||
|
|
||||||
|
auto status = m_channel->read_bm(BusmasterRegister::Status);
|
||||||
|
if (status & BMS_SimplexOnly)
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d will not use DMA because of simplex shenanigans", m_drive_index);
|
||||||
|
m_uses_dma = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto frame = MemoryManager::alloc_frame();
|
||||||
|
if (frame.has_error() || frame.value() > 0xffffffff)
|
||||||
|
{
|
||||||
|
kwarnln("ata: Failed to allocate memory below the 32-bit limit for the PRDT");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_dma_prdt_phys = frame.release_value();
|
||||||
|
m_dma_prdt = (prdt_entry*)MMU::translate_physical_address(m_dma_prdt_phys);
|
||||||
|
|
||||||
|
memset(m_dma_prdt, 0, ARCH_PAGE_SIZE);
|
||||||
|
|
||||||
|
frame = MemoryManager::alloc_frame();
|
||||||
|
if (frame.has_error() || frame.value() > 0xffffffff)
|
||||||
|
{
|
||||||
|
kwarnln("ata: Failed to allocate memory below the 32-bit limit for DMA memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_dma_mem_phys = frame.release_value();
|
||||||
|
m_dma_mem = (void*)MMU::translate_physical_address(m_dma_mem_phys);
|
||||||
|
|
||||||
|
memset(const_cast<void*>(m_dma_mem), 0, ARCH_PAGE_SIZE);
|
||||||
|
|
||||||
|
if (m_uses_dma)
|
||||||
|
{
|
||||||
|
auto cmd = m_channel->read_bm(BusmasterRegister::Command);
|
||||||
|
cmd &= ~BMC_StartStop;
|
||||||
|
m_channel->write_bm(BusmasterRegister::Command, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Drive::post_initialize()
|
||||||
|
{
|
||||||
|
if (m_is_atapi)
|
||||||
|
{
|
||||||
|
atapi_packet packet;
|
||||||
|
memset(&packet, 0, sizeof(packet));
|
||||||
|
packet.command_bytes[0] = ATAPI_ReadCapacity;
|
||||||
|
|
||||||
|
atapi_read_capacity_reply reply;
|
||||||
|
|
||||||
|
if (send_packet_atapi_pio(&packet, &reply, sizeof(reply)).has_error())
|
||||||
|
{
|
||||||
|
kwarnln("ata: Failed to send Read Capacity command to ATAPI drive");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_is_lba48 = true;
|
||||||
|
|
||||||
|
// FIXME: This assumes the host machine is little-endian.
|
||||||
|
u32 last_lba = __builtin_bswap32(reply.last_lba);
|
||||||
|
u32 sector_size = __builtin_bswap32(reply.sector_size);
|
||||||
|
|
||||||
|
m_block_count = last_lba + 1;
|
||||||
|
m_block_size = sector_size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_identify_data.big_lba) m_is_lba48 = true;
|
||||||
|
|
||||||
|
if (m_is_lba48) m_block_count = m_identify_data.sectors_48;
|
||||||
|
else
|
||||||
|
m_block_count = m_identify_data.sectors_28;
|
||||||
|
|
||||||
|
// FIXME: Should we check for CHS?
|
||||||
|
|
||||||
|
// FIXME: Maybe a different block size is in use? Detect that.
|
||||||
|
m_block_size = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 total_capacity;
|
||||||
|
if (!safe_mul(m_block_count, m_block_size).try_set_value(total_capacity))
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d's total capacity is too large", m_drive_index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
kinfoln("ata: Drive %d capacity information: Block Count=%lu, Block Size=%lu, Total Capacity=%lu",
|
||||||
|
m_drive_index, m_block_count, m_block_size, total_capacity);
|
||||||
|
|
||||||
|
char buf[2048];
|
||||||
|
if (atapi_read_pio(0, buf, sizeof(buf)).has_error()) return false;
|
||||||
|
|
||||||
|
char readable[2049];
|
||||||
|
for (int i = 0; i < 2048; i++) { readable[i] = _iscntrl(buf[i]) ? '.' : buf[i]; }
|
||||||
|
readable[2048] = 0;
|
||||||
|
kinfoln("ata: Read first sector from CD-ROM: %s", readable);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size)
|
||||||
|
{
|
||||||
|
u8* ptr = (u8*)out;
|
||||||
|
|
||||||
|
m_channel->select(m_drive_index);
|
||||||
|
|
||||||
|
// We use PIO here.
|
||||||
|
m_channel->write_register(Register::Features, 0x00);
|
||||||
|
|
||||||
|
m_channel->write_register(Register::LBAMiddle, (u8)(response_size & 0xff));
|
||||||
|
m_channel->write_register(Register::LBAHigh, (u8)(response_size >> 8));
|
||||||
|
|
||||||
|
m_channel->write_register(Register::Command, CMD_Packet);
|
||||||
|
|
||||||
|
m_channel->delay_400ns();
|
||||||
|
|
||||||
|
usize i = 0;
|
||||||
|
|
||||||
|
TRY(m_channel->wait_until_ready());
|
||||||
|
|
||||||
|
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
|
||||||
|
|
||||||
|
while (i < response_size)
|
||||||
|
{
|
||||||
|
TRY(m_channel->wait_until_ready());
|
||||||
|
|
||||||
|
usize byte_count =
|
||||||
|
m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle);
|
||||||
|
usize word_count = byte_count / 2;
|
||||||
|
|
||||||
|
while (word_count--)
|
||||||
|
{
|
||||||
|
u16 value = m_channel->read_data();
|
||||||
|
ptr[0] = (u8)(value & 0xff);
|
||||||
|
ptr[1] = (u8)(value >> 8);
|
||||||
|
ptr += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += byte_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
Result<void> Drive::send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size)
|
||||||
|
{
|
||||||
|
check(m_uses_dma);
|
||||||
|
|
||||||
|
m_channel->select(m_drive_index);
|
||||||
|
|
||||||
|
kdbgln("have selected");
|
||||||
|
|
||||||
|
// We use DMA here.
|
||||||
|
m_channel->write_register(Register::Features, 0x01);
|
||||||
|
|
||||||
|
m_channel->write_register(Register::LBAMiddle, 0);
|
||||||
|
m_channel->write_register(Register::LBAHigh, 0);
|
||||||
|
|
||||||
|
kdbgln("will do_dma_command");
|
||||||
|
|
||||||
|
TRY(do_dma_command(CMD_Packet, response_size, false));
|
||||||
|
|
||||||
|
TRY(m_channel->wait_until_ready());
|
||||||
|
|
||||||
|
kdbgln("send atapi packet data");
|
||||||
|
|
||||||
|
for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]);
|
||||||
|
|
||||||
|
kdbgln("do dma transfer");
|
||||||
|
|
||||||
|
TRY(do_dma_transfer());
|
||||||
|
|
||||||
|
memcpy(out, const_cast<void*>(m_dma_mem), response_size);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Drive::do_dma_command(u8 command, u16 count, bool write)
|
||||||
|
{
|
||||||
|
m_dma_prdt->address = (u32)m_dma_mem_phys;
|
||||||
|
m_dma_prdt->count = count;
|
||||||
|
m_dma_prdt->flags = END_OF_PRDT;
|
||||||
|
|
||||||
|
kdbgln("ata: do_dma_command: phys=%x, command=%x, count=%u, write=%d", m_dma_prdt->address, command, count,
|
||||||
|
write);
|
||||||
|
|
||||||
|
m_channel->write_prdt_address((u32)m_dma_prdt_phys);
|
||||||
|
|
||||||
|
auto status = m_channel->read_bm(BusmasterRegister::Status);
|
||||||
|
status &= ~(BMS_DMAFailure | BMS_IRQPending);
|
||||||
|
m_channel->write_bm(BusmasterRegister::Status, status);
|
||||||
|
|
||||||
|
auto cmd = m_channel->read_bm(BusmasterRegister::Command);
|
||||||
|
if (!write) cmd |= BMC_ReadWrite;
|
||||||
|
else
|
||||||
|
cmd &= ~BMC_ReadWrite;
|
||||||
|
m_channel->write_bm(BusmasterRegister::Command, cmd);
|
||||||
|
|
||||||
|
m_channel->prepare_for_irq();
|
||||||
|
|
||||||
|
m_channel->write_register(Register::Command, command);
|
||||||
|
|
||||||
|
cmd = m_channel->read_bm(BusmasterRegister::Command);
|
||||||
|
cmd |= BMC_StartStop;
|
||||||
|
m_channel->write_bm(BusmasterRegister::Command, cmd);
|
||||||
|
|
||||||
|
m_channel->delay_400ns();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Drive::do_dma_transfer()
|
||||||
|
{
|
||||||
|
if (!m_channel->wait_for_irq_or_timeout(2000))
|
||||||
|
{
|
||||||
|
kwarnln("ata: Drive %d timed out (DMA)", m_drive_index);
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 status = m_channel->read_control(ControlRegister::AltStatus);
|
||||||
|
kdbgln("ata: status after irq: %#x", status);
|
||||||
|
|
||||||
|
m_channel->delay_400ns();
|
||||||
|
|
||||||
|
auto cmd = m_channel->read_bm(BusmasterRegister::Command);
|
||||||
|
cmd &= ~BMC_StartStop;
|
||||||
|
m_channel->write_bm(BusmasterRegister::Command, cmd);
|
||||||
|
|
||||||
|
status = m_channel->read_bm(BusmasterRegister::Status);
|
||||||
|
m_channel->write_bm(BusmasterRegister::Status, status & ~(BMS_DMAFailure | BMS_IRQPending));
|
||||||
|
|
||||||
|
if (status & BMS_DMAFailure)
|
||||||
|
{
|
||||||
|
kwarnln("ata: DMA failure while trying to read drive %d", m_drive_index);
|
||||||
|
return err(EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result<void> Drive::atapi_read_pio(u64 lba, void* out, usize size)
|
||||||
|
{
|
||||||
|
check(lba < m_block_count);
|
||||||
|
check(size <= ARCH_PAGE_SIZE);
|
||||||
|
|
||||||
|
atapi_packet read_packet;
|
||||||
|
memset(&read_packet, 0, sizeof(read_packet));
|
||||||
|
read_packet.command_bytes[0] = ATAPI_Read;
|
||||||
|
read_packet.command_bytes[2] = (lba >> 0x18) & 0xff;
|
||||||
|
read_packet.command_bytes[3] = (lba >> 0x10) & 0xff;
|
||||||
|
read_packet.command_bytes[4] = (lba >> 0x08) & 0xff;
|
||||||
|
read_packet.command_bytes[5] = (lba >> 0x00) & 0xff;
|
||||||
|
read_packet.command_bytes[9] = (u8)(size / m_block_size);
|
||||||
|
|
||||||
|
return send_packet_atapi_pio(&read_packet, out, (u16)size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> Drive::read_lba(u64 lba, void* out, usize nblocks)
|
||||||
|
{
|
||||||
|
const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size;
|
||||||
|
if (m_is_atapi)
|
||||||
|
{
|
||||||
|
kdbgln("ata: Reading %zu blocks from ATAPI drive using PIO, at LBA %ld", nblocks, lba);
|
||||||
|
while (nblocks > blocks_per_page)
|
||||||
|
{
|
||||||
|
TRY(atapi_read_pio(lba, out, ARCH_PAGE_SIZE));
|
||||||
|
lba += blocks_per_page;
|
||||||
|
nblocks -= blocks_per_page;
|
||||||
|
out = offset_ptr(out, ARCH_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
return atapi_read_pio(lba, out, nblocks * m_block_size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
todo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Drive::irq_handler()
|
||||||
|
{
|
||||||
|
// Clear the IRQ flag.
|
||||||
|
u8 status = m_channel->read_register(Register::Status);
|
||||||
|
|
||||||
|
if (status & SR_Error)
|
||||||
|
{
|
||||||
|
u8 error = m_channel->read_register(Register::Error);
|
||||||
|
(void)error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_uses_dma)
|
||||||
|
{
|
||||||
|
status = m_channel->read_bm(BusmasterRegister::Status);
|
||||||
|
if (status & BMS_DMAFailure) { kwarnln("ata: DMA failure in irq"); }
|
||||||
|
m_channel->write_bm(BusmasterRegister::Status, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 next_minor = 0;
|
||||||
|
|
||||||
|
Result<SharedPtr<Device>> ATADevice::create(SharedPtr<ATA::Drive> drive)
|
||||||
|
{
|
||||||
|
auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) ATADevice()));
|
||||||
|
device->m_drive = drive;
|
||||||
|
TRY(DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, "cdrom", 0400));
|
||||||
|
return (SharedPtr<Device>)device;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<u64> ATADevice::read(u8* buf, usize offset, usize size) const
|
||||||
|
{
|
||||||
|
if (size == 0) return 0;
|
||||||
|
|
||||||
|
if (offset > m_drive->capacity()) return 0;
|
||||||
|
if (offset + size > m_drive->capacity()) size = m_drive->capacity() - offset;
|
||||||
|
|
||||||
|
usize length = size;
|
||||||
|
|
||||||
|
auto block_size = m_drive->block_size();
|
||||||
|
|
||||||
|
auto* temp = TRY(make_array<u8>(block_size));
|
||||||
|
auto guard = make_scope_guard([temp] { delete[] temp; });
|
||||||
|
|
||||||
|
if (offset % block_size)
|
||||||
|
{
|
||||||
|
usize extra_size = block_size - (offset % block_size);
|
||||||
|
TRY(m_drive->read_lba(offset / block_size, temp, 1));
|
||||||
|
memcpy(buf, temp + (offset % block_size), extra_size);
|
||||||
|
offset += extra_size;
|
||||||
|
size -= extra_size;
|
||||||
|
buf += extra_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size >= ARCH_PAGE_SIZE)
|
||||||
|
{
|
||||||
|
TRY(m_drive->read_lba(offset / block_size, buf, ARCH_PAGE_SIZE / block_size));
|
||||||
|
offset += ARCH_PAGE_SIZE;
|
||||||
|
size -= ARCH_PAGE_SIZE;
|
||||||
|
buf += ARCH_PAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size >= block_size)
|
||||||
|
{
|
||||||
|
TRY(m_drive->read_lba(offset / block_size, buf, 1));
|
||||||
|
offset += block_size;
|
||||||
|
size -= block_size;
|
||||||
|
buf += block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size)
|
||||||
|
{
|
||||||
|
TRY(m_drive->read_lba(offset / block_size, temp, 1));
|
||||||
|
memcpy(buf, temp, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
321
kernel/src/arch/x86_64/disk/ATA.h
Normal file
321
kernel/src/arch/x86_64/disk/ATA.h
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "arch/PCI.h"
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include "lib/KMutex.h"
|
||||||
|
#include <luna/Atomic.h>
|
||||||
|
#include <luna/SharedPtr.h>
|
||||||
|
#include <luna/StaticString.h>
|
||||||
|
|
||||||
|
namespace ATA
|
||||||
|
{
|
||||||
|
enum class Register : u16
|
||||||
|
{
|
||||||
|
Data = 0,
|
||||||
|
Error = 1,
|
||||||
|
Features = 1,
|
||||||
|
SectorCount = 2,
|
||||||
|
SectorNumber = 3,
|
||||||
|
LBALow = 3,
|
||||||
|
CylinderLow = 4,
|
||||||
|
LBAMiddle = 4,
|
||||||
|
CylinderHigh = 5,
|
||||||
|
LBAHigh = 5,
|
||||||
|
DriveSelect = 6,
|
||||||
|
Status = 7,
|
||||||
|
Command = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ControlRegister : u16
|
||||||
|
{
|
||||||
|
AltStatus = 0,
|
||||||
|
DeviceControl = 0,
|
||||||
|
DriveAddress = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BusmasterRegister : u16
|
||||||
|
{
|
||||||
|
Command = 0,
|
||||||
|
Status = 2,
|
||||||
|
PRDTAddress = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum StatusRegister : u8
|
||||||
|
{
|
||||||
|
SR_Busy = 0x80,
|
||||||
|
SR_DriveReady = 0x40,
|
||||||
|
SR_WriteFault = 0x20,
|
||||||
|
SR_SeekComplete = 0x10,
|
||||||
|
SR_DataRequestReady = 0x08,
|
||||||
|
SR_CorrectedData = 0x04,
|
||||||
|
SR_Index = 0x02,
|
||||||
|
SR_Error = 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CommandRegister : u8
|
||||||
|
{
|
||||||
|
CMD_Identify = 0xec,
|
||||||
|
CMD_Packet = 0xa0,
|
||||||
|
CMD_Identify_Packet = 0xa1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BusMasterStatus : u8
|
||||||
|
{
|
||||||
|
BMS_SimplexOnly = 0x80,
|
||||||
|
BMS_SlaveInit = 0x40,
|
||||||
|
BMS_MasterInit = 0x20,
|
||||||
|
BMS_IRQPending = 0x4,
|
||||||
|
BMS_DMAFailure = 0x2,
|
||||||
|
BMS_DMAMode = 0x1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BusMasterCommand : u8
|
||||||
|
{
|
||||||
|
BMC_ReadWrite = 0x8,
|
||||||
|
BMC_StartStop = 0x1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ATAIdentify
|
||||||
|
{
|
||||||
|
u16 flags;
|
||||||
|
u16 unused1[9];
|
||||||
|
char serial[20];
|
||||||
|
u16 unused2[3];
|
||||||
|
char firmware[8];
|
||||||
|
char model[40];
|
||||||
|
u16 sectors_per_int;
|
||||||
|
u16 unused3;
|
||||||
|
u16 capabilities[2];
|
||||||
|
u16 unused4[2];
|
||||||
|
u16 valid_ext_data;
|
||||||
|
u16 unused5[5];
|
||||||
|
u16 size_of_rw_mult;
|
||||||
|
u32 sectors_28;
|
||||||
|
u16 unused6[21];
|
||||||
|
u16 unused7 : 10;
|
||||||
|
u16 big_lba : 1;
|
||||||
|
u16 unused8 : 5;
|
||||||
|
u16 unused9[17];
|
||||||
|
u64 sectors_48;
|
||||||
|
u16 unused10[152];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ATAPICommand : u8
|
||||||
|
{
|
||||||
|
ATAPI_ReadCapacity = 0x25,
|
||||||
|
ATAPI_Read = 0xa8,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Controller;
|
||||||
|
class Channel;
|
||||||
|
|
||||||
|
struct prdt_entry
|
||||||
|
{
|
||||||
|
u32 address;
|
||||||
|
u16 count;
|
||||||
|
u16 flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct atapi_packet
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
u16 command_words[6];
|
||||||
|
u8 command_bytes[12];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct atapi_read_capacity_reply
|
||||||
|
{
|
||||||
|
u32 last_lba;
|
||||||
|
u32 sector_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr u16 END_OF_PRDT = (1 << 15);
|
||||||
|
|
||||||
|
class Drive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Drive(Channel* channel, u8 drive_index, Badge<Channel>);
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
bool post_initialize();
|
||||||
|
|
||||||
|
void irq_handler();
|
||||||
|
|
||||||
|
usize block_size() const
|
||||||
|
{
|
||||||
|
return m_block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize block_count() const
|
||||||
|
{
|
||||||
|
return m_block_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize capacity() const
|
||||||
|
{
|
||||||
|
return m_block_count * m_block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> read_lba(u64 lba, void* out, usize nblocks);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool identify_ata();
|
||||||
|
|
||||||
|
Result<void> send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size);
|
||||||
|
#if 0
|
||||||
|
Result<void> send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size);
|
||||||
|
|
||||||
|
Result<void> do_dma_command(u8 command, u16 count, bool write);
|
||||||
|
Result<void> do_dma_transfer();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result<void> atapi_read_pio(u64 lba, void* out, usize size);
|
||||||
|
|
||||||
|
Channel* m_channel;
|
||||||
|
|
||||||
|
u8 m_drive_index;
|
||||||
|
union {
|
||||||
|
u16 m_identify_words[256];
|
||||||
|
ATAIdentify m_identify_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool m_is_atapi { false };
|
||||||
|
bool m_uses_dma { true };
|
||||||
|
|
||||||
|
bool m_is_lba48;
|
||||||
|
u64 m_block_count;
|
||||||
|
u64 m_block_size;
|
||||||
|
|
||||||
|
prdt_entry* m_dma_prdt;
|
||||||
|
u64 m_dma_prdt_phys;
|
||||||
|
volatile void* m_dma_mem;
|
||||||
|
u64 m_dma_mem_phys;
|
||||||
|
|
||||||
|
constexpr static usize SERIAL_LEN = 20;
|
||||||
|
constexpr static usize REVISION_LEN = 8;
|
||||||
|
constexpr static usize MODEL_LEN = 40;
|
||||||
|
|
||||||
|
StaticString<SERIAL_LEN> m_serial;
|
||||||
|
StaticString<REVISION_LEN> m_revision;
|
||||||
|
StaticString<MODEL_LEN> m_model;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Channel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Channel(Controller* controller, u8 channel_index, Badge<Controller>);
|
||||||
|
|
||||||
|
u8 read_register(Register reg);
|
||||||
|
u16 read_data();
|
||||||
|
void write_data(u16 value);
|
||||||
|
void write_register(Register reg, u8 value);
|
||||||
|
u8 read_control(ControlRegister reg);
|
||||||
|
void write_control(ControlRegister reg, u8 value);
|
||||||
|
|
||||||
|
u8 read_bm(BusmasterRegister reg);
|
||||||
|
void write_bm(BusmasterRegister reg, u8 value);
|
||||||
|
u32 read_prdt_address();
|
||||||
|
void write_prdt_address(u32 value);
|
||||||
|
|
||||||
|
bool wait_for_reg_set(Register reg, u8 value, u64 timeout);
|
||||||
|
bool wait_for_reg_clear(Register reg, u8 value, u64 timeout);
|
||||||
|
|
||||||
|
Result<void> wait_until_ready();
|
||||||
|
|
||||||
|
void delay_400ns();
|
||||||
|
|
||||||
|
void prepare_for_irq();
|
||||||
|
|
||||||
|
void wait_for_irq();
|
||||||
|
bool wait_for_irq_or_timeout(u64 timeout);
|
||||||
|
void irq_handler(Registers*);
|
||||||
|
|
||||||
|
u8 irq_line()
|
||||||
|
{
|
||||||
|
return m_interrupt_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
void select(u8 drive);
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Controller* m_controller;
|
||||||
|
u8 m_channel_index;
|
||||||
|
bool m_is_pci_native_mode;
|
||||||
|
|
||||||
|
u8 m_interrupt_line;
|
||||||
|
|
||||||
|
KMutex<100> m_lock {};
|
||||||
|
|
||||||
|
Thread* m_thread { nullptr };
|
||||||
|
|
||||||
|
u16 m_io_base;
|
||||||
|
u16 m_control_base;
|
||||||
|
u16 m_busmaster_base;
|
||||||
|
|
||||||
|
bool m_irq_called { false };
|
||||||
|
|
||||||
|
u8 m_current_drive = (u8)-1;
|
||||||
|
|
||||||
|
SharedPtr<Drive> m_drives[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
class Controller
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Result<void> scan();
|
||||||
|
|
||||||
|
const PCI::Device& device() const
|
||||||
|
{
|
||||||
|
return m_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
void irq_handler(Registers*);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Controller(const PCI::Device& device);
|
||||||
|
PCI::Device m_device;
|
||||||
|
Channel m_primary_channel;
|
||||||
|
Channel m_secondary_channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ATADevice : public Device
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Initializer for DeviceRegistry.
|
||||||
|
static Result<void> create(SharedPtr<ATA::Drive> drive);
|
||||||
|
|
||||||
|
Result<usize> read(u8*, usize, usize) const override;
|
||||||
|
|
||||||
|
Result<usize> write(const u8*, usize, usize) override
|
||||||
|
{
|
||||||
|
return err(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool blocking() const override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_block_device() const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize size() const override
|
||||||
|
{
|
||||||
|
return m_drive->capacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ATADevice() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ATADevice() = default;
|
||||||
|
SharedPtr<ATA::Drive> m_drive;
|
||||||
|
};
|
@ -84,6 +84,20 @@ INT(20);
|
|||||||
INT(21);
|
INT(21);
|
||||||
INT(32);
|
INT(32);
|
||||||
INT(33);
|
INT(33);
|
||||||
|
INT(34);
|
||||||
|
INT(35);
|
||||||
|
INT(36);
|
||||||
|
INT(37);
|
||||||
|
INT(38);
|
||||||
|
INT(39);
|
||||||
|
INT(40);
|
||||||
|
INT(41);
|
||||||
|
INT(42);
|
||||||
|
INT(43);
|
||||||
|
INT(44);
|
||||||
|
INT(45);
|
||||||
|
INT(46);
|
||||||
|
INT(47);
|
||||||
INT(66);
|
INT(66);
|
||||||
|
|
||||||
void setup_idt()
|
void setup_idt()
|
||||||
@ -112,6 +126,20 @@ void setup_idt()
|
|||||||
TRAP(21);
|
TRAP(21);
|
||||||
IRQ(32);
|
IRQ(32);
|
||||||
IRQ(33);
|
IRQ(33);
|
||||||
|
IRQ(34);
|
||||||
|
IRQ(35);
|
||||||
|
IRQ(36);
|
||||||
|
IRQ(37);
|
||||||
|
IRQ(38);
|
||||||
|
IRQ(39);
|
||||||
|
IRQ(40);
|
||||||
|
IRQ(41);
|
||||||
|
IRQ(42);
|
||||||
|
IRQ(43);
|
||||||
|
IRQ(44);
|
||||||
|
IRQ(45);
|
||||||
|
IRQ(46);
|
||||||
|
IRQ(47);
|
||||||
SYS(66);
|
SYS(66);
|
||||||
|
|
||||||
static IDTR idtr;
|
static IDTR idtr;
|
||||||
|
@ -36,9 +36,18 @@ void remap_pic()
|
|||||||
IO::outb(PIC2_DATA, ICW4_8086);
|
IO::outb(PIC2_DATA, ICW4_8086);
|
||||||
io_delay();
|
io_delay();
|
||||||
|
|
||||||
IO::outb(PIC1_DATA, 0b11111100);
|
IO::outb(PIC1_DATA, 0b11111111);
|
||||||
io_delay();
|
io_delay();
|
||||||
IO::outb(PIC2_DATA, 0b11111111);
|
IO::outb(PIC2_DATA, 0b11111111);
|
||||||
|
io_delay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void change_pic_masks(u8 pic1_mask, u8 pic2_mask)
|
||||||
|
{
|
||||||
|
IO::outb(PIC1_DATA, pic1_mask);
|
||||||
|
io_delay();
|
||||||
|
IO::outb(PIC2_DATA, pic2_mask);
|
||||||
|
io_delay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pic_eoi(unsigned char irq)
|
void pic_eoi(unsigned char irq)
|
||||||
@ -49,5 +58,5 @@ void pic_eoi(unsigned char irq)
|
|||||||
|
|
||||||
void pic_eoi(Registers* regs)
|
void pic_eoi(Registers* regs)
|
||||||
{
|
{
|
||||||
pic_eoi((unsigned char)(regs->error)); // On IRQs, the error code is the IRQ number
|
pic_eoi((unsigned char)(regs->irq));
|
||||||
}
|
}
|
||||||
|
29
kernel/src/fs/MBR.cpp
Normal file
29
kernel/src/fs/MBR.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "fs/MBR.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
namespace MBR
|
||||||
|
{
|
||||||
|
Result<bool> identify(SharedPtr<Device> device)
|
||||||
|
{
|
||||||
|
// Cannot read a partition table from a character device! Who is even coming up with this silliness?
|
||||||
|
if (!device->is_block_device()) return false;
|
||||||
|
|
||||||
|
DiskHeader hdr;
|
||||||
|
usize nread = TRY(device->read((u8*)&hdr, 0, sizeof(hdr)));
|
||||||
|
check(nread == 512);
|
||||||
|
|
||||||
|
if (hdr.signature[0] != MBR_SIGNATURE_1 || hdr.signature[1] != MBR_SIGNATURE_2) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
const auto& part = hdr.partitions[i];
|
||||||
|
if (part.partition_type == 0) continue; // Not active.
|
||||||
|
|
||||||
|
bool bootable = part.attributes & MBR_BOOTABLE;
|
||||||
|
kinfoln("mbr: Partition #%d is active: bootable=%d, type=%x, start=%d, sectors=%d", i, bootable,
|
||||||
|
part.partition_type, part.start_lba, part.num_sectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
35
kernel/src/fs/MBR.h
Normal file
35
kernel/src/fs/MBR.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fs/devices/DeviceRegistry.h"
|
||||||
|
#include <luna/Types.h>
|
||||||
|
|
||||||
|
#define MBR_BOOTABLE 0x80
|
||||||
|
|
||||||
|
#define MBR_SIGNATURE_1 0x55
|
||||||
|
#define MBR_SIGNATURE_2 0xAA
|
||||||
|
|
||||||
|
namespace MBR
|
||||||
|
{
|
||||||
|
struct [[gnu::packed]] PartitionHeader
|
||||||
|
{
|
||||||
|
u8 attributes;
|
||||||
|
u8 chs_start[3];
|
||||||
|
u8 partition_type;
|
||||||
|
u8 chs_end[3];
|
||||||
|
u32 start_lba;
|
||||||
|
u32 num_sectors;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct [[gnu::packed]] DiskHeader
|
||||||
|
{
|
||||||
|
u8 mbr_code[440];
|
||||||
|
u8 disk_id[4];
|
||||||
|
u8 reserved[2];
|
||||||
|
PartitionHeader partitions[4];
|
||||||
|
u8 signature[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(DiskHeader) == 512ul);
|
||||||
|
|
||||||
|
Result<bool> identify(SharedPtr<Device> device);
|
||||||
|
};
|
@ -13,6 +13,7 @@ namespace DeviceRegistry
|
|||||||
Console = 1,
|
Console = 1,
|
||||||
Memory = 2,
|
Memory = 2,
|
||||||
Framebuffer = 3,
|
Framebuffer = 3,
|
||||||
|
Disk = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);
|
Result<SharedPtr<Device>> fetch_special_device(u32 major, u32 minor);
|
||||||
|
77
kernel/src/lib/KMutex.h
Normal file
77
kernel/src/lib/KMutex.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Log.h"
|
||||||
|
#include "arch/CPU.h"
|
||||||
|
#include "thread/Scheduler.h"
|
||||||
|
#include "thread/Thread.h"
|
||||||
|
#include <luna/CircularQueue.h>
|
||||||
|
|
||||||
|
template <usize ConcurrentThreads> class KMutex
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void lock()
|
||||||
|
{
|
||||||
|
int expected = 0;
|
||||||
|
while (!m_lock.compare_exchange_strong(expected, 1))
|
||||||
|
{
|
||||||
|
expected = 0;
|
||||||
|
auto* current = Scheduler::current();
|
||||||
|
|
||||||
|
// We cannot be interrupted between these functions, otherwise we might never exit the loop
|
||||||
|
CPU::disable_interrupts();
|
||||||
|
bool ok = m_blocked_threads.try_push(current);
|
||||||
|
if (!ok) kernel_sleep(10);
|
||||||
|
else
|
||||||
|
kernel_wait_for_event();
|
||||||
|
CPU::enable_interrupts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void unlock()
|
||||||
|
{
|
||||||
|
int expected = 1;
|
||||||
|
if (!m_lock.compare_exchange_strong(expected, 0))
|
||||||
|
{
|
||||||
|
kwarnln("KMutex::unlock() called on an unlocked lock with value %d", expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread* blocked;
|
||||||
|
if (m_blocked_threads.try_pop(blocked)) blocked->wake_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_lock()
|
||||||
|
{
|
||||||
|
int expected = 0;
|
||||||
|
return m_lock.compare_exchange_strong(expected, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CircularQueue<Thread*, ConcurrentThreads> m_blocked_threads;
|
||||||
|
Atomic<int> m_lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <usize ConcurrentThreads> class ScopedKMutexLock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedKMutexLock(KMutex<ConcurrentThreads>& lock) : m_lock(lock)
|
||||||
|
{
|
||||||
|
m_lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedKMutexLock()
|
||||||
|
{
|
||||||
|
if (!m_taken_over) m_lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedKMutexLock(const ScopedKMutexLock&) = delete;
|
||||||
|
ScopedKMutexLock(ScopedKMutexLock&&) = delete;
|
||||||
|
|
||||||
|
KMutex<ConcurrentThreads>& take_over()
|
||||||
|
{
|
||||||
|
m_taken_over = true;
|
||||||
|
return m_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KMutex<ConcurrentThreads>& m_lock;
|
||||||
|
bool m_taken_over { false };
|
||||||
|
};
|
@ -1,6 +1,5 @@
|
|||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "arch/CPU.h"
|
#include "arch/CPU.h"
|
||||||
#include "arch/PCI.h"
|
|
||||||
#include "arch/Timer.h"
|
#include "arch/Timer.h"
|
||||||
#include "boot/Init.h"
|
#include "boot/Init.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@ -11,6 +10,10 @@
|
|||||||
#include "thread/Scheduler.h"
|
#include "thread/Scheduler.h"
|
||||||
#include <luna/Units.h>
|
#include <luna/Units.h>
|
||||||
|
|
||||||
|
#ifdef ARCH_X86_64
|
||||||
|
#include "arch/x86_64/disk/ATA.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
extern void set_host_name(StringView);
|
extern void set_host_name(StringView);
|
||||||
|
|
||||||
void reap_thread()
|
void reap_thread()
|
||||||
@ -53,12 +56,9 @@ Result<void> init()
|
|||||||
auto reap = Scheduler::new_kernel_thread(reap_thread, "[reap]").release_value();
|
auto reap = Scheduler::new_kernel_thread(reap_thread, "[reap]").release_value();
|
||||||
Scheduler::set_reap_thread(reap);
|
Scheduler::set_reap_thread(reap);
|
||||||
|
|
||||||
PCI::scan(
|
#ifdef ARCH_X86_64
|
||||||
[](const PCI::Device& device) {
|
ATA::Controller::scan();
|
||||||
kinfoln("Found PCI mass storage device %.4x:%.4x, at address %u:%u:%u", device.id.vendor, device.id.device,
|
#endif
|
||||||
device.address.bus, device.address.slot, device.address.function);
|
|
||||||
},
|
|
||||||
{ .klass = 1 });
|
|
||||||
|
|
||||||
// Disable console logging before transferring control to userspace.
|
// Disable console logging before transferring control to userspace.
|
||||||
setup_log(log_debug_enabled(), log_serial_enabled(), false);
|
setup_log(log_debug_enabled(), log_serial_enabled(), false);
|
||||||
|
@ -25,6 +25,7 @@ set(FREESTANDING_SOURCES
|
|||||||
src/PathParser.cpp
|
src/PathParser.cpp
|
||||||
src/UBSAN.cpp
|
src/UBSAN.cpp
|
||||||
src/Base64.cpp
|
src/Base64.cpp
|
||||||
|
src/Hash.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
|
29
libluna/include/luna/Hash.h
Normal file
29
libluna/include/luna/Hash.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <luna/CString.h>
|
||||||
|
#include <luna/Types.h>
|
||||||
|
|
||||||
|
u64 hash_memory(const void* mem, usize size, u64 salt);
|
||||||
|
|
||||||
|
template <typename T> u64 hash(const T& value, u64 salt)
|
||||||
|
{
|
||||||
|
return hash_memory(&value, sizeof(value), salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> u64 hash(const char* const& value, u64 salt);
|
||||||
|
|
||||||
|
template <typename T> static void swap(T* a, T* b)
|
||||||
|
{
|
||||||
|
char* x = (char*)a;
|
||||||
|
char* y = (char*)b;
|
||||||
|
|
||||||
|
usize size = sizeof(T);
|
||||||
|
|
||||||
|
while (size--)
|
||||||
|
{
|
||||||
|
char t = *x;
|
||||||
|
*x = *y;
|
||||||
|
*y = t;
|
||||||
|
x += 1;
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
}
|
146
libluna/include/luna/HashTable.h
Normal file
146
libluna/include/luna/HashTable.h
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <luna/Hash.h>
|
||||||
|
#include <luna/Heap.h>
|
||||||
|
#include <luna/Option.h>
|
||||||
|
|
||||||
|
template <typename T> class HashTable
|
||||||
|
{
|
||||||
|
static constexpr usize GROW_RATE = 2;
|
||||||
|
static constexpr usize GROW_FACTOR = 16;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Result<bool> try_set(const T& value)
|
||||||
|
{
|
||||||
|
T copy { value };
|
||||||
|
return try_set(move(copy));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> try_set(T&& value)
|
||||||
|
{
|
||||||
|
if (should_grow()) TRY(rehash(m_capacity + GROW_FACTOR));
|
||||||
|
|
||||||
|
u64 index = hash(value, m_salt) % m_capacity;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto& bucket = m_buckets[index];
|
||||||
|
if (bucket.has_value())
|
||||||
|
{
|
||||||
|
if (*bucket == value) return false;
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bucket = { move(value) };
|
||||||
|
m_size++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* try_find(const T& value)
|
||||||
|
{
|
||||||
|
if (!m_size) return nullptr;
|
||||||
|
|
||||||
|
check(m_capacity);
|
||||||
|
|
||||||
|
const u64 index = hash(value, m_salt) % m_capacity;
|
||||||
|
usize i = index;
|
||||||
|
|
||||||
|
do {
|
||||||
|
auto& bucket = m_buckets[index];
|
||||||
|
if (bucket.has_value())
|
||||||
|
{
|
||||||
|
if (*bucket == value) return bucket.value_ptr();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
} while (i != index);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_remove(const T& value)
|
||||||
|
{
|
||||||
|
if (!m_size) return false;
|
||||||
|
|
||||||
|
check(m_capacity);
|
||||||
|
|
||||||
|
const u64 index = hash(value, m_salt) % m_capacity;
|
||||||
|
usize i = index;
|
||||||
|
|
||||||
|
do {
|
||||||
|
auto& bucket = m_buckets[index];
|
||||||
|
if (bucket.has_value())
|
||||||
|
{
|
||||||
|
if (*bucket == value)
|
||||||
|
{
|
||||||
|
bucket = {};
|
||||||
|
m_size--;
|
||||||
|
if (i != index) rehash(m_capacity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} while (i != index);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
for (usize i = 0; i < m_capacity; i++) m_buckets[i].~Option();
|
||||||
|
|
||||||
|
free_impl(m_buckets);
|
||||||
|
m_capacity = m_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~HashTable()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool should_grow()
|
||||||
|
{
|
||||||
|
return (m_capacity == 0) || ((m_size * GROW_RATE) >= m_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> rehash(usize new_capacity)
|
||||||
|
{
|
||||||
|
HashTable<T> new_table;
|
||||||
|
TRY(new_table.initialize(new_capacity));
|
||||||
|
|
||||||
|
if (m_capacity != 0)
|
||||||
|
{
|
||||||
|
for (usize i = 0; i < m_capacity; i++)
|
||||||
|
{
|
||||||
|
auto& opt = m_buckets[i];
|
||||||
|
if (opt.has_value())
|
||||||
|
{
|
||||||
|
auto value = opt.release_value();
|
||||||
|
TRY(new_table.try_set(move(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swap(this, &new_table);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> initialize(usize initial_capacity)
|
||||||
|
{
|
||||||
|
check(m_buckets == nullptr);
|
||||||
|
m_capacity = initial_capacity;
|
||||||
|
m_buckets = (Option<T>*)TRY(calloc_impl(initial_capacity, sizeof(Option<T>), false));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<T>* m_buckets { nullptr };
|
||||||
|
usize m_capacity { 0 };
|
||||||
|
usize m_size { 0 };
|
||||||
|
// FIXME: Randomize this to protect against hash table attacks.
|
||||||
|
u64 m_salt { 0 };
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <luna/CString.h>
|
#include <luna/CString.h>
|
||||||
|
#include <luna/StringView.h>
|
||||||
#include <luna/Types.h>
|
#include <luna/Types.h>
|
||||||
|
|
||||||
template <usize Size> class StaticString
|
template <usize Size> class StaticString
|
||||||
@ -24,12 +25,27 @@ template <usize Size> class StaticString
|
|||||||
m_length = length;
|
m_length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void adopt(StringView string)
|
||||||
|
{
|
||||||
|
usize length = strlcpy(m_buffer, string.chars(),
|
||||||
|
string.length() > sizeof(m_buffer) ? sizeof(m_buffer) : string.length() + 1);
|
||||||
|
if (length > Size) { m_length = Size; }
|
||||||
|
else
|
||||||
|
m_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
StaticString<Size>& operator=(const char* string)
|
StaticString<Size>& operator=(const char* string)
|
||||||
{
|
{
|
||||||
adopt(string);
|
adopt(string);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StaticString<Size>& operator=(StringView string)
|
||||||
|
{
|
||||||
|
adopt(string);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
template <usize OtherSize> StaticString<Size>& operator=(const StaticString<OtherSize>& string)
|
template <usize OtherSize> StaticString<Size>& operator=(const StaticString<OtherSize>& string)
|
||||||
{
|
{
|
||||||
if constexpr (OtherSize == Size)
|
if constexpr (OtherSize == Size)
|
||||||
@ -46,11 +62,38 @@ template <usize Size> class StaticString
|
|||||||
return m_buffer;
|
return m_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char* data()
|
||||||
|
{
|
||||||
|
return m_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_length(usize len)
|
||||||
|
{
|
||||||
|
m_length = len;
|
||||||
|
}
|
||||||
|
|
||||||
usize length() const
|
usize length() const
|
||||||
{
|
{
|
||||||
return m_length;
|
return m_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void trim(StringView delim)
|
||||||
|
{
|
||||||
|
isize i = (isize)m_length;
|
||||||
|
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
char c = m_buffer[i];
|
||||||
|
if (!strchr(delim.chars(), c)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
|
||||||
|
m_buffer[i] = '\0';
|
||||||
|
|
||||||
|
m_length = (usize)i;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
char m_buffer[Size + 1];
|
char m_buffer[Size + 1];
|
||||||
usize m_length { 0 };
|
usize m_length { 0 };
|
||||||
|
@ -15,12 +15,7 @@ template <typename T> class Vector
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector(const Vector<T>& other)
|
Vector(const Vector<T>& other) = delete;
|
||||||
{
|
|
||||||
reserve(other.capacity());
|
|
||||||
memcpy(m_data, other.data(), other.size());
|
|
||||||
m_size = other.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector(Vector<T>&& other)
|
Vector(Vector<T>&& other)
|
||||||
{
|
{
|
||||||
@ -32,18 +27,7 @@ template <typename T> class Vector
|
|||||||
other.m_data = nullptr;
|
other.m_data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<T>& operator=(const Vector<T>& other)
|
Vector<T>& operator=(const Vector<T>& other) = delete;
|
||||||
{
|
|
||||||
if (&other == this) return *this;
|
|
||||||
|
|
||||||
if (m_data) clear();
|
|
||||||
|
|
||||||
reserve(other.capacity());
|
|
||||||
for (usize i = 0; i < other.size(); i++) { new (&m_data[i]) T(other.m_data[i]); }
|
|
||||||
m_size = other.size();
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<T>& operator=(Vector<T>&& other)
|
Vector<T>& operator=(Vector<T>&& other)
|
||||||
{
|
{
|
||||||
@ -217,6 +201,35 @@ template <typename T> class Vector
|
|||||||
m_data = nullptr;
|
m_data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<Vector<T>> shallow_copy()
|
||||||
|
{
|
||||||
|
Vector<T> other;
|
||||||
|
TRY(other.try_reserve(m_capacity));
|
||||||
|
memcpy(other.m_data, m_data, m_size);
|
||||||
|
other.m_size = m_size;
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Vector<T>> deep_copy()
|
||||||
|
{
|
||||||
|
Vector<T> other;
|
||||||
|
TRY(other.try_reserve(m_capacity));
|
||||||
|
for (usize i = 0; i < m_size; i++) { TRY(other.try_append(m_data[i])); }
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Vector<T>> deep_copy(Result<T> (*copy_function)(const T&))
|
||||||
|
{
|
||||||
|
Vector<T> other;
|
||||||
|
TRY(other.try_reserve(m_capacity));
|
||||||
|
for (usize i = 0; i < m_size; i++)
|
||||||
|
{
|
||||||
|
auto copy = TRY(copy_function(m_data[i]));
|
||||||
|
TRY(other.try_append(move(copy)));
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T* m_data { nullptr };
|
T* m_data { nullptr };
|
||||||
usize m_capacity { 0 };
|
usize m_capacity { 0 };
|
||||||
|
14
libluna/src/Hash.cpp
Normal file
14
libluna/src/Hash.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include <luna/Hash.h>
|
||||||
|
|
||||||
|
u64 hash_memory(const void* mem, usize size, u64 salt)
|
||||||
|
{
|
||||||
|
const char* p = (const char*)mem;
|
||||||
|
u64 h = salt;
|
||||||
|
while (--size) h = h * 101 + (u64)*p++;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> u64 hash(const char* const& value, u64 salt)
|
||||||
|
{
|
||||||
|
return hash_memory(value, strlen(value), salt);
|
||||||
|
}
|
@ -1,46 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* @file ArgumentParser.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Command-line argument parser.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <luna/StringView.h>
|
#include <luna/StringView.h>
|
||||||
#include <luna/Vector.h>
|
#include <luna/Vector.h>
|
||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief A simple command-line argument parser.
|
||||||
|
*/
|
||||||
class ArgumentParser
|
class ArgumentParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new ArgumentParser object.
|
||||||
|
*/
|
||||||
ArgumentParser();
|
ArgumentParser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a description for this command-line utility (shown in the help text).
|
||||||
|
*
|
||||||
|
* @param description The description to use.
|
||||||
|
*/
|
||||||
void add_description(StringView description);
|
void add_description(StringView description);
|
||||||
|
|
||||||
Result<void> add_positional_argument(StringView& out, StringView name, bool required);
|
/**
|
||||||
|
* @brief Register a new positional argument.
|
||||||
|
*
|
||||||
|
* @param out The variable where the argument's value will be stored.
|
||||||
|
* @param name The positional argument's name.
|
||||||
|
* @param fallback The value to use if the user does not provide one.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> add_positional_argument(StringView& out, StringView name, StringView fallback);
|
Result<void> add_positional_argument(StringView& out, StringView name, StringView fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a new positional argument.
|
||||||
|
*
|
||||||
|
* @param out The variable where the argument's value will be stored.
|
||||||
|
* @param name The positional argument's name.
|
||||||
|
* @param required Whether the user must enter a value for this argument. If this is false and the user does not
|
||||||
|
* enter a value, out will be set to an empty string.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
|
Result<void> add_positional_argument(StringView& out, StringView name, bool required);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a new switch argument.
|
||||||
|
*
|
||||||
|
* @param out This variable will be set to true if the user provides the switch argument on the command
|
||||||
|
* line.
|
||||||
|
* @param short_flag The short flag to use for this argument, excluding the '-' prefix: an option '-c' would be
|
||||||
|
* passed as 'c'. Can only be a single ASCII character. If you do not wish this argument to have a short flag,
|
||||||
|
* use the space character: ' '.
|
||||||
|
* @param long_flag The long flag to use for this argument, excluding the '--' prefix: an option '--example'
|
||||||
|
* would be passed as 'example'. Can be a string of any length. If you do not wish this argument to have a long
|
||||||
|
* flag, use an empty string: "".
|
||||||
|
* @param help The help text to show for this argument (optional).
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> add_switch_argument(bool& out, char short_flag, StringView long_flag, StringView help = {});
|
Result<void> add_switch_argument(bool& out, char short_flag, StringView long_flag, StringView help = {});
|
||||||
|
|
||||||
Result<void> add_value_argument(StringView& out, char short_flag, StringView long_flag, bool value_required,
|
/**
|
||||||
StringView help = {});
|
* @brief Register a new value argument.
|
||||||
|
*
|
||||||
|
* @param out The variable where the argument's value will be stored.
|
||||||
|
* @param short_flag The short flag to use for this argument, excluding the '-' prefix: an option '-c' would be
|
||||||
|
* passed as 'c'. Can only be a single ASCII character. If you do not wish this argument to have a short flag,
|
||||||
|
* use the space character: ' '.
|
||||||
|
* @param long_flag The long flag to use for this argument, excluding the '--' prefix: an option '--example'
|
||||||
|
* would be passed as 'example'. Can be a string of any length. If you do not wish this argument to have a long
|
||||||
|
* flag, use an empty string: "".
|
||||||
|
* @param fallback The value to use if the user uses the argument but does not provide a value. If, however, the
|
||||||
|
* user does not specify this argument at all, out WILL NOT BE MODIFIED!
|
||||||
|
* @param help The help text to show for this argument (optional).
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> add_value_argument(StringView& out, char short_flag, StringView long_flag, StringView fallback,
|
Result<void> add_value_argument(StringView& out, char short_flag, StringView long_flag, StringView fallback,
|
||||||
StringView help = {});
|
StringView help = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a new value argument.
|
||||||
|
*
|
||||||
|
* @param out The variable where the argument's value will be stored.
|
||||||
|
* @param short_flag The short flag to use for this argument, excluding the '-' prefix: an option '-c' would be
|
||||||
|
* passed as 'c'. Can only be a single ASCII character. If you do not wish this argument to have a short flag,
|
||||||
|
* use the space character: ' '.
|
||||||
|
* @param long_flag The long flag to use for this argument, excluding the '--' prefix: an option '--example'
|
||||||
|
* would be passed as 'example'. Can be a string of any length. If you do not wish this argument to have a long
|
||||||
|
* flag, use an empty string: "".
|
||||||
|
* @param value_required Whether the user is required to pass a value when using this argument. If this is
|
||||||
|
* false and the user does not enter a value along with the argument, out will be set to an empty string. If,
|
||||||
|
* however, the user does not specify this argument at all, out WILL NOT BE MODIFIED!
|
||||||
|
* @param help The help text to show for this argument (optional).
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
|
Result<void> add_value_argument(StringView& out, char short_flag, StringView long_flag, bool value_required,
|
||||||
|
StringView help = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the vector to append extra unregistered positional arguments to, after all
|
||||||
|
* registered positional arguments have been parsed. (If no vector is set, these values will instead be
|
||||||
|
* discarded!)
|
||||||
|
*
|
||||||
|
* @param out The vector of StringViews to use.
|
||||||
|
* @param allow_no_more_flags If set, after starting to append values into the vector, no more flags (switch and
|
||||||
|
* value arguments) will be parsed, and will instead be treated as regular positional arguments, as if '--' had
|
||||||
|
* been specified on the command line.
|
||||||
|
*/
|
||||||
void set_vector_argument(Vector<StringView>& out, bool allow_no_more_flags = false);
|
void set_vector_argument(Vector<StringView>& out, bool allow_no_more_flags = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse the given command-line using this ArgumentParser's registered arguments.
|
||||||
|
*
|
||||||
|
* @param argc The argc value passed to main() or luna_main().
|
||||||
|
* @param argv The argv value passed to main() or luna_main().
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> parse(int argc, char* const* argv);
|
Result<void> parse(int argc, char* const* argv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A program's copyright and version information.
|
||||||
|
*/
|
||||||
struct ProgramInfo
|
struct ProgramInfo
|
||||||
{
|
{
|
||||||
StringView name;
|
StringView name; // The program's name.
|
||||||
StringView version;
|
StringView version; // The program's version/release.
|
||||||
StringView copyright;
|
StringView copyright; // The program's copyright statement.
|
||||||
StringView license;
|
StringView license; // The program's licensing information.
|
||||||
StringView authors;
|
StringView authors; // The program's authors (optional).
|
||||||
StringView package;
|
StringView package; // The package the program is a part of (optional).
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set this program's copyright and version information (shown in the version text).
|
||||||
|
*
|
||||||
|
* @param info The program information to use.
|
||||||
|
*/
|
||||||
void add_program_info(ProgramInfo info);
|
void add_program_info(ProgramInfo info);
|
||||||
|
|
||||||
/* Used from programs that are part of the Luna source tree, to add the same version info for all programs.
|
/**
|
||||||
* Should not be used otherwise. */
|
* @brief For programs that are part of the Luna source tree, set the version information using Luna's own
|
||||||
|
* version and copyright info and the program name.
|
||||||
|
*
|
||||||
|
* @param name The program name to show in the version information.
|
||||||
|
*/
|
||||||
void add_system_program_info(StringView name);
|
void add_system_program_info(StringView name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Show a short message describing how to find usage information (usually "<program> --help").
|
||||||
|
*
|
||||||
|
* The actual message shown is:
|
||||||
|
* "Try running '<program> --help' for more information." if --help has not been overridden.
|
||||||
|
* "Try running '<program> -h' for more information." if -h has not been overridden.
|
||||||
|
* If both have been overridden, no output is shown.
|
||||||
|
*
|
||||||
|
* Then, the program exits with a non-zero exit code.
|
||||||
|
* This function is designed to be used when the program detects an invalid argument value after parse() has run
|
||||||
|
* successfully.
|
||||||
|
*
|
||||||
|
* @param program_name The program name to show (usually argv[0]).
|
||||||
|
*/
|
||||||
void short_usage(StringView program_name);
|
void short_usage(StringView program_name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file Directory.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief A C++-friendly API for directory access.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <luna/SharedPtr.h>
|
#include <luna/SharedPtr.h>
|
||||||
@ -7,44 +16,85 @@
|
|||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief An object-oriented directory handle, which is closed when all references to it go out of scope.
|
||||||
|
*/
|
||||||
class Directory
|
class Directory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create a new Directory object from a path, and open the directory.
|
||||||
|
*
|
||||||
|
* @param path The path to open.
|
||||||
|
* @return Result<SharedPtr<Directory>> A new Directory object.
|
||||||
|
*/
|
||||||
static Result<SharedPtr<Directory>> open(const Path& path);
|
static Result<SharedPtr<Directory>> open(const Path& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A filter to skip certain directory entries.
|
||||||
|
*/
|
||||||
enum class Filter
|
enum class Filter
|
||||||
{
|
{
|
||||||
None,
|
None, // Do not skip anything.
|
||||||
Hidden,
|
Hidden, // Skip all hidden files (that start with a '.' character).
|
||||||
ParentAndBase
|
ParentAndBase // Skip only the '.' and '..' entries.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the next entry name from this Directory.
|
||||||
|
*
|
||||||
|
* @param filter The entries to filter out.
|
||||||
|
* @return Result<String> The next entry's name as an owned String object, or an empty string if
|
||||||
|
* there are no more entries.
|
||||||
|
*/
|
||||||
Result<String> next(Filter filter);
|
Result<String> next(Filter filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the list of entry names from this Directory.
|
||||||
|
*
|
||||||
|
* @param filter The entries to filter out.
|
||||||
|
* @return Result<Vector<String>> The list of entry names.
|
||||||
|
*/
|
||||||
Result<Vector<String>> list_names(Filter filter);
|
Result<Vector<String>> list_names(Filter filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A directory entry with extra information.
|
||||||
|
*/
|
||||||
struct Entry
|
struct Entry
|
||||||
{
|
{
|
||||||
String name;
|
String name; // The entry's name.
|
||||||
mode_t mode;
|
mode_t mode; // The entry's file permissions.
|
||||||
usize size;
|
usize size; // The entry's size.
|
||||||
time_t mtime;
|
time_t mtime; // The entry's last modification time.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the list of entries from this Directory, fetching extra information for each one.
|
||||||
|
*
|
||||||
|
* @param filter The entries to filter out.
|
||||||
|
* @return Result<Vector<Entry>> The list of entries.
|
||||||
|
*/
|
||||||
Result<Vector<Entry>> list(Filter filter);
|
Result<Vector<Entry>> list(Filter filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rewinds this Directory back to the beginning of its entry list.
|
||||||
|
*/
|
||||||
void rewind();
|
void rewind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the file descriptor associated with this Directory.
|
||||||
|
*
|
||||||
|
* @return int The file descriptor.
|
||||||
|
*/
|
||||||
int fd()
|
int fd()
|
||||||
{
|
{
|
||||||
return dirfd(m_dirp);
|
return dirfd(m_dirp);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Directory();
|
~Directory();
|
||||||
|
|
||||||
private:
|
|
||||||
Directory(Badge<Directory>);
|
Directory(Badge<Directory>);
|
||||||
|
|
||||||
|
private:
|
||||||
DIR* m_dirp { nullptr };
|
DIR* m_dirp { nullptr };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @file File.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief A C++-friendly API for file access.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <bits/open-flags.h>
|
#include <fcntl.h>
|
||||||
#include <luna/Buffer.h>
|
#include <luna/Buffer.h>
|
||||||
#include <luna/Result.h>
|
#include <luna/Result.h>
|
||||||
#include <luna/SharedPtr.h>
|
#include <luna/SharedPtr.h>
|
||||||
@ -9,52 +18,160 @@
|
|||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief An object-oriented file handle, which is closed when all references to it go out of scope.
|
||||||
|
*/
|
||||||
class File
|
class File
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The supported file opening modes.
|
||||||
|
*/
|
||||||
enum OpenMode
|
enum OpenMode
|
||||||
{
|
{
|
||||||
ReadOnly = O_RDONLY,
|
ReadOnly = O_RDONLY, // Open the file read-only.
|
||||||
WriteOnly = O_WRONLY | O_TRUNC,
|
WriteOnly = O_WRONLY | O_TRUNC, // Open the file write-only, truncating it.
|
||||||
ReadWrite = O_RDWR | O_TRUNC,
|
ReadWrite = O_RDWR | O_TRUNC, // Open the file read-write, truncating it.
|
||||||
Append = O_WRONLY | O_APPEND,
|
Append = O_WRONLY | O_APPEND, // Open the file write-only, and append to it.
|
||||||
ReadAppend = O_RDWR | O_APPEND,
|
ReadAppend = O_RDWR | O_APPEND, // Open the file read-write, and append to it.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new File object from a file path, opening the file.
|
||||||
|
*
|
||||||
|
* @param path The path to open.
|
||||||
|
* @param flags The opening mode to pass to open(2).
|
||||||
|
* @return Result<SharedPtr<File>> A new File object, or ENOENT if the file did not exist.
|
||||||
|
*/
|
||||||
static Result<SharedPtr<File>> open(const Path& path, OpenMode flags);
|
static Result<SharedPtr<File>> open(const Path& path, OpenMode flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new File object from a file path, opening the file or creating it if it does not exist.
|
||||||
|
*
|
||||||
|
* @param path The path to open.
|
||||||
|
* @param flags The opening mode to pass to open(2).
|
||||||
|
* @param mode If a new file is created, the file permissions to apply to it. (default: u=rw,g=r,o=r)
|
||||||
|
* @return Result<SharedPtr<File>> A new File object.
|
||||||
|
*/
|
||||||
static Result<SharedPtr<File>> open_or_create(const Path& path, OpenMode flags, mode_t mode = 0644);
|
static Result<SharedPtr<File>> open_or_create(const Path& path, OpenMode flags, mode_t mode = 0644);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new File object from a file path, creating the file but erroring out if the file already
|
||||||
|
* exists.
|
||||||
|
*
|
||||||
|
* @param path The path to open.
|
||||||
|
* @param flags The opening mode to pass to open(2).
|
||||||
|
* @param mode The file permissions to apply to the created file. (default: u=rw,g=r,o=r)
|
||||||
|
* @return Result<SharedPtr<File>> A new File object, or EEXIST if the file already existed.
|
||||||
|
*/
|
||||||
static Result<SharedPtr<File>> create(const Path& path, OpenMode flags, mode_t mode = 0644);
|
static Result<SharedPtr<File>> create(const Path& path, OpenMode flags, mode_t mode = 0644);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
If path is "-", return standard input (as is common for many CLI apps). Otherwise, open path for reading.
|
* @brief If path is "-", return standard input (as is common for many CLI apps). Otherwise, open path for
|
||||||
|
* reading.
|
||||||
This function is a convenience function for CLI apps, so that they don't need to check path and open standard
|
*
|
||||||
input if necessary.
|
* @param path The path to open.
|
||||||
*/
|
* @return Result<SharedPtr<File>> A new File object if path was not "-", or the File
|
||||||
|
* object for standard input if path was "-".
|
||||||
|
*/
|
||||||
static Result<SharedPtr<File>> open_input_file(StringView path);
|
static Result<SharedPtr<File>> open_input_file(StringView path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the File corresponding to standard input.
|
||||||
|
*
|
||||||
|
* @return SharedPtr<File> The File object for standard input.
|
||||||
|
*/
|
||||||
static SharedPtr<File> standard_input();
|
static SharedPtr<File> standard_input();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the File corresponding to standard output.
|
||||||
|
*
|
||||||
|
* @return SharedPtr<File> The File object for standard output.
|
||||||
|
*/
|
||||||
static SharedPtr<File> standard_output();
|
static SharedPtr<File> standard_output();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the File corresponding to standard error.
|
||||||
|
*
|
||||||
|
* @return SharedPtr<File> The File object for standard error.
|
||||||
|
*/
|
||||||
static SharedPtr<File> standard_error();
|
static SharedPtr<File> standard_error();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Make this File object automatically close on the next call to execve(2).
|
||||||
|
*/
|
||||||
void set_close_on_exec();
|
void set_close_on_exec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a string to this File.
|
||||||
|
*
|
||||||
|
* @param str The string to write (can be not null-terminated).
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> write(StringView str);
|
Result<void> write(StringView str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a memory buffer to this File.
|
||||||
|
*
|
||||||
|
* @param buf The buffer to write.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> write(const Buffer& buf);
|
Result<void> write(const Buffer& buf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a line from this File.
|
||||||
|
*
|
||||||
|
* @return Result<String> The line as an owned String object, or an empty string if there is no more
|
||||||
|
* data to read.
|
||||||
|
*/
|
||||||
Result<String> read_line();
|
Result<String> read_line();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the entire File's contents as a string.
|
||||||
|
*
|
||||||
|
* @return Result<String> The file's contents as an owned String object.
|
||||||
|
*/
|
||||||
Result<String> read_all_as_string();
|
Result<String> read_all_as_string();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read data from this File.
|
||||||
|
*
|
||||||
|
* @param buf The output buffer to store the data in. Will be resized to fit the data.
|
||||||
|
* @param size The maximum number of bytes to read.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> read(Buffer& buf, usize size);
|
Result<void> read(Buffer& buf, usize size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the entire File's contents.
|
||||||
|
*
|
||||||
|
* @return Result<Buffer> The file's contents as an owned Buffer object.
|
||||||
|
*/
|
||||||
Result<Buffer> read_all();
|
Result<Buffer> read_all();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a single character from the File.
|
||||||
|
*
|
||||||
|
* @return Result<int> A character value above 0 (to be cast to an unsigned char), or EOF (typically -1)
|
||||||
|
* if there is no more data to read.
|
||||||
|
*/
|
||||||
Result<int> getchar();
|
Result<int> getchar();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the file descriptor associated with this File.
|
||||||
|
*
|
||||||
|
* @return int The file descriptor.
|
||||||
|
*/
|
||||||
int fd() const
|
int fd() const
|
||||||
{
|
{
|
||||||
return m_fd;
|
return m_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move this File's file pointer back to the beginning.
|
||||||
|
*/
|
||||||
|
void rewind();
|
||||||
|
|
||||||
File(Badge<File>);
|
File(Badge<File>);
|
||||||
~File();
|
~File();
|
||||||
|
|
||||||
@ -68,8 +185,39 @@ namespace os
|
|||||||
int m_fd { -1 };
|
int m_fd { -1 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print a formatted string to standard output.
|
||||||
|
*
|
||||||
|
* @param fmt The format string (in the same format as printf(3)).
|
||||||
|
* @param ... The format arguments.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> print(StringView fmt, ...);
|
Result<void> print(StringView fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print a newline-terminated formatted string to standard output.
|
||||||
|
*
|
||||||
|
* @param fmt The format string (in the same format as printf(3)).
|
||||||
|
* @param ... The format arguments.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> println(StringView fmt, ...);
|
Result<void> println(StringView fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print a formatted string to standard error.
|
||||||
|
*
|
||||||
|
* @param fmt The format string (in the same format as printf(3)).
|
||||||
|
* @param ... The format arguments.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> eprint(StringView fmt, ...);
|
Result<void> eprint(StringView fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print a newline-terminated formatted string to standard error.
|
||||||
|
*
|
||||||
|
* @param fmt The format string (in the same format as printf(3)).
|
||||||
|
* @param ... The format arguments.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> eprintln(StringView fmt, ...);
|
Result<void> eprintln(StringView fmt, ...);
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* @file FileSystem.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief APIs to read and modify the general file system.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <bits/struct_stat.h>
|
|
||||||
#include <luna/Result.h>
|
#include <luna/Result.h>
|
||||||
#include <luna/StringView.h>
|
#include <luna/StringView.h>
|
||||||
#include <os/Path.h>
|
#include <os/Path.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
namespace FileSystem
|
namespace FileSystem
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief Check whether a given path exists.
|
||||||
|
*
|
||||||
|
* @param path The path to check.
|
||||||
|
* @param follow_symlinks If path is a symbolic link, whether to follow it (defaults to true).
|
||||||
|
* @return true The path exists.
|
||||||
|
* @return false The path does not exist, or it was a symbolic link and following it leaded to a nonexistent
|
||||||
|
* path.
|
||||||
|
*/
|
||||||
bool exists(const Path& path, bool follow_symlinks = true);
|
bool exists(const Path& path, bool follow_symlinks = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether a given path is a directory.
|
||||||
|
*
|
||||||
|
* @param path The path to check.
|
||||||
|
* @param follow_symlinks If path is a symbolic link, whether to follow it (defaults to false).
|
||||||
|
* @return true The path is a directory, or the path was a symbolic link and following it leaded to a directory.
|
||||||
|
* @return false The path does not exist, or it is not a directory.
|
||||||
|
*/
|
||||||
bool is_directory(const Path& path, bool follow_symlinks = false);
|
bool is_directory(const Path& path, bool follow_symlinks = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieve file status information.
|
||||||
|
*
|
||||||
|
* @param path The path to examine.
|
||||||
|
* @param st The buffer to store file status information in.
|
||||||
|
* @param follow_symlinks If path is a symbolic link, whether to follow it and retrieve its target's status, or
|
||||||
|
* otherwise retrieve the symbolic link's status (defaults to true).
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks = true);
|
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new directory in the file system.
|
||||||
|
*
|
||||||
|
* @param path The path for the new directory.
|
||||||
|
* @param mode The file permissions to apply to the new directory.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> create_directory(StringView path, mode_t mode);
|
Result<void> create_directory(StringView path, mode_t mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a file or empty directory from the file system. (The file will continue to exist if there are
|
||||||
|
* other hard links to it or a process still has it open.)
|
||||||
|
*
|
||||||
|
* @param path The path to remove.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> remove(const Path& path);
|
Result<void> remove(const Path& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a directory tree from the file system recursively, deleting subfiles and subdirectories as
|
||||||
|
* well.
|
||||||
|
*
|
||||||
|
* @param path The path to remove.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> remove_tree(const Path& path);
|
Result<void> remove_tree(const Path& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the target of a symbolic link.
|
||||||
|
*
|
||||||
|
* @param path The symbolic link's path.
|
||||||
|
* @return Result<String> If the path was not a symbolic link, an empty string, otherwise the symbolic link's
|
||||||
|
* target.
|
||||||
|
*/
|
||||||
Result<String> readlink(const Path& path);
|
Result<String> readlink(const Path& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the current process's working directory.
|
||||||
|
*
|
||||||
|
* @return Result<String> The working directory as an owned String object.
|
||||||
|
*/
|
||||||
Result<String> working_directory();
|
Result<String> working_directory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fetch the current user' home directory.
|
||||||
|
*
|
||||||
|
* @return Result<String> The home directory as an owned String object.
|
||||||
|
*/
|
||||||
Result<String> home_directory();
|
Result<String> home_directory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Change the current process's working directory.
|
||||||
|
*
|
||||||
|
* @param path The new working directory.
|
||||||
|
* @return Result<void> Whether the operation succeeded.
|
||||||
|
*/
|
||||||
Result<void> change_directory(StringView path);
|
Result<void> change_directory(StringView path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @file Main.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Alternative C++ main function.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <luna/Result.h>
|
#include <luna/Result.h>
|
||||||
|
|
||||||
Result<int> luna_main(int argc, char** argv);
|
/**
|
||||||
|
* @brief The main() function for Luna C++ apps. This function is extern as it should be implemented by the user if they
|
||||||
|
* do not implement the C main() function.
|
||||||
|
*
|
||||||
|
* @param argc The argc parameter passed to the regular main() function.
|
||||||
|
* @param argv The argv parameter passed to the regular main() function.
|
||||||
|
* @return Result<int> A normal status code, or an errno code, which will be displayed along with an abnormal exit code.
|
||||||
|
*/
|
||||||
|
extern Result<int> luna_main(int argc, char** argv);
|
||||||
|
@ -1,8 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @file Mode.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Visual file permissions parsing and formatting.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief Create a visual representation of file permissions using the integral representation.
|
||||||
|
*
|
||||||
|
* The output is formatted in the same style as the output of "ls -l".
|
||||||
|
* For example:
|
||||||
|
* An integral mode of S_IFREG | 0644 would result in "-rw-r--r--".
|
||||||
|
* An integral mode of S_IFDIR | 0755 would result in "drwxr-xr-x".
|
||||||
|
* An integral mode of S_IFLNK | 0777 would result in "lrwxrwxrwx".
|
||||||
|
*
|
||||||
|
* @param mode The integral mode.
|
||||||
|
* @param out The buffer to store the formatted file permissions in, as a C-string (minimum 11 bytes, including the
|
||||||
|
* terminating null byte).
|
||||||
|
*/
|
||||||
void format_mode(mode_t mode, char out[11]);
|
void format_mode(mode_t mode, char out[11]);
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* @file Path.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Dirfd-friendly Path class.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <luna/StringView.h>
|
#include <luna/StringView.h>
|
||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief A dirfd-friendly Path class.
|
||||||
|
*/
|
||||||
class Path
|
class Path
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Path object using a C-string file path.
|
||||||
|
*
|
||||||
|
* @param path The file path to use.
|
||||||
|
*/
|
||||||
Path(const char* path);
|
Path(const char* path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Path object using a StringView file path.
|
||||||
|
*
|
||||||
|
* @param path The file path to use.
|
||||||
|
*/
|
||||||
Path(StringView path);
|
Path(StringView path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Path object using a file descriptor.
|
||||||
|
*
|
||||||
|
* @param fd The file descriptor to use.
|
||||||
|
*/
|
||||||
Path(int fd);
|
Path(int fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Path object using a parent file descriptor and a file path.
|
||||||
|
*
|
||||||
|
* @param dirfd The parent file descriptor to use.
|
||||||
|
* @param name The file path to use.
|
||||||
|
*/
|
||||||
Path(int dirfd, StringView name);
|
Path(int dirfd, StringView name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the file descriptor associated with this Path.
|
||||||
|
*
|
||||||
|
* @return int The file descriptor, or AT_FDCWD if this Path does not have one.
|
||||||
|
*/
|
||||||
int dirfd() const
|
int dirfd() const
|
||||||
{
|
{
|
||||||
return m_dirfd.value_or(AT_FDCWD);
|
return m_dirfd.value_or(AT_FDCWD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the file path associated with this Path.
|
||||||
|
*
|
||||||
|
* @return StringView The path, or an empty StringView if this Path does not have one.
|
||||||
|
*/
|
||||||
StringView name() const
|
StringView name() const
|
||||||
{
|
{
|
||||||
return m_name.value_or(""_sv);
|
return m_name.value_or(""_sv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine whether this Path consists only of a file descriptor.
|
||||||
|
*
|
||||||
|
* @return int The fstatat()-compatible flag value to use in case this Path consists only of a file descriptor.
|
||||||
|
*/
|
||||||
int is_empty_path() const
|
int is_empty_path() const
|
||||||
{
|
{
|
||||||
if (m_dirfd.has_value() && !m_name.has_value()) return AT_EMPTY_PATH;
|
if (m_dirfd.has_value() && !m_name.has_value()) return AT_EMPTY_PATH;
|
||||||
|
@ -1,17 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* @file Process.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Functions to manipulate processes.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <luna/Result.h>
|
#include <luna/Result.h>
|
||||||
#include <luna/String.h>
|
#include <luna/String.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @brief An object representing a process.
|
||||||
|
*/
|
||||||
class Process
|
class Process
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create a new process, which is an identical copy of the current one.
|
||||||
|
*
|
||||||
|
* @return Result<pid_t> The child's process ID in the parent process, and 0 in the child process.
|
||||||
|
*/
|
||||||
static Result<pid_t> fork();
|
static Result<pid_t> fork();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replace the current process's executable with another one.
|
||||||
|
*
|
||||||
|
* @param path The new executable's path.
|
||||||
|
* @param args The argument list to pass to the new executable.
|
||||||
|
* @param search_in_path Determines whether to search in the system binary directories if path is just a name.
|
||||||
|
* @return Result<void> Always an error, as this function does not return on success.
|
||||||
|
*/
|
||||||
static Result<void> exec(StringView path, Slice<String> args, bool search_in_path = true);
|
static Result<void> exec(StringView path, Slice<String> args, bool search_in_path = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replace the current process's executable with another one.
|
||||||
|
*
|
||||||
|
* @param path The new executable's path.
|
||||||
|
* @param args The argument list to pass to the new executable.
|
||||||
|
* @param search_in_path Determines whether to search in the system binary directories if path is just a name.
|
||||||
|
* @return Result<void> Always an error, as this function does not return on success.
|
||||||
|
*/
|
||||||
static Result<void> exec(StringView path, Slice<StringView> args, bool search_in_path = true);
|
static Result<void> exec(StringView path, Slice<StringView> args, bool search_in_path = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replace the current process's executable with another one.
|
||||||
|
*
|
||||||
|
* @param path The new executable's path.
|
||||||
|
* @param args The argument list to pass to the new executable.
|
||||||
|
* @param env The environment to pass to the new executable, instead of the current environment.
|
||||||
|
* @param search_in_path Determines whether to search in the system binary directories if path is just a name.
|
||||||
|
* @return Result<void> Always an error, as this function does not return on success.
|
||||||
|
*/
|
||||||
static Result<void> exec(StringView path, Slice<String> args, Slice<String> env, bool search_in_path = true);
|
static Result<void> exec(StringView path, Slice<String> args, Slice<String> env, bool search_in_path = true);
|
||||||
|
|
||||||
|
// To use as the child argument to wait() to wait for any child.
|
||||||
|
static constexpr pid_t ANY_CHILD = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for a child process to finish running.
|
||||||
|
*
|
||||||
|
* @param child The process ID of the child process, or ANY_CHILD to wait for any child.
|
||||||
|
* @param status The child's exit status will be stored here. If you don't want it, use nullptr.
|
||||||
|
* @param options Options to pass to waitpid(2).
|
||||||
|
* @return Result<pid_t> The process ID of the child process that was waited for (may not be the same as the
|
||||||
|
* child argument if ANY_CHILD was passed).
|
||||||
|
*/
|
||||||
|
static Result<pid_t> wait(pid_t child, int* status, int options = 0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file ArgumentParser.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Command-line argument parser.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <luna/StringBuilder.h>
|
#include <luna/StringBuilder.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
@ -75,6 +84,7 @@ namespace os
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change this every year!
|
||||||
constexpr auto copyright_text = "Copyright (C) 2023, the Luna authors.";
|
constexpr auto copyright_text = "Copyright (C) 2023, the Luna authors.";
|
||||||
constexpr auto license_text = "Licensed under the BSD-2 license <https://opensource.org/license/bsd-2-clause/>";
|
constexpr auto license_text = "Licensed under the BSD-2 license <https://opensource.org/license/bsd-2-clause/>";
|
||||||
|
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file Directory.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief A C++-friendly API for directory access.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <luna/CString.h>
|
#include <luna/CString.h>
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
#include <fcntl.h>
|
/**
|
||||||
|
* @file File.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief A C++-friendly API for file access.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <luna/StringBuilder.h>
|
#include <luna/StringBuilder.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
@ -194,6 +202,11 @@ namespace os
|
|||||||
fcntl(m_fd, F_SETFD, FD_CLOEXEC);
|
fcntl(m_fd, F_SETFD, FD_CLOEXEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void File::rewind()
|
||||||
|
{
|
||||||
|
lseek(m_fd, 0, SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Do not allocate memory for printing.
|
// FIXME: Do not allocate memory for printing.
|
||||||
Result<void> print_impl(SharedPtr<File> f, StringView fmt, va_list ap)
|
Result<void> print_impl(SharedPtr<File> f, StringView fmt, va_list ap)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
#include <bits/modes.h>
|
/**
|
||||||
|
* @file FileSystem.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief APIs to read and modify the general file system.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -33,9 +41,8 @@ namespace os::FileSystem
|
|||||||
|
|
||||||
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks)
|
Result<void> stat(const Path& path, struct stat& st, bool follow_symlinks)
|
||||||
{
|
{
|
||||||
long rc =
|
long rc = syscall(SYS_fstatat, path.dirfd(), path.name().chars(), &st,
|
||||||
syscall(SYS_fstatat, path.dirfd(), path.name().chars(), &st,
|
(int)(path.is_empty_path() | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW)));
|
||||||
(int)((path.is_empty_path() ? AT_EMPTY_PATH : 0) | (follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW)));
|
|
||||||
|
|
||||||
return Result<void>::from_syscall(rc);
|
return Result<void>::from_syscall(rc);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file Main.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Alternative C++ main function.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <os/Main.h>
|
#include <os/Main.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -5,6 +14,8 @@
|
|||||||
int g_argc;
|
int g_argc;
|
||||||
char** g_argv;
|
char** g_argv;
|
||||||
|
|
||||||
|
// This function will only be linked into the final binary if the user does not already define main(), which implies
|
||||||
|
// that they should have defined luna_main() instead. If not, good for them: linker errors!
|
||||||
__attribute__((weak)) int main(int argc, char** argv)
|
__attribute__((weak)) int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
g_argc = argc;
|
g_argc = argc;
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file Mode.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Visual file permissions parsing and formatting.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <os/Mode.h>
|
#include <os/Mode.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file Path.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Dirfd-friendly Path class.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <os/Path.h>
|
#include <os/Path.h>
|
||||||
|
|
||||||
namespace os
|
namespace os
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file Process.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Functions to manipulate processes.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
@ -53,4 +62,10 @@ namespace os
|
|||||||
|
|
||||||
return err(errno);
|
return err(errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<pid_t> Process::wait(pid_t child, int* status, int options)
|
||||||
|
{
|
||||||
|
long rc = syscall(SYS_waitpid, child, status, options);
|
||||||
|
return Result<pid_t>::from_syscall(rc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ luna_test(libluna/TestVector.cpp TestVector)
|
|||||||
luna_test(libluna/TestBase64.cpp TestBase64)
|
luna_test(libluna/TestBase64.cpp TestBase64)
|
||||||
luna_test(libluna/TestUtf8.cpp TestUtf8)
|
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_app(run-tests.cpp run-tests)
|
luna_app(run-tests.cpp run-tests)
|
||||||
endif()
|
endif()
|
||||||
|
79
tests/libluna/TestHashTable.cpp
Normal file
79
tests/libluna/TestHashTable.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include <luna/HashTable.h>
|
||||||
|
#include <os/File.h>
|
||||||
|
#include <test.h>
|
||||||
|
|
||||||
|
struct TwoInts
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
int b;
|
||||||
|
|
||||||
|
bool operator==(TwoInts other) const
|
||||||
|
{
|
||||||
|
return other.a == a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> u64 hash(const TwoInts& value, u64 salt)
|
||||||
|
{
|
||||||
|
return hash(value.a, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestResult test_empty_hash_table()
|
||||||
|
{
|
||||||
|
HashTable<int> table;
|
||||||
|
|
||||||
|
validate(table.try_find(0) == nullptr);
|
||||||
|
|
||||||
|
test_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestResult test_hash_table_find()
|
||||||
|
{
|
||||||
|
HashTable<int> table;
|
||||||
|
|
||||||
|
validate(TRY(table.try_set(0)));
|
||||||
|
|
||||||
|
validate(table.try_find(0));
|
||||||
|
validate(table.try_find(1) == nullptr);
|
||||||
|
|
||||||
|
test_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestResult test_hash_table_remove()
|
||||||
|
{
|
||||||
|
HashTable<int> table;
|
||||||
|
|
||||||
|
validate(TRY(table.try_set(0)));
|
||||||
|
|
||||||
|
validate(table.try_find(0));
|
||||||
|
|
||||||
|
validate(table.try_remove(0));
|
||||||
|
|
||||||
|
validate(table.try_find(0) == nullptr);
|
||||||
|
|
||||||
|
test_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestResult test_hash_table_duplicates()
|
||||||
|
{
|
||||||
|
HashTable<TwoInts> table;
|
||||||
|
|
||||||
|
validate(TRY(table.try_set(TwoInts { 1, 5 })));
|
||||||
|
validate(!TRY(table.try_set(TwoInts { 1, 3 })));
|
||||||
|
|
||||||
|
validate(table.try_find(TwoInts { 1, 0 })->b == 5);
|
||||||
|
|
||||||
|
test_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> test_main()
|
||||||
|
{
|
||||||
|
test_prelude;
|
||||||
|
|
||||||
|
run_test(test_empty_hash_table);
|
||||||
|
run_test(test_hash_table_find);
|
||||||
|
run_test(test_hash_table_remove);
|
||||||
|
run_test(test_hash_table_duplicates);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
@ -17,7 +17,7 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
|
|
||||||
auto dir = TRY(os::Directory::open(test_dir));
|
auto dir = TRY(os::Directory::open(test_dir));
|
||||||
|
|
||||||
auto files = TRY(dir->list(os::Directory::Filter::Hidden));
|
auto files = TRY(dir->list_names(os::Directory::Filter::Hidden));
|
||||||
|
|
||||||
for (const auto& program : files)
|
for (const auto& program : files)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user