Kernel: Add basic Credentials for the system

Now filesystem access/open, etc confirm that you have access for rwxs
This commit is contained in:
Bananymous 2023-06-11 19:52:13 +03:00
parent 46c34db6cb
commit c62e820bcf
13 changed files with 168 additions and 54 deletions

View File

@ -21,12 +21,10 @@ namespace LibELF
{ {
#ifdef __is_kernel #ifdef __is_kernel
BAN::ErrorOr<BAN::UniqPtr<ELF>> ELF::load_from_file(BAN::StringView file_path) BAN::ErrorOr<BAN::UniqPtr<ELF>> ELF::load_from_file(BAN::RefPtr<Inode> inode)
{ {
auto file = TRY(VirtualFileSystem::get().file_from_absolute_path(file_path, true));
PageTable::current().lock(); PageTable::current().lock();
size_t page_count = BAN::Math::div_round_up<size_t>(file.inode->size(), PAGE_SIZE); size_t page_count = BAN::Math::div_round_up<size_t>(inode->size(), PAGE_SIZE);
vaddr_t vaddr = PageTable::current().get_free_contiguous_pages(page_count, (vaddr_t)g_kernel_end); vaddr_t vaddr = PageTable::current().get_free_contiguous_pages(page_count, (vaddr_t)g_kernel_end);
auto virtual_range = BAN::UniqPtr<VirtualRange>::adopt( auto virtual_range = BAN::UniqPtr<VirtualRange>::adopt(
VirtualRange::create( VirtualRange::create(
@ -37,9 +35,9 @@ namespace LibELF
); );
PageTable::current().unlock(); PageTable::current().unlock();
TRY(file.inode->read(0, (void*)vaddr, file.inode->size())); TRY(inode->read(0, (void*)vaddr, inode->size()));
ELF* elf_ptr = new ELF(BAN::move(virtual_range), file.inode->size()); ELF* elf_ptr = new ELF(BAN::move(virtual_range), inode->size());
if (elf_ptr == nullptr) if (elf_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM); return BAN::Error::from_errno(ENOMEM);

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#ifdef __is_kernel #ifdef __is_kernel
#include <kernel/FS/Inode.h>
#include <kernel/Memory/VirtualRange.h> #include <kernel/Memory/VirtualRange.h>
#endif #endif
@ -16,7 +17,11 @@ namespace LibELF
class ELF class ELF
{ {
public: public:
#ifdef __is_kernel
static BAN::ErrorOr<BAN::UniqPtr<ELF>> load_from_file(BAN::RefPtr<Kernel::Inode>);
#else
static BAN::ErrorOr<BAN::UniqPtr<ELF>> load_from_file(BAN::StringView); static BAN::ErrorOr<BAN::UniqPtr<ELF>> load_from_file(BAN::StringView);
#endif
const Elf64FileHeader& file_header64() const; const Elf64FileHeader& file_header64() const;
const Elf64ProgramHeader& program_header64(size_t) const; const Elf64ProgramHeader& program_header64(size_t) const;

View File

@ -19,6 +19,7 @@ set(KERNEL_SOURCES
kernel/Errors.cpp kernel/Errors.cpp
kernel/Font.cpp kernel/Font.cpp
kernel/FS/Ext2.cpp kernel/FS/Ext2.cpp
kernel/FS/Inode.cpp
kernel/FS/VirtualFileSystem.cpp kernel/FS/VirtualFileSystem.cpp
kernel/Input/PS2Controller.cpp kernel/Input/PS2Controller.cpp
kernel/Input/PS2Keyboard.cpp kernel/Input/PS2Keyboard.cpp

View File

@ -0,0 +1,40 @@
#pragma once
#include <sys/types.h>
namespace Kernel
{
class Credentials
{
public:
Credentials(uid_t ruid, uid_t euid, gid_t rgid, gid_t egid)
: m_ruid(ruid), m_euid(euid), m_suid(0)
, m_rgid(rgid), m_egid(egid), m_sgid(0)
{ }
uid_t ruid() const { return m_ruid; }
uid_t euid() const { return m_euid; }
uid_t suid() const { return m_suid; }
gid_t rgid() const { return m_rgid; }
gid_t egid() const { return m_egid; }
gid_t sgid() const { return m_sgid; }
private:
uid_t m_ruid, m_euid, m_suid;
gid_t m_rgid, m_egid, m_sgid;
};
}
namespace BAN::Formatter
{
template<typename F>
void print_argument(F putc, const Kernel::Credentials& credentials, const ValueFormat&)
{
print(putc, "(ruid {}, euid {})", credentials.ruid(), credentials.euid());
}
}

View File

@ -6,6 +6,7 @@
#include <BAN/Vector.h> #include <BAN/Vector.h>
#include <kernel/API/DirectoryEntry.h> #include <kernel/API/DirectoryEntry.h>
#include <kernel/Credentials.h>
#include <sys/types.h> #include <sys/types.h>
#include <time.h> #include <time.h>
@ -56,6 +57,8 @@ namespace Kernel
public: public:
virtual ~Inode() {} virtual ~Inode() {}
bool can_access(const Credentials&, int);
bool operator==(const Inode& other) const { return dev() == other.dev() && rdev() == other.rdev() && ino() == other.ino(); } bool operator==(const Inode& other) const { return dev() == other.dev() && rdev() == other.rdev() && ino() == other.ino(); }
virtual ino_t ino() const = 0; virtual ino_t ino() const = 0;

View File

@ -11,21 +11,21 @@ namespace Kernel
class VirtualFileSystem : public FileSystem class VirtualFileSystem : public FileSystem
{ {
public: public:
static BAN::ErrorOr<void> initialize(BAN::StringView); static void initialize(BAN::StringView);
static VirtualFileSystem& get(); static VirtualFileSystem& get();
virtual ~VirtualFileSystem() {}; virtual ~VirtualFileSystem() {};
virtual BAN::RefPtr<Inode> root_inode() override { return m_root_fs->root_inode(); } virtual BAN::RefPtr<Inode> root_inode() override { return m_root_fs->root_inode(); }
BAN::ErrorOr<void> mount(BAN::StringView, BAN::StringView); BAN::ErrorOr<void> mount(const Credentials&, BAN::StringView, BAN::StringView);
BAN::ErrorOr<void> mount(FileSystem*, BAN::StringView); BAN::ErrorOr<void> mount(const Credentials&, FileSystem*, BAN::StringView);
struct File struct File
{ {
BAN::RefPtr<Inode> inode; BAN::RefPtr<Inode> inode;
BAN::String canonical_path; BAN::String canonical_path;
}; };
BAN::ErrorOr<File> file_from_absolute_path(BAN::StringView, bool follow_link); BAN::ErrorOr<File> file_from_absolute_path(const Credentials&, BAN::StringView, int);
private: private:
VirtualFileSystem() = default; VirtualFileSystem() = default;

View File

@ -3,6 +3,7 @@
#include <BAN/String.h> #include <BAN/String.h>
#include <BAN/StringView.h> #include <BAN/StringView.h>
#include <BAN/Vector.h> #include <BAN/Vector.h>
#include <kernel/Credentials.h>
#include <kernel/FS/Inode.h> #include <kernel/FS/Inode.h>
#include <kernel/Memory/FixedWidthAllocator.h> #include <kernel/Memory/FixedWidthAllocator.h>
#include <kernel/Memory/GeneralAllocator.h> #include <kernel/Memory/GeneralAllocator.h>
@ -37,7 +38,7 @@ namespace Kernel
public: public:
static Process* create_kernel(entry_t, void*); static Process* create_kernel(entry_t, void*);
static BAN::ErrorOr<Process*> create_userspace(BAN::StringView); static BAN::ErrorOr<Process*> create_userspace(const Credentials&, BAN::StringView);
~Process(); ~Process();
[[noreturn]] void exit(int status); [[noreturn]] void exit(int status);
@ -91,12 +92,12 @@ namespace Kernel
const userspace_info_t& userspace_info() const { return m_userspace_info; } const userspace_info_t& userspace_info() const { return m_userspace_info; }
private: private:
Process(pid_t); Process(const Credentials&, pid_t);
static Process* create_process(); static Process* create_process(const Credentials&);
static void register_process(Process*); static void register_process(Process*);
// Load an elf file to virtual address space of the current page table // Load an elf file to virtual address space of the current page table
static BAN::ErrorOr<BAN::UniqPtr<LibELF::ELF>> load_elf_for_exec(BAN::StringView file_path, const BAN::String& cwd, const BAN::Vector<BAN::StringView>& path_env); static BAN::ErrorOr<BAN::UniqPtr<LibELF::ELF>> load_elf_for_exec(const Credentials&, BAN::StringView file_path, const BAN::String& cwd, const BAN::Vector<BAN::StringView>& path_env);
// Copy an elf file from the current page table to the processes own // Copy an elf file from the current page table to the processes own
void load_elf_to_memory(LibELF::ELF&); void load_elf_to_memory(LibELF::ELF&);
@ -125,6 +126,8 @@ namespace Kernel
int waiting { 0 }; int waiting { 0 };
}; };
Credentials m_credentials;
BAN::Vector<OpenFileDescription> m_open_files; BAN::Vector<OpenFileDescription> m_open_files;
BAN::Vector<VirtualRange*> m_mapped_ranges; BAN::Vector<VirtualRange*> m_mapped_ranges;

View File

@ -108,7 +108,8 @@ namespace Kernel
TerminalDriver* m_terminal_driver { nullptr }; TerminalDriver* m_terminal_driver { nullptr };
public: public:
virtual Mode mode() const override { return { Mode::IFCHR | Mode::IRUSR }; } // FIXME: these should be crw------- with the owner being user
virtual Mode mode() const override { return { Mode::IFCHR | Mode::IRUSR | Mode::IWUSR | Mode::IRGRP | Mode::IWGRP | Mode::IROTH | Mode::IWOTH }; }
virtual uid_t uid() const override { return 0; } virtual uid_t uid() const override { return 0; }
virtual gid_t gid() const override { return 0; } virtual gid_t gid() const override { return 0; }
virtual dev_t rdev() const override { return m_rdev; } virtual dev_t rdev() const override { return m_rdev; }

View File

@ -0,0 +1,59 @@
#include <kernel/FS/Inode.h>
#include <fcntl.h>
namespace Kernel
{
bool Inode::can_access(const Credentials& credentials, int flags)
{
if (credentials.euid() == 0)
return true;
// We treat O_SEARCH as O_RDONLY
if (flags & (O_RDONLY | O_SEARCH))
{
if (mode().mode & S_IROTH)
{ }
else if ((mode().mode & S_IRUSR) && credentials.euid() == uid())
{ }
else if ((mode().mode & S_IRGRP) && credentials.egid() == gid())
{ }
else
{
return false;
}
}
if (flags & O_WRONLY)
{
if (mode().mode & S_IWOTH)
{ }
else if ((mode().mode & S_IWUSR) && credentials.euid() == uid())
{ }
else if ((mode().mode & S_IWGRP) && credentials.egid() == gid())
{ }
else
{
return false;
}
}
if (flags & O_EXEC)
{
if (mode().mode & S_IXOTH)
{ }
else if ((mode().mode & S_IXUSR) && credentials.euid() == uid())
{ }
else if ((mode().mode & S_IXGRP) && credentials.egid() == gid())
{ }
else
{
return false;
}
}
return true;
}
}

View File

@ -4,32 +4,27 @@
#include <kernel/FS/Ext2.h> #include <kernel/FS/Ext2.h>
#include <kernel/FS/VirtualFileSystem.h> #include <kernel/FS/VirtualFileSystem.h>
#include <kernel/LockGuard.h> #include <kernel/LockGuard.h>
#include <fcntl.h>
namespace Kernel namespace Kernel
{ {
static VirtualFileSystem* s_instance = nullptr; static VirtualFileSystem* s_instance = nullptr;
BAN::ErrorOr<void> VirtualFileSystem::initialize(BAN::StringView root) void VirtualFileSystem::initialize(BAN::StringView root)
{ {
ASSERT(s_instance == nullptr); ASSERT(s_instance == nullptr);
s_instance = new VirtualFileSystem(); s_instance = new VirtualFileSystem();
if (s_instance == nullptr) ASSERT(s_instance);
return BAN::Error::from_errno(ENOMEM);
BAN::ScopeGuard guard([] { delete s_instance; s_instance = nullptr; } );
ASSERT(root.size() >= 5 && root.substring(0, 5) == "/dev/"sv);; ASSERT(root.size() >= 5 && root.substring(0, 5) == "/dev/"sv);;
root = root.substring(5); root = root.substring(5);
auto partition_inode = TRY(DeviceManager::get().read_directory_inode(root)); auto partition_inode = MUST(DeviceManager::get().read_directory_inode(root));
s_instance->m_root_fs = TRY(Ext2FS::create(*(Partition*)partition_inode.ptr())); s_instance->m_root_fs = MUST(Ext2FS::create(*(Partition*)partition_inode.ptr()));
DeviceManager::get().set_blksize(s_instance->m_root_fs->root_inode()->blksize()); DeviceManager::get().set_blksize(s_instance->m_root_fs->root_inode()->blksize());
TRY(s_instance->mount(&DeviceManager::get(), "/dev")); MUST(s_instance->mount({ 0, 0, 0, 0 }, &DeviceManager::get(), "/dev"));
guard.disable();
return {};
} }
VirtualFileSystem& VirtualFileSystem::get() VirtualFileSystem& VirtualFileSystem::get()
@ -38,9 +33,9 @@ namespace Kernel
return *s_instance; return *s_instance;
} }
BAN::ErrorOr<void> VirtualFileSystem::mount(BAN::StringView partition, BAN::StringView target) BAN::ErrorOr<void> VirtualFileSystem::mount(const Credentials& credentials, BAN::StringView partition, BAN::StringView target)
{ {
auto partition_file = TRY(file_from_absolute_path(partition, true)); auto partition_file = TRY(file_from_absolute_path(credentials, partition, true));
if (!partition_file.inode->is_device()) if (!partition_file.inode->is_device())
return BAN::Error::from_errno(ENOTBLK); return BAN::Error::from_errno(ENOTBLK);
@ -49,12 +44,12 @@ namespace Kernel
return BAN::Error::from_errno(ENOTBLK); return BAN::Error::from_errno(ENOTBLK);
auto* file_system = TRY(Ext2FS::create(*(Partition*)device)); auto* file_system = TRY(Ext2FS::create(*(Partition*)device));
return mount(file_system, target); return mount(credentials, file_system, target);
} }
BAN::ErrorOr<void> VirtualFileSystem::mount(FileSystem* file_system, BAN::StringView path) BAN::ErrorOr<void> VirtualFileSystem::mount(const Credentials& credentials, FileSystem* file_system, BAN::StringView path)
{ {
auto file = TRY(file_from_absolute_path(path, true)); auto file = TRY(file_from_absolute_path(credentials, path, true));
if (!file.inode->mode().ifdir()) if (!file.inode->mode().ifdir())
return BAN::Error::from_errno(ENOTDIR); return BAN::Error::from_errno(ENOTDIR);
@ -82,7 +77,7 @@ namespace Kernel
return nullptr; return nullptr;
} }
BAN::ErrorOr<VirtualFileSystem::File> VirtualFileSystem::file_from_absolute_path(BAN::StringView path, bool follow_link) BAN::ErrorOr<VirtualFileSystem::File> VirtualFileSystem::file_from_absolute_path(const Credentials& credentials, BAN::StringView path, int flags)
{ {
LockGuard _(m_lock); LockGuard _(m_lock);
@ -129,6 +124,9 @@ namespace Kernel
} }
else else
{ {
if (!inode->can_access(credentials, O_RDONLY))
return BAN::Error::from_errno(EACCES);
inode = TRY(inode->read_directory_inode(path_part)); inode = TRY(inode->read_directory_inode(path_part));
if (auto* mount_point = mount_from_host_inode(inode)) if (auto* mount_point = mount_from_host_inode(inode))
@ -140,7 +138,7 @@ namespace Kernel
path_parts.pop_back(); path_parts.pop_back();
if (inode->mode().iflnk() && (follow_link || !path_parts.empty())) if (inode->mode().iflnk() && (!(flags & O_NOFOLLOW) || !path_parts.empty()))
{ {
auto target = TRY(inode->link_target()); auto target = TRY(inode->link_target());
if (target.empty()) if (target.empty())
@ -174,6 +172,9 @@ namespace Kernel
} }
} }
if (!inode->can_access(credentials, flags))
return BAN::Error::from_errno(EACCES);
if (canonical_path.empty()) if (canonical_path.empty())
TRY(canonical_path.push_back('/')); TRY(canonical_path.push_back('/'));

View File

@ -18,10 +18,10 @@ namespace Kernel
static BAN::Vector<Process*> s_processes; static BAN::Vector<Process*> s_processes;
static SpinLock s_process_lock; static SpinLock s_process_lock;
Process* Process::create_process() Process* Process::create_process(const Credentials& credentials)
{ {
static pid_t s_next_pid = 1; static pid_t s_next_pid = 1;
auto* process = new Process(s_next_pid++); auto* process = new Process(credentials, s_next_pid++);
ASSERT(process); ASSERT(process);
return process; return process;
} }
@ -37,7 +37,7 @@ namespace Kernel
Process* Process::create_kernel(entry_t entry, void* data) Process* Process::create_kernel(entry_t entry, void* data)
{ {
auto* process = create_process(); auto* process = create_process({ 0, 0, 0, 0 });
MUST(process->m_working_directory.push_back('/')); MUST(process->m_working_directory.push_back('/'));
auto* thread = MUST(Thread::create_kernel(entry, data, process)); auto* thread = MUST(Thread::create_kernel(entry, data, process));
process->add_thread(thread); process->add_thread(thread);
@ -45,11 +45,11 @@ namespace Kernel
return process; return process;
} }
BAN::ErrorOr<Process*> Process::create_userspace(BAN::StringView path) BAN::ErrorOr<Process*> Process::create_userspace(const Credentials& credentials, BAN::StringView path)
{ {
auto elf = TRY(load_elf_for_exec(path, "/"sv, {})); auto elf = TRY(load_elf_for_exec(credentials, path, "/"sv, {}));
auto* process = create_process(); auto* process = create_process(credentials);
MUST(process->m_working_directory.push_back('/')); MUST(process->m_working_directory.push_back('/'));
process->m_page_table = BAN::UniqPtr<PageTable>::adopt(MUST(PageTable::create_userspace()));; process->m_page_table = BAN::UniqPtr<PageTable>::adopt(MUST(PageTable::create_userspace()));;
@ -89,8 +89,9 @@ namespace Kernel
return process; return process;
} }
Process::Process(pid_t pid) Process::Process(const Credentials& credentials, pid_t pid)
: m_pid(pid) : m_credentials(credentials)
, m_pid(pid)
, m_tty(TTY::current()) , m_tty(TTY::current())
{ } { }
@ -167,7 +168,7 @@ namespace Kernel
return {}; return {};
} }
BAN::ErrorOr<BAN::UniqPtr<LibELF::ELF>> Process::load_elf_for_exec(BAN::StringView file_path, const BAN::String& cwd, const BAN::Vector<BAN::StringView>& path_env) BAN::ErrorOr<BAN::UniqPtr<LibELF::ELF>> Process::load_elf_for_exec(const Credentials& credentials, BAN::StringView file_path, const BAN::String& cwd, const BAN::Vector<BAN::StringView>& path_env)
{ {
if (file_path.empty()) if (file_path.empty())
return BAN::Error::from_errno(ENOENT); return BAN::Error::from_errno(ENOENT);
@ -204,7 +205,7 @@ namespace Kernel
TRY(absolute_path.push_back('/')); TRY(absolute_path.push_back('/'));
TRY(absolute_path.append(file_path)); TRY(absolute_path.append(file_path));
if (!VirtualFileSystem::get().file_from_absolute_path(absolute_path, true).is_error()) if (!VirtualFileSystem::get().file_from_absolute_path(credentials, absolute_path, O_EXEC).is_error())
break; break;
absolute_path.clear(); absolute_path.clear();
@ -214,7 +215,9 @@ namespace Kernel
return BAN::Error::from_errno(ENOENT); return BAN::Error::from_errno(ENOENT);
} }
auto elf_or_error = LibELF::ELF::load_from_file(absolute_path); auto file = TRY(VirtualFileSystem::get().file_from_absolute_path(credentials, absolute_path, O_EXEC));
auto elf_or_error = LibELF::ELF::load_from_file(file.inode);
if (elf_or_error.is_error()) if (elf_or_error.is_error())
{ {
if (elf_or_error.error().get_error_code() == EINVAL) if (elf_or_error.error().get_error_code() == EINVAL)
@ -240,7 +243,7 @@ namespace Kernel
BAN::ErrorOr<Process*> Process::fork(uintptr_t rsp, uintptr_t rip) BAN::ErrorOr<Process*> Process::fork(uintptr_t rsp, uintptr_t rip)
{ {
Process* forked = create_process(); Process* forked = create_process(m_credentials);
forked->m_page_table = BAN::UniqPtr<PageTable>::adopt(MUST(PageTable::create_userspace())); forked->m_page_table = BAN::UniqPtr<PageTable>::adopt(MUST(PageTable::create_userspace()));
@ -286,7 +289,7 @@ namespace Kernel
path_env = TRY(BAN::StringView(envp[i]).substring(5).split(':')); path_env = TRY(BAN::StringView(envp[i]).substring(5).split(':'));
} }
auto elf = TRY(load_elf_for_exec(path, TRY(working_directory()), path_env)); auto elf = TRY(load_elf_for_exec(m_credentials, path, TRY(working_directory()), path_env));
LockGuard lock_guard(m_lock); LockGuard lock_guard(m_lock);
@ -456,7 +459,7 @@ namespace Kernel
BAN::String absolute_path = TRY(absolute_path_of(path)); BAN::String absolute_path = TRY(absolute_path_of(path));
auto file = TRY(VirtualFileSystem::get().file_from_absolute_path(absolute_path, !(flags & O_NOFOLLOW))); auto file = TRY(VirtualFileSystem::get().file_from_absolute_path(m_credentials, absolute_path, flags));
LockGuard _(m_lock); LockGuard _(m_lock);
int fd = TRY(get_free_fd()); int fd = TRY(get_free_fd());
@ -592,7 +595,7 @@ namespace Kernel
auto directory = absolute_path.sv().substring(0, index); auto directory = absolute_path.sv().substring(0, index);
auto file_name = absolute_path.sv().substring(index); auto file_name = absolute_path.sv().substring(index);
auto parent_file = TRY(VirtualFileSystem::get().file_from_absolute_path(directory, true)); auto parent_file = TRY(VirtualFileSystem::get().file_from_absolute_path(m_credentials, directory, O_WRONLY));
TRY(parent_file.inode->create_file(file_name, mode)); TRY(parent_file.inode->create_file(file_name, mode));
return {}; return {};
@ -606,7 +609,7 @@ namespace Kernel
absolute_source = TRY(absolute_path_of(source)); absolute_source = TRY(absolute_path_of(source));
absolute_target = TRY(absolute_path_of(target)); absolute_target = TRY(absolute_path_of(target));
} }
TRY(VirtualFileSystem::get().mount(absolute_source, absolute_target)); TRY(VirtualFileSystem::get().mount(m_credentials, absolute_source, absolute_target));
return {}; return {};
} }
@ -680,7 +683,7 @@ namespace Kernel
{ {
BAN::String absolute_path = TRY(absolute_path_of(path)); BAN::String absolute_path = TRY(absolute_path_of(path));
auto file = TRY(VirtualFileSystem::get().file_from_absolute_path(absolute_path, true)); auto file = TRY(VirtualFileSystem::get().file_from_absolute_path(m_credentials, absolute_path, O_SEARCH));
if (!file.inode->mode().ifdir()) if (!file.inode->mode().ifdir())
return BAN::Error::from_errno(ENOTDIR); return BAN::Error::from_errno(ENOTDIR);

View File

@ -173,14 +173,14 @@ static void init2(void* tty1)
DeviceManager::get().initialize_pci_devices(); DeviceManager::get().initialize_pci_devices();
DeviceManager::get().initialize_updater(); DeviceManager::get().initialize_updater();
MUST(VirtualFileSystem::initialize(cmdline.root)); VirtualFileSystem::initialize(cmdline.root);
if (auto res = PS2Controller::initialize(); res.is_error()) if (auto res = PS2Controller::initialize(); res.is_error())
dprintln("{}", res.error()); dprintln("{}", res.error());
((TTY*)tty1)->initialize_device(); ((TTY*)tty1)->initialize_device();
MUST(Process::create_userspace("/usr/bin/Shell"sv)); MUST(Process::create_userspace({ 0, 0, 0, 0 }, "/usr/bin/Shell"sv));
return; return;
Process::create_kernel( Process::create_kernel(

View File

@ -58,7 +58,7 @@ void list_directory(const char* path)
} }
else else
{ {
printf("%s %d %s", mode_string(st.st_mode), st.st_size, dirent->d_name); printf("%s %6d %s", mode_string(st.st_mode), st.st_size, dirent->d_name);
} }
} }
else else