vimdir/source/directive.c
2025-01-25 14:03:27 +01:00

359 lines
9.0 KiB
C

#include "directive.h"
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include "kvec.h"
#include "global.h"
#include "error.h"
#include "file_utils.h"
typedef struct {
char * name;
struct stat st;
bool is_mentioned;
} entry_t;
static
int entry_cmp(const void * a, const void * b) { // For qsort()
const entry_t * const A = a;
const entry_t * const B = b;
return strcmp(A->name, B->name);
}
static kvec_t(entry_t) entries;
static kvec_t(const char*) directory_queue;
static kvec_t(move_data_t) move_data;
static
int add_directory(const char * const folder) {
DIR * dir = opendir(folder);
CHECK_OPEN(dir, folder, return 1);
char full_path[PATH_MAX];
struct dirent * mydirent;
struct stat file_stat;
entry_t entry;
while ((mydirent = readdir(dir)) != NULL) {
if (strcmp(mydirent->d_name, ".") == 0
|| strcmp(mydirent->d_name, "..") == 0) {
continue;
}
sprintf(full_path, "%s/%s",
folder,
mydirent->d_name
);
int e = stat(full_path, &file_stat);
CHECK_OPEN(!(e == -1), full_path, return 1);
entry = (entry_t) {
.name = strdup(full_path),
.st = file_stat,
.is_mentioned = false,
};
kv_push(entry_t, entries, entry);
if (is_recursive
&& (kv_A(entries, entries.n-1).st.st_mode & S_IFDIR)) {
kv_push(const char*, directory_queue, kv_A(entries, entries.n-1).name);
}
}
closedir(dir);
return 0;
}
int init_directive_c(const char * const folder) {
init_file_utils(is_dry_run, custom_rm);
kv_init(entries);
kv_init(directory_queue);
kv_init(move_data);
kv_push(const char*, directory_queue, folder);
while (directory_queue.n) {
if (add_directory(kv_pop(directory_queue))) {
return 1;
}
}
qsort(
entries.a,
entries.n,
sizeof(entry_t),
entry_cmp
);
return 0;
}
int deinit_directive_c(void) {
for (int i = 0; i < entries.n; i++) {
free(kv_A(entries, i).name);
}
for (int i = 0; i < move_data.n; i++) {
move_data_t move = kv_A(move_data, i);
free(move.orig_name);
free(move.curt_name);
free(move.dest_name);
}
kv_destroy(directory_queue);
kv_destroy(entries);
kv_destroy(move_data);
deinit_file_utis();
return 0;
}
int make_directive_file(FILE * f) {
for (int i = 0; i < entries.n; i++) {
entry_t * entry = &kv_A(entries, i);
// ID
fprintf(f, "%03d",
i
);
// Permissions
if (do_permissions) {
char permissions[11];
fprintf(f, "\t%s",
mode_to_str(entry->st.st_mode, permissions)
);
}
// Owner
if (do_owner) {
struct passwd * usr = getpwuid(entry->st.st_uid);
struct group * grp = getgrgid(entry->st.st_gid);
fprintf(f, "\t%s:%s",
usr->pw_name,
grp->gr_name
);
}
// Name
fprintf(f, "\t%s",
entry->name
);
// if Directory
if (entry->st.st_mode & S_IFDIR) {
putc('/', f);
}
putc('\n', f);
}
return 0;
}
static inline
char * next_field(const char * s) {
while (*s != '\t'
&& *s != '\n'
&& *s != '\0') {
++s;
}
if (*s != '\0') {
++s;
}
return (char*)s;
}
int execute_directive_file(FILE * f) {
#define NEXT_FIELD do { \
if (*(sp = next_field(sp)) == '\0') { \
errorn(E_FORMAT); \
goto recovery; \
} \
} while (0)
#define CHECK(x) do { \
if (x) { goto recovery; } \
} while (0)
#define CHECK_FORMAT(n, x) do { \
if (n != x) { \
errorn(E_FORMAT); \
goto recovery; \
} \
} while (0)
/* io buffering
*/
const int LINE_SIZE = 4096;
char line[LINE_SIZE];
char buffer[LINE_SIZE/2];
/* String Pointer, indexing `line`
*/
char * sp;
/* alias reference to the current entry being operated on
*/
entry_t * entry;
/* since new files fille be missing from `entries`,
* but we only the the latest one,
* we buffer it on the stack
*/
char touch_buffer[LINE_SIZE/2];
entry_t touch_entry;
while (fgets(line, LINE_SIZE, f) != NULL) {
sp = line;
// ID
do {
int id;
int e = sscanf(line, "%d\t", &id);
if (e == 1) { // normal entry
if (id < 0
|| id > entries.n) {
errorn(E_INDEX, id);
goto recovery;
}
entry = &kv_A(entries, id);
NEXT_FIELD;
} else { // creation
char * const saved_sp = sp;
// skip to the name
if (do_permissions) { NEXT_FIELD; }
if (do_owner) { NEXT_FIELD; }
CHECK_FORMAT(1, sscanf(sp, "%s\n", touch_buffer));
mytouch(touch_buffer);
struct stat file_stat;
int es = stat(touch_buffer, &file_stat);
CHECK_OPEN(!(es == -1), touch_buffer, goto recovery);
touch_entry = (entry_t) {
.name = touch_buffer,
.st = file_stat,
.is_mentioned = false,
};
entry = &touch_entry;
sp = saved_sp;
}
} while (0);
// Copy
if (entry->is_mentioned) {
char * const saved_sp = sp;
// skip to the name
if (do_permissions) { NEXT_FIELD; }
if (do_owner) { NEXT_FIELD; }
CHECK_FORMAT(1, sscanf(sp, "%s\n", buffer));
mycopy(entry->name, buffer);
sp = saved_sp;
}
// Permission
if (do_permissions) {
mode_t mode;
CHECK_FORMAT(1, sscanf(sp, "%s\t", buffer));
mode = str_to_mode(buffer);
if (entry->st.st_mode != mode) {
CHECK(mychmod(entry->name, mode));
}
NEXT_FIELD;
}
// Owner
if (do_owner) {
char buffer2[113];
CHECK_FORMAT(2, sscanf(sp, "%[^:]:%s\t", buffer, buffer2));
struct passwd * usr = getpwuid(entry->st.st_uid);
struct group * grp = getgrgid(entry->st.st_gid);
if (strcmp(usr->pw_name, buffer)
|| strcmp(grp->gr_name, buffer2)) {
CHECK(mychown(entry->name, buffer, buffer2));
}
NEXT_FIELD;
}
// Name (move)
if (!entry->is_mentioned) {
CHECK_FORMAT(1, sscanf(sp, "%s\n", buffer));
size_t len = strlen(buffer);
if (buffer[len-1] == '/') {
buffer[len-1] = '\0';
}
if (strcmp(entry->name, buffer)) {
if (access(buffer, F_OK)) {
mymove(entry->name, buffer);
} else {
move_data_t move = mytempmove(entry->name, buffer);
if (!move.orig_name) {
errorn(E_FILE_SWAP, entry->name, buffer);
goto recovery;
}
kv_push(move_data_t, move_data, move);
}
}
}
// -- Poke
entry->is_mentioned = true;
}
// Deletion
for (int i = 0; i < entries.n; i++) {
entry_t * entry = &kv_A(entries, i);
if (!entry->is_mentioned) {
CHECK(mydelete(entry->name));
}
}
// Swap (move)
for (int i = 0; i < move_data.n; i++) {
move_data_t move = kv_A(move_data, i);
// NOTE: we could be overwritting here;
// thats the behaviour the user would expect
int result = mymove(move.curt_name, move.dest_name);
// on the otherhand, upon error,
// you dont want your files replaced
if (result
&& !access(move.orig_name, F_OK)) {
// the result of this is intentionally unchecked
mymove(move.curt_name, move.orig_name);
}
}
#undef NEXT_FIELD
#undef CHECK
#undef CHECK_FORMAT
return 0;
recovery:
/* If an error is encountered, we wish to leave the filesystem in a "valid" state.
* Therefor, files waiting to be swapped (possessing a temporary name) are restored back
* (if possible, if we run into another error, theres not much to do).
*/
for (int i = 0; i < move_data.n; i++) {
move_data_t move = kv_A(move_data, i);
if (!access(move.orig_name, F_OK)) {
mymove(move.curt_name, move.orig_name);
}
}
return 1;
}