feat: real stat metadata across all filesystems, plus mkdir and rmdir
This commit is contained in:
1 parent
ca5331c9db
commit
edc3452d61
14 files changed
+557
-25
No files matched your search
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* mkdir - command to create directories
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
|
||||
// The kernel resolves paths into a 256-byte buffer, so anything longer is
|
||||
// rejected up front rather than acted on after silent truncation.
|
||||
static constexpr int MaxPath = 256;
|
||||
|
||||
static int g_failures = 0;
|
||||
|
||||
static void report(const char* path, const char* reason) {
|
||||
montauk::print("mkdir: ");
|
||||
montauk::print(path);
|
||||
montauk::print(": ");
|
||||
montauk::print(reason);
|
||||
montauk::putchar('\n');
|
||||
g_failures++;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
montauk::print("usage: mkdir [-p] <directory> [directory ...]\n");
|
||||
montauk::print(" -p create missing parent directories, and succeed\n");
|
||||
montauk::print(" when the directory already exists\n");
|
||||
}
|
||||
|
||||
// Copy the next space-delimited token out of *p, advancing *p past it.
|
||||
// Returns false once the input is exhausted.
|
||||
static bool next_token(const char** p, char* out, int outMax) {
|
||||
const char* s = montauk::skip_spaces(*p);
|
||||
if (*s == '\0') {
|
||||
*p = s;
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
while (*s != '\0' && *s != ' ') {
|
||||
if (n < outMax - 1) out[n++] = *s;
|
||||
s++;
|
||||
}
|
||||
out[n] = '\0';
|
||||
*p = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Length of a leading drive prefix ("0:", "12:"), or 0 when there is none.
|
||||
// The prefix names the root, which always exists and is never created.
|
||||
static int drive_prefix_len(const char* s) {
|
||||
int i = 0;
|
||||
while (s[i] >= '0' && s[i] <= '9') i++;
|
||||
return (i > 0 && s[i] == ':') ? i + 1 : 0;
|
||||
}
|
||||
|
||||
static bool path_exists(const char* path, bool& isDir) {
|
||||
montauk::abi::FileStat st;
|
||||
if (montauk::stat(path, &st) < 0) return false;
|
||||
isDir = st.isDir != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create every missing component of the path. fmkdir already succeeds on a
|
||||
// directory that exists, so each prefix can be created unconditionally; it
|
||||
// fails only when a component is in the way as a regular file.
|
||||
static bool make_with_parents(const char* path) {
|
||||
char partial[MaxPath];
|
||||
|
||||
int i = drive_prefix_len(path);
|
||||
// Leading slashes belong to the root, not to any component.
|
||||
while (path[i] == '/') i++;
|
||||
for (int k = 0; k < i; k++) partial[k] = path[k];
|
||||
int n = i;
|
||||
|
||||
while (path[i] != '\0') {
|
||||
while (path[i] != '\0' && path[i] != '/') partial[n++] = path[i++];
|
||||
partial[n] = '\0';
|
||||
|
||||
// Collapse repeated separators; a trailing slash ends the path.
|
||||
while (path[i] == '/') i++;
|
||||
|
||||
if (montauk::fmkdir(partial) < 0) {
|
||||
report(partial, "cannot create directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path[i] != '\0') partial[n++] = '/';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void make_dir(const char* path, bool parents) {
|
||||
if (montauk::slen(path) >= MaxPath) {
|
||||
report(path, "path too long");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isDir = false;
|
||||
if (path_exists(path, isDir)) {
|
||||
// -p treats an existing directory as success, but never a file.
|
||||
if (parents && isDir) return;
|
||||
report(path, isDir ? "directory already exists" : "file already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
if (parents) {
|
||||
make_with_parents(path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (montauk::fmkdir(path) < 0) {
|
||||
report(path, "cannot create directory");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void _start() {
|
||||
char args[1024];
|
||||
montauk::getargs(args, sizeof(args));
|
||||
|
||||
const char* p = args;
|
||||
// Sized past MaxPath so an over-long operand survives tokenizing intact
|
||||
// and is caught by the length check instead of being truncated into a
|
||||
// different, possibly valid, path.
|
||||
char tok[MaxPath + 2];
|
||||
bool parents = false;
|
||||
int operands = 0;
|
||||
|
||||
while (next_token(&p, tok, sizeof(tok))) {
|
||||
// Options are recognized only before the first operand, so a
|
||||
// directory named "-p" stays reachable once one has been given.
|
||||
if (operands == 0 && tok[0] == '-' && tok[1] != '\0') {
|
||||
if (montauk::streq(tok, "-p")) {
|
||||
parents = true;
|
||||
continue;
|
||||
}
|
||||
montauk::print("mkdir: unknown option: ");
|
||||
montauk::print(tok);
|
||||
montauk::putchar('\n');
|
||||
usage();
|
||||
montauk::exit(2);
|
||||
}
|
||||
|
||||
operands++;
|
||||
make_dir(tok, parents);
|
||||
}
|
||||
|
||||
if (operands == 0) {
|
||||
usage();
|
||||
montauk::exit(2);
|
||||
}
|
||||
|
||||
montauk::exit(g_failures > 0 ? 1 : 0);
|
||||
}
|
||||
Reference in new issue
Block a user