mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
216 lines
6.4 KiB
C++
216 lines
6.4 KiB
C++
#include <coreutils/command_support.h>
|
|
|
|
#include <app/dir.h>
|
|
#include <app/io.h>
|
|
|
|
#include <tactility/memory.h>
|
|
#include <tactility/paths.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
namespace {
|
|
|
|
// Reads the calling app instance's own cwd (app/dir.h) - inherited from whichever instance
|
|
// started this one (e.g. the interactive shell, after a `cd`), so this always agrees with `pwd`.
|
|
// "/" if unset/unavailable (matches this module's old hardcoded default).
|
|
// The buffer is allocated per call, in external memory when available. Apps have small stacks,
|
|
// and a shared static buffer would be overwritten by other apps that run at the same time.
|
|
struct MemoryDeleter {
|
|
void operator()(char* memory) const { memory_free(memory); }
|
|
};
|
|
|
|
using DirectoryBuffer = std::unique_ptr<char[], MemoryDeleter>;
|
|
|
|
const char* currentDirectory(DirectoryBuffer& buffer) {
|
|
constexpr MemoryPolicy policy = { .required = 0, .desired = MEMORY_CAPABILITY_EXTERNAL, .alignment = 0 };
|
|
buffer.reset(static_cast<char*>(memory_alloc_with_policy(FILE_MAX_PATH_STRING_LENGTH, &policy)));
|
|
if (buffer == nullptr || app_dir_get_cwd(buffer.get(), FILE_MAX_PATH_STRING_LENGTH) != ERROR_NONE) {
|
|
return "/";
|
|
}
|
|
return buffer.get();
|
|
}
|
|
|
|
/**
|
|
* Collapses `.` and `..` segments in an absolute path, in place.
|
|
*
|
|
* Scanned manually rather than with strtok: strtok keeps static state across calls and is not
|
|
* reentrant, which is the wrong shape for something more than one coreutil app might call.
|
|
*/
|
|
void normalizePath(char* path) {
|
|
struct Segment {
|
|
const char* start;
|
|
size_t length;
|
|
};
|
|
Segment segments[32];
|
|
int depth = 0;
|
|
|
|
const char* p = path;
|
|
while (*p != '\0') {
|
|
while (*p == '/') {
|
|
p++;
|
|
}
|
|
if (*p == '\0') {
|
|
break;
|
|
}
|
|
|
|
const char* start = p;
|
|
while (*p != '\0' && *p != '/') {
|
|
p++;
|
|
}
|
|
const size_t length = static_cast<size_t>(p - start);
|
|
|
|
if (length == 1 && start[0] == '.') {
|
|
continue;
|
|
}
|
|
if (length == 2 && start[0] == '.' && start[1] == '.') {
|
|
if (depth > 0) {
|
|
depth--;
|
|
}
|
|
continue;
|
|
}
|
|
if (depth < static_cast<int>(sizeof(segments) / sizeof(segments[0]))) {
|
|
segments[depth++] = Segment { start, length };
|
|
}
|
|
}
|
|
|
|
// Rebuild into a scratch buffer first: the segments still point into `path`.
|
|
char rebuilt[FILE_MAX_PATH_STRING_LENGTH];
|
|
size_t offset = 0;
|
|
for (int i = 0; i < depth && offset + 1 < sizeof(rebuilt); i++) {
|
|
rebuilt[offset++] = '/';
|
|
size_t copy = segments[i].length;
|
|
if (offset + copy >= sizeof(rebuilt)) {
|
|
copy = sizeof(rebuilt) - offset - 1;
|
|
}
|
|
memcpy(&rebuilt[offset], segments[i].start, copy);
|
|
offset += copy;
|
|
}
|
|
if (offset == 0) {
|
|
rebuilt[offset++] = '/';
|
|
}
|
|
rebuilt[offset] = '\0';
|
|
|
|
memcpy(path, rebuilt, offset + 1);
|
|
}
|
|
|
|
// Module-local equivalent of ShellFs::resolvePath(): resolves against this app instance's own
|
|
// cwd (currentDirectory(), above) - inherited from whichever instance started it.
|
|
bool resolvePath(const char* path, char* out, size_t outSize) {
|
|
DirectoryBuffer currentBuffer;
|
|
const char* current = currentDirectory(currentBuffer);
|
|
|
|
if (path == nullptr || path[0] == '\0') {
|
|
snprintf(out, outSize, "%s", current);
|
|
return true;
|
|
}
|
|
|
|
int written;
|
|
if (path[0] == '/') {
|
|
written = snprintf(out, outSize, "%s", path);
|
|
} else if (strcmp(current, "/") == 0) {
|
|
written = snprintf(out, outSize, "/%s", path);
|
|
} else {
|
|
written = snprintf(out, outSize, "%s/%s", current, path);
|
|
}
|
|
|
|
if (written < 0 || static_cast<size_t>(written) >= outSize) {
|
|
return false;
|
|
}
|
|
|
|
normalizePath(out);
|
|
|
|
// Trailing slashes confuse stat() on some filesystems; drop all but a lone root.
|
|
size_t length = strlen(out);
|
|
while (length > 1 && out[length - 1] == '/') {
|
|
out[--length] = '\0';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool isRoot(const char* resolved) {
|
|
return resolved != nullptr && strcmp(resolved, "/") == 0;
|
|
}
|
|
|
|
bool resolvePathArg(const char* command, const char* path, char* out, size_t outSize) {
|
|
if (!resolvePath(path, out, outSize)) {
|
|
printf("%s: %s: path too long\n", command, path);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int reportResult(const char* command, const char* subject, error_t result) {
|
|
if (result == ERROR_NONE) {
|
|
return 0;
|
|
}
|
|
printf("%s: %s: %s\n", command, subject, error_to_string(result));
|
|
return 1;
|
|
}
|
|
|
|
bool stdoutIsTerminal() {
|
|
AppWindowSize size {};
|
|
return app_io_ioctl(STDOUT_FILENO, APP_IOCTL_GET_WINDOW_SIZE, &size) == ERROR_NONE;
|
|
}
|
|
|
|
const char* const COLOUR_RESET = "\x1B[0m";
|
|
const char* const COLOUR_DIR = "\x1B[94m"; // bright blue
|
|
const char* const COLOUR_EXEC = "\x1B[92m"; // bright green
|
|
const char* const COLOUR_SIZE = "\x1B[90m"; // grey, so it recedes behind the names
|
|
const char* const COLOUR_ERROR = "\x1B[91m"; // bright red
|
|
|
|
const char* color(const char* sequence) {
|
|
return stdoutIsTerminal() ? sequence : "";
|
|
}
|
|
|
|
bool buildTarget(const char* sourcePath, const char* targetArg, char* out, size_t outSize) {
|
|
char resolvedTarget[FILE_MAX_PATH_STRING_LENGTH];
|
|
if (!resolvePath(targetArg, resolvedTarget, sizeof(resolvedTarget))) {
|
|
return false;
|
|
}
|
|
|
|
if (!directory_exists(resolvedTarget)) {
|
|
snprintf(out, outSize, "%s", resolvedTarget);
|
|
return true;
|
|
}
|
|
|
|
const char* base = strrchr(sourcePath, '/');
|
|
base = (base != nullptr) ? base + 1 : sourcePath;
|
|
|
|
const int written = snprintf(out, outSize, "%s/%s", resolvedTarget, base);
|
|
return written > 0 && static_cast<size_t>(written) < outSize;
|
|
}
|
|
|
|
int parseLineCount(int argc, char** argv, int* outCount) {
|
|
if (argc > 2 && strcmp(argv[1], "-n") == 0) {
|
|
*outCount = atoi(argv[2]);
|
|
return 3;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
bool streamFile(const char* resolvedPath, void* context, bool (*callback)(const char*, size_t, void*)) {
|
|
FILE* file = fopen(resolvedPath, "rb");
|
|
if (file == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
char chunk[512];
|
|
for (;;) {
|
|
const size_t read = fread(chunk, 1, sizeof(chunk), file);
|
|
if (read == 0) {
|
|
break;
|
|
}
|
|
if (!callback(chunk, read, context)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(file);
|
|
return true;
|
|
}
|