summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Murphy <tylerm@tylerm.dev>2023-05-03 12:17:56 -0400
committerTyler Murphy <tylerm@tylerm.dev>2023-05-03 12:17:56 -0400
commitf2606d0875dbaadb3f414d98d8f37fdbdf6036ea (patch)
tree5319a70aaba6cb57d554e7e7b0651908db4ec20a
parentfix ls segfault (diff)
downloadlazysphere-f2606d0875dbaadb3f414d98d8f37fdbdf6036ea.tar.gz
lazysphere-f2606d0875dbaadb3f414d98d8f37fdbdf6036ea.tar.bz2
lazysphere-f2606d0875dbaadb3f414d98d8f37fdbdf6036ea.zip
add rm, cp, mkdir, mv
-rw-r--r--Makefile1
-rw-r--r--readme.md2
-rw-r--r--src/command.h4
-rw-r--r--src/commands/cat.c2
-rw-r--r--src/commands/cp.c232
-rw-r--r--src/commands/dd.c6
-rw-r--r--src/commands/ed.c64
-rw-r--r--src/commands/groups.c4
-rw-r--r--src/commands/head.c9
-rw-r--r--src/commands/id.c4
-rw-r--r--src/commands/ls.c20
-rw-r--r--src/commands/mkdir.c60
-rw-r--r--src/commands/mv.c108
-rw-r--r--src/commands/printf.c3
-rw-r--r--src/commands/rm.c132
-rw-r--r--src/commands/tail.c11
-rw-r--r--src/commands/tee.c2
-rw-r--r--src/commands/wc.c2
-rw-r--r--src/commands/xargs.c4
-rw-r--r--src/main.c21
-rw-r--r--src/util/shared.c106
-rw-r--r--src/util/shared.h14
22 files changed, 718 insertions, 93 deletions
diff --git a/Makefile b/Makefile
index ba4984d..708443e 100644
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@ CCFLAGS = -std=c99 -Wall -Wextra -pedantic -O2
CCFLAGS += -D_DEFAULT_SOURCE -DMAJOR=$(MAJOR) -DMINOR=$(MINOR) -DPATCH=$(PATCH) -DCHECK_LINK
CCFLAGS += $(INCFLAGS)
+LDFLAGS = -s
LDFLAGS += $(INCFLAGS)
BIN = bin
diff --git a/readme.md b/readme.md
index a594b66..05eea07 100644
--- a/readme.md
+++ b/readme.md
@@ -4,7 +4,7 @@
A terrible busybox/gnu coreutils clone.
Currently the only supported commands are:
-`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head`, `ed`, `tee`, `true`, `false`, `tee`, `whoami`, `wc`, `xargs`, `tac`
+`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head`, `ed`, `tee`, `true`, `false`, `tee`, `whoami`, `wc`, `xargs`, `tac`, `rm`, `cp`, `mkdir`, `mv`
## How to
diff --git a/src/command.h b/src/command.h
index a73e3c8..2e468dd 100644
--- a/src/command.h
+++ b/src/command.h
@@ -33,3 +33,7 @@ COMMAND(whoami);
COMMAND(wc);
COMMAND(xargs);
COMMAND(tac);
+COMMAND(rm);
+COMMAND(cp);
+COMMAND(makedir);
+COMMAND(mv);
diff --git a/src/commands/cat.c b/src/commands/cat.c
index 6ed77e8..8c15522 100644
--- a/src/commands/cat.c
+++ b/src/commands/cat.c
@@ -103,7 +103,7 @@ static int short_arg(char c, char* next) {
flags.end_lines_dollar = true;
break;
default:
- error("error: unkown flag -%c", c);
+ return ARG_INVALID;
}
return ARG_UNUSED;
}
diff --git a/src/commands/cp.c b/src/commands/cp.c
new file mode 100644
index 0000000..37e3354
--- /dev/null
+++ b/src/commands/cp.c
@@ -0,0 +1,232 @@
+#include "../command.h"
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static struct {
+ bool recurse;
+ bool preserve;
+ bool sym_link;
+ bool hard_link;
+ bool verbose;
+} flags;
+
+static void help(void) {
+ printf("Usage: cp [-rplsv] SOURCE... DEST\n\n");
+ printf("Copy SOURCEs to DEST\n\n");
+ printf("\t-R,-r\tRecurse\n");
+ printf("\t-p\tPreserve file attributes if possible\n");
+ printf("\t-l,-s\tCreate (sym)links\n");
+ printf("\t-v\tVerbose\n");
+}
+
+static int short_arg(char c, char* next) {
+ UNUSED(next);
+ switch (c) {
+ case 'R':
+ case 'r':
+ flags.recurse = true;
+ break;
+ case 'p':
+ flags.preserve = true;
+ break;
+ case 'l':
+ flags.hard_link = true;
+ flags.sym_link = false;
+ break;
+ case 's':
+ flags.sym_link = true;
+ flags.hard_link = false;
+ break;
+ case 'v':
+ flags.verbose = true;
+ break;
+ default:
+ return ARG_INVALID;
+ }
+ return ARG_UNUSED;
+}
+
+static bool copy_file(char* from, char* to) {
+ FILE* from_f = get_file_s(from, "r");
+ if (from_f == NULL) { return false; }
+
+ FILE* to_f = get_file_s(to, "w");
+ if (to_f == NULL) { fclose(from_f); return false; }
+
+ #define BS 1024
+
+ char buf[BS];
+ int read;
+
+ while ((read = fread(buf, 1, BS, from_f)) > 0) {
+ fwrite(buf, 1, read, to_f);
+ }
+
+ if (flags.verbose) {
+ output("copied '%s'", from);
+ }
+
+ return true;
+}
+
+static bool symlink_file(char* from, char* to) {
+ if (symlink(from, to) < 0) {
+ error_s("failed to symlink '%s': %s", from, strerror(errno));
+ return false;
+ } else if (flags.verbose) {
+ output("symlinked '%s'", from);
+ }
+ return true;
+}
+
+static bool hardlink_file(char* from, char* to) {
+ if (link(from, to) < 0) {
+ error_s("failed to hardlink '%s': %s", from, strerror(errno));
+ return false;
+ } else if (flags.verbose) {
+ output("hardlinked '%s'", from);
+ }
+ return true;
+}
+
+static void run_copy(struct stat* s) {
+
+ char* from = get_path_buffer();
+ char* to = get_path_buffer_2();
+
+ bool result;
+ if (flags.sym_link) {
+ result = symlink_file(from, to);
+ } else if (flags.hard_link) {
+ result = hardlink_file(from, to);
+ } else {
+ result = copy_file(from, to);
+ }
+
+ if (!result) return;
+ if (!flags.preserve) return;
+
+ if (chmod(to, s->st_mode) < 0) {
+ error_s("cannot chmod '%s': %s", to, strerror(errno));
+ return;
+ }
+
+ if (chown(to, s->st_uid, s->st_gid) < 0) {
+ error_s("cannot chown '%s': %s", to, strerror(errno));
+ return;
+ }
+}
+
+static void cp_file(char* path);
+
+static void cp_directory(struct stat* s) {
+ if (!flags.recurse) {
+ error_s("-r not specified; omitting directory '%s'", get_path_buffer());
+ return;
+ }
+
+ if (mkdir(get_path_buffer_2(), s->st_mode) < 0 && errno != EEXIST) {
+ error_s("cannot create directory '%s': %s", get_path_buffer_2(), strerror(errno));
+ return;
+ }
+
+ DIR* d = opendir(get_path_buffer());
+
+ if (d == NULL) {
+ error_s("cannot open directory '%s': %s", get_path_buffer(), strerror(errno));
+ return;
+ }
+
+ struct dirent* file;
+ while ((file = readdir(d)) != NULL) {
+ if (is_dot_dir(file->d_name)) continue;
+ cp_file(file->d_name);
+ }
+}
+
+static char* get_file_name(char* path) {
+ if (path[0] == '\0') return path;
+
+ int last = 0;
+ int i = 0;
+ while (true) {
+ if (path[i+1] == '\0') break;
+ if (path[i] == '/') {
+ last = i;
+ }
+ i++;
+ }
+
+ if (last == 0) return path;
+ else return path + last + 1;
+}
+
+static void cp_file(char* path) {
+
+ int save = push_path_buffer(path);
+ int save2 = push_path_buffer_2(get_file_name(path));
+
+ struct stat s;
+ if (lstat(get_path_buffer(), &s) < 0) {
+ pop_path_buffer(save);
+ error_s("cannot stat '%s': %s", get_path_buffer(), strerror(errno));
+ return;
+ }
+
+ if (S_ISDIR(s.st_mode)) {
+ cp_directory(&s);
+ } else {
+ run_copy(&s);
+ }
+
+ pop_path_buffer(save);
+ pop_path_buffer_2(save2);
+}
+
+COMMAND(cp) {
+
+ flags.hard_link = false;
+ flags.sym_link = false;
+ flags.preserve = false;
+ flags.recurse = false;
+ flags.verbose = false;
+
+ int start = parse_args(argc, argv, help, short_arg, NULL);
+
+ if (argc - start < 2) {
+ global_help(help);
+ }
+
+ // only when 2 args and first is a file, the 2nd will be a file
+ if (argc - start == 2) {
+ struct stat s;
+ if (lstat(argv[start], &s) < 0) {
+ error("cannot stat '%s': %s", argv[start], strerror(errno));
+ }
+ push_path_buffer(argv[argc-2]);
+ push_path_buffer_2(argv[argc-1]);
+ if (!S_ISDIR(s.st_mode)) {
+ run_copy(&s);
+ } else {
+ cp_directory(&s);
+ }
+ return EXIT_SUCCESS;
+ }
+
+ // push directory
+ push_path_buffer_2(argv[argc-1]);
+
+ struct stat s;
+ if (lstat(get_path_buffer_2(), &s) < 0) {
+ error("target: '%s': %s", get_path_buffer_2(), strerror(errno));
+ }
+
+ for (int i = start; i < argc - 1; i++) {
+ cp_file(argv[i]);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/commands/dd.c b/src/commands/dd.c
index 6b973e2..78f6041 100644
--- a/src/commands/dd.c
+++ b/src/commands/dd.c
@@ -29,16 +29,16 @@ COMMAND(dd) {
char* str = argv[i] + 3;
bs = get_number(str);
if (bs < 1) {
- error("error: block size must be greater than 0");
+ error("block size must be greater than 0");
}
} else if (prefix("count=", argv[i])) {
char* str = argv[i] + 6;
count = get_number(str);
if (count < 1) {
- error("error: count must be greather than 0");
+ error("count must be greather than 0");
}
} else {
- error("error: unkown option %s", argv[i]);
+ error("unkown option %s", argv[i]);
}
}
diff --git a/src/commands/ed.c b/src/commands/ed.c
index 44f1a2c..379da3c 100644
--- a/src/commands/ed.c
+++ b/src/commands/ed.c
@@ -50,7 +50,7 @@ static bool read_regex(char** end, re_t* regex, enum RegexDirection dir) {
while(true) {
c = *(index++);
if (c == '\0') {
- fprintf(stderr, "error: missing regex after %c\n", dir == BEFORE ? '?' : '/');
+ error_s("missing regex after %c\n", dir == BEFORE ? '?' : '/');
return false;
}
if (c == (dir == BEFORE ? '?' : '/')) {
@@ -170,7 +170,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a)
n_pre = -1;
} else {
if (n_pre < 0) {
- fprintf(stderr, "error: input cannot be negative\n");
+ error_s("input cannot be negative\n");
return false;
}
index = end_pre;
@@ -192,7 +192,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a)
char* end;
long int n = strtol(index, &end, 10) - 1;
if (n < 0) {
- fprintf(stderr, "error: input cannot be negative\n");
+ error_s("input cannot be negative\n");
return false;
}
if (index == end) {
@@ -201,7 +201,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a)
address.data.index.i = line_current - n;
}
if (address.data.index.i < 0) {
- fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1);
+ error_s("line number %ld does not exist\n", address.data.index.i + 1);
return false;
}
break;
@@ -211,7 +211,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a)
char* end;
long int n = strtol(index, &end, 10) - 1;
if (n < 0) {
- fprintf(stderr, "error: input cannot be negative\n");
+ error_s("input cannot be negative\n");
return false;
}
if (index == end) {
@@ -220,7 +220,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a)
address.data.index.i = line_current + n;
}
if (address.data.index.i >= (long int) line_count) {
- fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1);
+ error_s("line number %ld does not exist\n", address.data.index.i + 1);
return false;
}
break;
@@ -256,7 +256,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a)
address.data.index.i = n_pre;
}
if (address.data.index.i < 0 || address.data.index.i >= (long int) line_count) {
- fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1);
+ error_s("line number %ld does not exist\n", address.data.index.i + 1);
return false;
}
}
@@ -404,7 +404,7 @@ static void delete_lines(unsigned long a, unsigned long b) {
static bool handle_append(struct LineAddress* address) {
if (address->type != INDEX) {
- fprintf(stderr, "error: append command requires index addressing\n");
+ error_s("append command requires index addressing\n");
return false;
}
if (line_count == 0) {
@@ -424,20 +424,20 @@ static bool handle_append(struct LineAddress* address) {
static bool handle_delete(struct LineAddress* address) {
if (address->empty && address->data.index.i >= (long int) line_count) {
- fprintf(stderr, "error: line number %ld does not exist\n", address->data.index.i + 1);
+ error_s("line number %ld does not exist\n", address->data.index.i + 1);
return false;
}
if (address->type == INDEX) {
delete_lines(address->data.index.i, address->data.index.i);
- printf("ed: deleted line %ld\n", address->data.index.i+1);
+ output("deleted line %ld\n", address->data.index.i+1);
} else if (address->type == RANGE) {
delete_lines(address->data.range.a, address->data.range.b);
- printf("ed: deleted lines %ld-%ld\n", address->data.range.a+1, address->data.range.b+1);
+ output("deleted lines %ld-%ld\n", address->data.range.a+1, address->data.range.b+1);
} else if (address->type == SET) {
for (unsigned long i = 0; i < address->data.set.s; i++) {
delete_lines(address->data.set.b[i], address->data.set.b[i]);
}
- printf("ed: deleted %lu lines\n", address->data.set.s);
+ output("deleted %lu lines\n", address->data.set.s);
}
return true;
}
@@ -446,7 +446,7 @@ static bool get_file_name(char** filename) {
size_t len = strlen(*filename);
if (len < 1 || (len == 1 && **filename == '\n')) {
if (default_filename == NULL) {
- fprintf(stderr, "error: no default filename specified\n");
+ error_s("no default filename specified\n");
return false;
}
*filename = default_filename;
@@ -468,7 +468,7 @@ static bool get_file_name(char** filename) {
static void write_file(char* filename, struct LineAddress* address, char* type) {
if (line_count < 1) {
- fprintf(stderr, "error: cannot write empty file\n");
+ error_s("cannot write empty file\n");
return;
}
if (!get_file_name(&filename)) return;
@@ -497,7 +497,7 @@ static void write_file(char* filename, struct LineAddress* address, char* type)
}
pending_writes = false;
fclose(file);
- printf("ed: wrote %d lines from %s\n", wrote, filename);
+ output("wrote %d lines from %s\n", wrote, filename);
}
static void read_file(char* filename) {
@@ -512,7 +512,7 @@ static void read_file(char* filename) {
if (size < 1) {
free(buf);
- fprintf(stderr, "error: attempted to read a empty file\n");
+ error_s("attempted to read a empty file\n");
return;
}
@@ -522,7 +522,7 @@ static void read_file(char* filename) {
}
append_lines(line, buf, size);
free(buf);
- printf("ed: read and appended %lu lines from %s\n", size, filename);
+ output("read and appended %lu lines from %s\n", size, filename);
}
static void expand_string(char** buf, int* capacity, int* size, char* text, int len) {
@@ -582,7 +582,7 @@ static void prompt(void) {
if (cmd == ',') {
if (address.type != INDEX) {
- fprintf(stderr, "error: comma range addressing requires two index addresses\n");
+ error_s("comma range addressing requires two index addresses\n");
free_address(address);
return;
}
@@ -593,7 +593,7 @@ static void prompt(void) {
return;
}
if (address2.type != INDEX) {
- fprintf(stderr, "error: comma range addressing requires two index addresses\n");
+ error_s("comma range addressing requires two index addresses\n");
free_address(address);
free_address(address2);
return;
@@ -606,7 +606,7 @@ static void prompt(void) {
}
if (address.type == RANGE && address.data.range.a > address.data.range.b) {
- fprintf(stderr, "error: range addressing must be in ascending order\n");
+ error_s("range addressing must be in ascending order\n");
free_address(address);
return;
}
@@ -618,10 +618,10 @@ test:
case '\n':
if (address.empty) {
if (line_current == line_count) {
- fprintf(stderr, "error: line number %ld does not exist\n", line_current + 1);
+ error_s("line number %ld does not exist\n", line_current + 1);
break;
} else if (line_current + 1 == line_count) {
- fprintf(stderr, "error: line number %ld does not exist\n", line_current + 2);
+ error_s("line number %ld does not exist\n", line_current + 2);
break;
} else {
line_current++;
@@ -634,7 +634,7 @@ test:
} else if (address.type == RANGE) {
line_current = address.data.range.b;
} else if (address.type == SET) {
- fprintf(stderr, "error: unexpected range addressing\n");
+ error_s("unexpected range addressing\n");
break;
}
printf("%s", lines[line_current]);
@@ -656,7 +656,7 @@ test:
__attribute__((fallthrough));
case 'p':
if (address.empty && address.data.index.i >= (long int) line_count) {
- fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1);
+ error_s("line number %ld does not exist\n", address.data.index.i + 1);
break;
}
if (address.type == INDEX) {
@@ -686,7 +686,7 @@ test:
skip_whitespace(&index);
free_address(address);
if (*(index++) != '/') {
- fprintf(stderr, "error: unexpected character at start of regex\n");
+ error_s("unexpected character at start of regex\n");
break;
}
if (!parse_regex(&index, &address, ALL)) { return; }
@@ -701,18 +701,18 @@ test:
case 's':
skip_whitespace(&index);
if (*(index++) != '/') {
- fprintf(stderr, "error: unexpected character at start of regex\n");
+ error_s("unexpected character at start of regex\n");
break;
}
if (!parse_regex_lines(&index, &address)) { return; }
char* replace = index;
while(*index != '\0' && *index != '/') index++;
if (*index != '/') {
- fprintf(stderr, "error: / missing after %c\n", *index);
+ error_s("/ missing after %c\n", *index);
break;
}
if (address.data.set.s < 1) {
- printf("ed: no matches found\n");
+ error_s("no matches found\n");
break;
}
*(index++) = '\0';
@@ -725,11 +725,11 @@ test:
char* end;
matches = strtol(index, &end, 10);
if (end == index) {
- fprintf(stderr, "error: invalid number: %s\n", index);
+ error_s("invalid number: %s\n", index);
break;
}
if (matches < 1) {
- fprintf(stderr, "error: matches cannot be less than 1\n");
+ error_s("matches cannot be less than 1\n");
break;
}
}
@@ -739,7 +739,7 @@ test:
for (unsigned long i = 0; i < address.data.set.s; i++) {
matches_found += substute_string(address.data.set.b[i], matches, last_regex, replace, sub_len);
}
- printf("ed: replaced %ld matches over %ld lines\n", matches_found, address.data.set.s);
+ output("replaced %ld matches over %ld lines\n", matches_found, address.data.set.s);
pending_writes = true;
break;
case 'w': {
@@ -782,7 +782,7 @@ test:
printf("%ld\n", line_current + 1);
break;
default:
- fprintf(stderr, "error: unimplemented command\n");
+ error_s("unimplemented command\n");
break;
}
diff --git a/src/commands/groups.c b/src/commands/groups.c
index aea92af..763f294 100644
--- a/src/commands/groups.c
+++ b/src/commands/groups.c
@@ -9,7 +9,7 @@ COMMAND_EMPTY(groups) {
struct passwd* pw = getpwuid(uid);
if(pw == NULL){
- perror("error: failed to fetch groups: ");
+ error("failed to fetch groups: %s", strerror(errno));
}
int ngroups = 0;
@@ -21,7 +21,7 @@ COMMAND_EMPTY(groups) {
for (int i = 0; i < ngroups; i++){
struct group* gr = getgrgid(groups[i]);
if(gr == NULL){
- perror("error: failed to fetch groups: ");
+ error("failed to fetch groups: %s", strerror(errno));
}
printf("%s ",gr->gr_name);
}
diff --git a/src/commands/head.c b/src/commands/head.c
index 44bc45b..c09dc9a 100644
--- a/src/commands/head.c
+++ b/src/commands/head.c
@@ -70,7 +70,7 @@ static int short_arg(char c, char* next) {
check_arg(next);
long int bkm = get_blkm(next);
if (bkm < 1) {
- error("error: bkm cannot be less than 1");
+ error("bkm cannot be less than 1");
}
flags.count = bkm;
return ARG_USED;
@@ -80,7 +80,7 @@ static int short_arg(char c, char* next) {
check_arg(next);
long int bkm = get_blkm(next);
if (bkm < 1) {
- error("error: bkm cannot be less than 1");
+ error("bkm cannot be less than 1");
}
flags.count = bkm;
return ARG_USED;
@@ -91,9 +91,8 @@ static int short_arg(char c, char* next) {
case 'v':
flags.print_headers = true;
break;
- default: {
- error("error: unknown option -%c", c);
- }
+ default:
+ return ARG_INVALID;
}
return ARG_UNUSED;
}
diff --git a/src/commands/id.c b/src/commands/id.c
index 578c1e3..4bd7bca 100644
--- a/src/commands/id.c
+++ b/src/commands/id.c
@@ -10,7 +10,7 @@ COMMAND_EMPTY(user_id) {
struct passwd* pw = getpwuid(uid);
if(pw == NULL){
- perror("error: failed to fetch groups: ");
+ error("failed to fetch groups: %s", strerror(errno));
}
int ngroups = 0;
@@ -30,7 +30,7 @@ COMMAND_EMPTY(user_id) {
for (int i = 0; i < ngroups; i++){
struct group* gr = getgrgid(groups[i]);
if(gr == NULL){
- perror("error: failed to fetch groups: ");
+ error("failed to fetch groups: %s", strerror(errno));
}
printf("%d(%s)", gr->gr_gid, gr->gr_name);
if (i + 1 < ngroups) putchar(',');
diff --git a/src/commands/ls.c b/src/commands/ls.c
index fec8cdd..a5c60d2 100644
--- a/src/commands/ls.c
+++ b/src/commands/ls.c
@@ -55,9 +55,9 @@ static DIR* get_directory(char* path) {
DIR* d = opendir(path);
if (d == NULL) {
if (errno == ENOTDIR) {
- printf("\x1b[0m%s is a a file\n", path);
+ error_s("`%s` is a a file\n", path);
} else {
- printf("\x1b[0merror: failed to open directory '%s': %s\n", path, strerror(errno));
+ error_s("failed to open directory '%s': %s\n", path, strerror(errno));
}
}
return d;
@@ -74,7 +74,7 @@ static bool get_file_info(const char* file_name, struct FileInfo* info) {
int save = push_path_buffer(file_name);
if (lstat(get_path_buffer(), &s) < 0) {
- printf("\x1b[0merror: failed to read file '%s': %s\n", get_path_buffer(), strerror(errno));
+ error_s("failed to read file '%s': %s\n", get_path_buffer(), strerror(errno));
pop_path_buffer(save);
return false;
}
@@ -156,14 +156,14 @@ static bool get_file_info(const char* file_name, struct FileInfo* info) {
info->usr = getpwuid(s.st_uid);
if (info->usr == NULL) {
- fprintf(stderr, "error: failed to get user from %s\n", get_path_buffer());
+ error_s("failed to get user from `%s`\n", get_path_buffer());
pop_path_buffer(save);
return false;
}
info->grp = getgrgid(s.st_gid);
if (info->grp == NULL) {
- fprintf(stderr, "error: failed to get user from %s\n", get_path_buffer());
+ error_s("failed to get user from `%s`\n", get_path_buffer());
pop_path_buffer(save);
return false;
}
@@ -288,10 +288,6 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo
if (!flags.more_info) printf("\n");
}
-static bool is_dot_dir(const char* path) {
- return streql(path, ".") || streql(path, "..");
-}
-
static int num_places (int n) {
int r = 1;
if (n < 0) n = (n == INT_MIN) ? INT_MAX: -n;
@@ -465,7 +461,7 @@ static int short_arg(char c, char* next) {
flags.more_info = true;
break;
default:
- error("error: unkown option -%c", c);
+ return ARG_INVALID;
}
return ARG_UNUSED;
}
@@ -481,7 +477,7 @@ static int long_arg(char* cur, char* next) {
} else if (streql("no", arg) || streql("never", arg)) {
flags.colored = NO;
} else {
- error("error: invalid color options: %s", arg);
+ error("invalid color options: %s", arg);
}
} else {
return ARG_IGNORE;
@@ -496,7 +492,7 @@ COMMAND(ls) {
flags.hide_dot = false;
flags.one_column = false;
flags.recurse = false;
- flags.colored = AUTO;
+ flags.colored = NO;
int start = parse_args(argc, argv, help, short_arg, long_arg);
diff --git a/src/commands/mkdir.c b/src/commands/mkdir.c
new file mode 100644
index 0000000..3ff1afd
--- /dev/null
+++ b/src/commands/mkdir.c
@@ -0,0 +1,60 @@
+#include "../command.h"
+
+static struct {
+ bool make_parent;
+ mode_t mode;
+} flags;
+
+static int short_arg(char c, char* next) {
+ switch (c) {
+ case 'p':
+ flags.make_parent = true;
+ break;
+ case 'm':
+ check_arg(next);
+ flags.mode = get_mode(next);
+ return ARG_USED;
+ default:
+ return ARG_INVALID;
+ }
+ return ARG_UNUSED;
+}
+
+static void help(void) {
+ printf("Usage: mkdir [-m MODE] [-p] DIRECTORY...\n\n");
+ printf("Create DIRECTORY\n\n");
+ printf("\t-m\tMODE\n");
+ printf("\t-p\tNo error if exists; make parent directories as needed\n");
+}
+
+static bool mkdir_parents(char* path) {
+ for (size_t i = 1; i < strlen(path); i++) {
+ if (path[i] != '/') continue;
+ path[i] = '\0';
+ if (mkdir(path, flags.mode) < 0 && errno != EEXIST) {
+ error_s("failed to create directory '%s': %s", path, strerror(errno));
+ return false;
+ };
+ path[i] = '/';
+ }
+ return true;
+}
+
+COMMAND(makedir) {
+ if (argc < 1) global_help(help);
+
+ flags.make_parent = false;
+ flags.mode = 0755;
+ int start = parse_args(argc, argv, help, short_arg, NULL);
+
+ for (int i = start; i < argc; i++) {
+ if (flags.make_parent && !mkdir_parents(argv[i])) {
+ continue;
+ }
+ if (mkdir(argv[i], flags.mode) < 0) {
+ error_s("failed to create directory '%s': %s", argv[i], strerror(errno));
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/commands/mv.c b/src/commands/mv.c
new file mode 100644
index 0000000..f3e759a
--- /dev/null
+++ b/src/commands/mv.c
@@ -0,0 +1,108 @@
+#include "../command.h"
+
+static struct {
+ bool prompt;
+ bool dont_overwrite;
+ bool refuse_if_dir;
+ bool verbose;
+} flags;
+
+static void help(void) {
+ printf("Usage: mv [-inT] SOURCE... DIRECTORY\n\n");
+ printf("Rename SOURCE to DEST, or move SOURCEs to DIRECTORY\n");
+ printf("\t-i\tInteractive, prompt before overwriting\n");
+ printf("\t-n\tDon't overwrite an existing file\n");
+ printf("\t-T\tRefuse to move if DEST is a directory\n");
+ printf("\t-v\tVerbose\n");
+}
+
+static int short_arg(char c, char* next) {
+ UNUSED(next);
+ switch (c) {
+ case 't':
+ flags.prompt = true;
+ break;
+ case 'n':
+ flags.dont_overwrite = true;
+ break;
+ case 'T':
+ flags.refuse_if_dir = true;
+ break;
+ case 'v':
+ flags.verbose = true;
+ break;
+ default:
+ return ARG_UNUSED;
+ }
+ return ARG_USED;
+}
+
+static void mv_dir(bool exists) {
+ if (exists && flags.dont_overwrite) {
+ if (flags.verbose) output("skipping '%s'; overwrise is false", get_path_buffer_2());
+ return;
+ }
+ if (exists && flags.prompt) {
+ fprintf(stderr, "overwrite '%s'? ", get_path_buffer_2());
+ fflush(stderr);
+ char c = getchar();
+ if (c != 'y' && c != 'Y') {
+ if (flags.verbose) output("skipping...");
+ return;
+ }
+ }
+
+ if (rename(get_path_buffer(), get_path_buffer_2()) < 0) {
+ error_s("cannot move '%s': %s", get_path_buffer(), strerror(errno));
+ } else if (flags.verbose) {
+ output("moved '%s'", get_path_buffer());
+ }
+}
+
+COMMAND(mv) {
+
+ flags.refuse_if_dir = false;
+ flags.dont_overwrite = false;
+ flags.prompt = false;
+ flags.verbose = false;
+
+ int start = parse_args(argc, argv, help, short_arg, NULL);
+
+ if (argc - start < 2) {
+ global_help(help);
+ }
+
+ push_path_buffer_2(argv[argc-1]);
+
+ bool dest = true;
+ struct stat s;
+ if (lstat(get_path_buffer_2(), &s) < 0 && argc - start > 2) {
+ dest = false;
+ error("cannot stat '%s': %s", get_path_buffer_2(), strerror(errno));
+ }
+
+ if (dest && flags.refuse_if_dir) {
+ if (S_ISDIR(s.st_mode)) {
+ error("target '%s': Is A Directory", get_path_buffer_2());
+ }
+ }
+
+ if (argc - start == 2) {
+ push_path_buffer(argv[argc-2]);
+ mv_dir(dest);
+ return EXIT_SUCCESS;
+ }
+
+ if (dest && !S_ISDIR(s.st_mode)) {
+ error("target '%s': Is Not A Directory", get_path_buffer_2());
+ }
+
+ for (int i = start; i < argc - 1; i++) {
+ int save = push_path_buffer(argv[i]);
+ bool exists = lstat(get_path_buffer(), &s) >= 0;
+ mv_dir(exists);
+ pop_path_buffer(save);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/commands/printf.c b/src/commands/printf.c
index f9f3931..df6ce32 100644
--- a/src/commands/printf.c
+++ b/src/commands/printf.c
@@ -96,7 +96,8 @@ static void help(void) {
COMMAND(print) {
if (argc < 1) {
- error("usage: printf FORMAT [ARG]...\n");
+ global_help(help);
+ return EXIT_SUCCESS;
}
parse_help(argc, argv, help);
diff --git a/src/commands/rm.c b/src/commands/rm.c
new file mode 100644
index 0000000..b85f91a
--- /dev/null
+++ b/src/commands/rm.c
@@ -0,0 +1,132 @@
+#include "../command.h"
+#include <dirent.h>
+#include <string.h>
+
+static struct {
+ bool force;
+ bool prompt;
+ bool verbose;
+ bool recurse;
+} flags;
+
+#ifdef FRENCH
+ static bool get_frenched;
+#endif
+
+static void help(void) {
+ printf("Usage: rm [-irfv] FILE...\n\n");
+ printf("Remove (unlink) FILESs\n\n");
+ printf("\t-i\tAlways prompt before removing\n");
+ printf("\t-f\tForce, never prompt\n");
+ printf("\t-v\tVerbose\n");
+ printf("\t-R,-r\tRecurse\n");
+}
+
+static int short_arg(char c, char* next) {
+ UNUSED(next);
+ switch (c) {
+ case 'i':
+ flags.prompt = true;
+ break;
+ case 'f':
+ flags.force = true;
+ break;
+ case 'v':
+ flags.verbose = true;
+ break;
+ case 'R':
+ case 'r':
+ flags.recurse = true;
+ break;
+ default:
+ return ARG_INVALID;
+ }
+ return ARG_UNUSED;
+}
+
+static void rm_file(char* path);
+
+static bool rm_dir() {
+ DIR* d = opendir(get_path_buffer());
+ if (d == NULL) {
+ error_s("failed to stat '%s': %s\n", get_path_buffer(), strerror(errno));
+ return false;
+ }
+
+ struct dirent* file;
+ while ((file = readdir(d)) != NULL) {
+ if (is_dot_dir(file->d_name)) continue;
+ rm_file(file->d_name);
+ }
+
+ closedir(d);
+ return true;
+}
+
+static void rm_file(char* path) {
+ int save = push_path_buffer(path);
+
+ struct stat s;
+ if (lstat(get_path_buffer(), &s) < 0) {
+ pop_path_buffer(save);
+ error_s("failed to stat '%s': %s\n", get_path_buffer(), strerror(errno));
+ return;
+ }
+
+ if (S_ISDIR(s.st_mode)) {
+ if (!flags.force) {
+ error_s("cannot delete '%s': Is a directory\n", get_path_buffer());
+ pop_path_buffer(save);
+ return;
+ }
+ if (flags.recurse && !rm_dir()) {
+ pop_path_buffer(save);
+ return;
+ }
+ }
+
+ if (flags.prompt) {
+ fprintf(stderr, "delete '%s'? ", get_path_buffer());
+ fflush(stderr);
+ char c = getchar();
+ if (c != 'y' && c != 'Y') {
+ fprintf(stderr, "Skipping...\n");
+ pop_path_buffer(save);
+ return;
+ }
+ }
+
+ if (remove(get_path_buffer()) < 0) {
+ error_s("failed to delete '%s': %s\n", get_path_buffer(), strerror(errno));
+ } else if (flags.verbose) {
+ output("deleted '%s'\n", get_path_buffer());
+ }
+
+ pop_path_buffer(save);
+}
+
+COMMAND(rm) {
+ if (argc < 1) {
+ global_help(help);
+ return EXIT_SUCCESS;
+ }
+
+ flags.prompt = false;
+ flags.force = false;
+ flags.verbose = false;
+ flags.recurse = false;
+
+ int start = parse_args(argc, argv, help, short_arg, NULL);
+
+#ifdef FRENCH
+ if (streql(argv[0], "-rf")) {
+ printf("\x1b[94mremoving \x1b[97mthe \x1b[91mfrench \x1b[93m(beguette noises)\x1b[0m\n");
+ }
+#endif
+
+ for (int i = start; i < argc; i++) {
+ rm_file(argv[i]);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/commands/tail.c b/src/commands/tail.c
index a611842..4856bc0 100644
--- a/src/commands/tail.c
+++ b/src/commands/tail.c
@@ -131,7 +131,7 @@ static int short_arg(char c, char* next) {
check_arg(next);
long int bkm = get_blkm(next);
if (bkm < 1) {
- error("error: bkm cannot be less than 1");
+ error("bkm cannot be less than 1");
}
flags.count = bkm;
return ARG_USED;
@@ -141,7 +141,7 @@ static int short_arg(char c, char* next) {
check_arg(next);
long int bkm = get_blkm(next);
if (bkm < 1) {
- error("error: bkm cannot be less than 1");
+ error("bkm cannot be less than 1");
}
flags.count = bkm;
return ARG_USED;
@@ -159,14 +159,13 @@ static int short_arg(char c, char* next) {
check_arg(next);
long int sec = get_number(next);
if (sec < 1) {
- error("error: wait seconds cannot be less than 1");
+ error("wait seconds cannot be less than 1");
}
flags.grow_wait = sec;
return ARG_USED;
}
- default: {
- error("error: unknown option -%c", c);
- }
+ default:
+ return ARG_INVALID;
}
return ARG_UNUSED;
}
diff --git a/src/commands/tee.c b/src/commands/tee.c
index ced5b06..652e369 100644
--- a/src/commands/tee.c
+++ b/src/commands/tee.c
@@ -41,7 +41,7 @@ static int short_arg(char c, char* next) {
flags.handle_sigint = true;
break;
default:
- error("error: unkown option: %c", c);
+ return ARG_INVALID;
}
return ARG_UNUSED;
}
diff --git a/src/commands/wc.c b/src/commands/wc.c
index 9acbf5c..4012b6c 100644
--- a/src/commands/wc.c
+++ b/src/commands/wc.c
@@ -111,7 +111,7 @@ static int short_arg(char c, char* next) {
flags.longest_line = true;
break;
default:
- error("error: invald option -%c", c);
+ return ARG_INVALID;
}
flags.has_flags = true;
return ARG_UNUSED;
diff --git a/src/commands/xargs.c b/src/commands/xargs.c
index 01fb6ce..8f24441 100644
--- a/src/commands/xargs.c
+++ b/src/commands/xargs.c
@@ -45,10 +45,12 @@ static int short_arg(char c, char* next) {
check_arg(next);
long int n = get_number(next);
if (n < 1) {
- error("error: max arg count must be at least 1");
+ error("max arg count must be at least 1");
}
flags.max_args = n;
return ARG_USED;
+ default:
+ return ARG_INVALID;
}
return ARG_UNUSED;
}
diff --git a/src/main.c b/src/main.c
index 7bd931e..e23b915 100644
--- a/src/main.c
+++ b/src/main.c
@@ -8,9 +8,12 @@
#include <sys/stat.h>
#include <sys/types.h>
+char* cmd;
+
int main (ARGUMENTS) {
if (argc < 1) {
- error("error: argument 0 missing");
+ fprintf(stderr, "fatal: argument 0 missing");
+ return EXIT_FAILURE;
}
#ifdef CHECK_LINK
@@ -21,7 +24,7 @@ int main (ARGUMENTS) {
if (argc < 2) {
printf("usage: lazysphere [function [arguments]...]\n\n");
printf("currently defined functions:\n");
- printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head, ed, tee, true, false, tee, whoami, wc, xargs, tac\n");
+ printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head, ed, tee, true, false, tee, whoami, wc, xargs, tac, rm, cp, mkdir, mv\n");
return EXIT_SUCCESS;
}
argc--;
@@ -29,7 +32,6 @@ int main (ARGUMENTS) {
}
#endif
- const char* cmd;
if (strncmp("./", argv[0], 2) == 0) {
cmd = argv[0] + 2;
} else {
@@ -52,6 +54,8 @@ int main (ARGUMENTS) {
return user_id();
} else if (streql(cmd, "ls") || streql(cmd, "dir")) {
return ls(NEXT_ARGS);
+ } else if (streql(cmd, "lsd")) {
+ printf("look at all the funny colors\n");
} else if (streql(cmd, "tail")) {
return tail(NEXT_ARGS);
} else if (streql(cmd, "head")) {
@@ -72,8 +76,17 @@ int main (ARGUMENTS) {
return xargs(NEXT_ARGS);
} else if (streql(cmd, "tac")) {
return tac(NEXT_ARGS);
+ } else if (streql(cmd, "rm")) {
+ return rm(NEXT_ARGS);
+ } else if (streql(cmd, "cp")) {
+ return cp(NEXT_ARGS);
+ } else if (streql(cmd, "mkdir")) {
+ return makedir(NEXT_ARGS);
+ } else if (streql(cmd, "mv")) {
+ return mv(NEXT_ARGS);
} else {
- error("error: invalid command %s", cmd);
+ fprintf(stderr, "lazysphere: invalid command %s", cmd);
+ return EXIT_FAILURE;
}
return EXIT_SUCCESS;
diff --git a/src/util/shared.c b/src/util/shared.c
index 600a967..8f2fe93 100644
--- a/src/util/shared.c
+++ b/src/util/shared.c
@@ -1,5 +1,6 @@
#include "shared.h"
+#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
@@ -10,15 +11,36 @@
#include <sys/stat.h>
#include <paths.h>
-void error(const char* format, ...) {
+extern char* cmd;
+
+void error_s(const char *format, ...) {
va_list list;
va_start(list, format);
+ fprintf(stderr, "%s: ", cmd);
+ vfprintf(stderr, format, list);
+ fprintf(stderr, "\n");
+}
+
+void error(const char *format, ...) {
+ va_list list;
+ va_start(list, format);
+
+ fprintf(stderr, "%s: ", cmd);
vfprintf(stderr, format, list);
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
+void output(const char *format, ...) {
+ va_list list;
+ va_start(list, format);
+
+ fprintf(stdout, "%s: ", cmd);
+ vfprintf(stdout, format, list);
+ fprintf(stdout, "\n");
+}
+
FILE* get_file_s(const char* path, const char* type) {
struct stat s;
if (streql("-", path) && type[0] == 'r') {
@@ -28,11 +50,11 @@ FILE* get_file_s(const char* path, const char* type) {
}
if (lstat(path, &s) < 0) {
if (type[0] != 'r') goto read;
- fprintf(stderr, "error: failed to read %s: %s\n", path, strerror(errno));
+ error_s("failed to read %s: %s", path, strerror(errno));
return NULL;
}
if (S_ISDIR(s.st_mode)) {
- fprintf(stderr, "error: %s is a directory\n", path);
+ error_s("%s is a directory", path);
return NULL;
}
@@ -40,7 +62,7 @@ FILE* get_file_s(const char* path, const char* type) {
read:
file = fopen(path, type);
if (file == NULL) {
- fprintf(stderr, "error: failed to %s file %s: %s\n", type[0] == 'r' ? "read" : "write", path, strerror(errno));
+ error_s("failed to %s file %s: %s", type[0] == 'r' ? "read" : "write", path, strerror(errno));
}
return file;
}
@@ -55,7 +77,7 @@ long int get_number(const char* text) {
char* end;
long int n = strtol(text, &end, 10);
if (text == end) {
- error("error: %s is not a valid number", text);
+ error("%s is not a valid number", text);
}
return n;
}
@@ -64,7 +86,7 @@ long int get_blkm(const char* text) {
char* end;
long int n = strtol(text, &end, 10);
if (text == end) {
- error("error: %s is not a valid bkm", text);
+ error("%s is not a valid bkm", text);
}
if (*end == '\0') return n;
switch (*end) {
@@ -78,12 +100,23 @@ long int get_blkm(const char* text) {
case 'm':
return n * 1024 * 1204;
default:
- error("error: invalid bkm type %c", *end);
+ error("invalid bkm type %c", *end);
}
// shouldnt get here anyways
return 0;
}
+mode_t get_mode(const char* next) {
+ char* end = NULL;
+ mode_t mode = (mode_t)strtol(next, &end, 8);
+ if (!end) return 0;
+ while(isspace(*end)) end++;
+ if (*end != '\0' || (unsigned) mode < 010000) {
+ error("invalid file mode: `%s`", next);
+ }
+ return mode;
+}
+
bool streql(const char* a, const char* b) {
if (*a != *b) return false;
int n = 0;
@@ -151,11 +184,11 @@ void print_date_time(time_t mills, char buf[13]) {
void check_arg (char* arg) {
if (arg == NULL) {
- error("error: expected another argument after option");
+ error("expected another argument after option");
}
}
-static void global_help(void (*help)(void)) {
+void global_help(void (*help)(void)) {
printf("LazySphere v%d.%d.%d multi-call binary.\n\n", MAJOR, MINOR, PATCH);
help();
exit(EXIT_SUCCESS);
@@ -196,6 +229,8 @@ int parse_args(int argc, char** argv, void (*help)(void), int (*short_arg)(char,
start++;
} else if (r == ARG_IGNORE) {
goto exit;
+ } else if (r == ARG_INVALID) {
+ error("invalid argument %s", argv[current]);
}
} else {
if (short_arg == NULL) {
@@ -208,6 +243,8 @@ int parse_args(int argc, char** argv, void (*help)(void), int (*short_arg)(char,
start++;
} else if (r == ARG_IGNORE) {
goto exit;
+ } else if (r == ARG_INVALID) {
+ error("invalid argument -%c", argv[current][j]);
}
}
}
@@ -220,7 +257,7 @@ exit:
int get_tty() {
int fd = open(_PATH_TTY, O_RDONLY);
- if (fd < 0) error("error: failed to get tty: %s", strerror(errno));
+ if (fd < 0) error("failed to get tty: %s", strerror(errno));
return fd;
}
@@ -228,11 +265,27 @@ FILE* get_tty_stream(char* type) {
int fd = get_tty();
FILE* file = fdopen(fd, type);
if (file == NULL) {
- error("error: failed to open tty stream: %s", strerror(errno));
+ error("failed to open tty stream: %s", strerror(errno));
}
return file;
}
+static int push_path_buffer_b(char* buf, int* index, const char* string) {
+ int save = *index;
+ if (*index > 1 || (*index == 1 && buf[0] != '/')) {
+ buf[(*index)++] = '/';
+ }
+ int string_len = strlen(string);
+ memcpy(buf + *index, string, string_len + 1);
+ *index += string_len;
+ return save;
+}
+
+static void pop_path_buffer_b(char* buf, int* index, int i) {
+ *index = i;
+ buf[*index] = '\0';
+}
+
static char path_buffer[PATH_MAX + 1];
static int path_buffer_index = 0;
@@ -241,17 +294,28 @@ char* get_path_buffer() {
}
int push_path_buffer(const char* string) {
- int save = path_buffer_index;
- if (path_buffer_index > 1 || (path_buffer_index == 1 && path_buffer[0] != '/')) {
- path_buffer[path_buffer_index++] = '/';
- }
- int string_len = strlen(string);
- memcpy(path_buffer + path_buffer_index, string, string_len + 1);
- path_buffer_index += string_len;
- return save;
+ return push_path_buffer_b(path_buffer, &path_buffer_index, string);
}
void pop_path_buffer(int i) {
- path_buffer_index = i;
- path_buffer[path_buffer_index] = '\0';
+ pop_path_buffer_b(path_buffer, &path_buffer_index, i);
+}
+
+static char path_buffer_2[PATH_MAX + 1];
+static int path_buffer_index_2 = 0;
+
+char* get_path_buffer_2() {
+ return path_buffer_2;
+}
+
+int push_path_buffer_2(const char* string) {
+ return push_path_buffer_b(path_buffer_2, &path_buffer_index_2, string);
+}
+
+void pop_path_buffer_2(int i) {
+ pop_path_buffer_b(path_buffer_2, &path_buffer_index_2, i);
+}
+
+bool is_dot_dir(const char* path) {
+ return streql(path, ".") || streql(path, "..");
}
diff --git a/src/util/shared.h b/src/util/shared.h
index 9bade52..26e27c3 100644
--- a/src/util/shared.h
+++ b/src/util/shared.h
@@ -33,12 +33,19 @@ enum When {
};
__attribute__ ((__format__(printf, 1, 2)))
+void error_s(const char* format, ...);
+
+__attribute__ ((__format__(printf, 1, 2)))
void error(const char* format, ...);
+__attribute__ ((__format__(printf, 1, 2)))
+void output(const char* format, ...);
+
FILE* get_file_s(const char* path, const char* type);
FILE* get_file(const char* path, const char* type);
long int get_number(const char* text);
long int get_blkm(const char* text);
+mode_t get_mode(const char* next);
bool streql(const char* a, const char* b);
bool prefix(const char* pre, const char* str);
@@ -50,8 +57,10 @@ void print_date_time(time_t mills, char buf[13]);
#define ARG_UNUSED 0
#define ARG_USED 1
#define ARG_IGNORE 2
+#define ARG_INVALID 3
void check_arg (char* arg);
+void global_help(void (*help)(void));
void parse_help (int argc, char** argv, void (*help)(void));
int parse_args (int argc, char** argv, void (*help)(void), int (*short_arg)(char, char*), int (*long_arg)(char*, char*));
@@ -62,3 +71,8 @@ char* get_path_buffer();
int push_path_buffer(const char* string);
void pop_path_buffer(int i);
+char* get_path_buffer_2();
+int push_path_buffer_2(const char* string);
+void pop_path_buffer_2(int i);
+
+bool is_dot_dir(const char* path);