/* SPDX-License-Identifier: GPL-3.0-or-later */
/*
* post_process.c
*
* Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
*/
#include "internal.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
static void alloc_inode_num_dfs(fstree_t *fs, tree_node_t *root)
{
bool has_subdirs = false;
tree_node_t *it;
for (it = root->data.dir.children; it != NULL; it = it->next) {
if (S_ISDIR(it->mode)) {
has_subdirs = true;
break;
}
}
if (has_subdirs) {
for (it = root->data.dir.children; it != NULL; it = it->next) {
if (S_ISDIR(it->mode))
alloc_inode_num_dfs(fs, it);
}
}
for (it = root->data.dir.children; it != NULL; it = it->next) {
if (it->mode != FSTREE_MODE_HARD_LINK_RESOLVED) {
it->inode_num = fs->unique_inode_count + 1;
fs->unique_inode_count += 1;
}
}
}
static int resolve_hard_links_dfs(fstree_t *fs, tree_node_t *n)
{
tree_node_t *it;
if (n->mode == FSTREE_MODE_HARD_LINK) {
if (fstree_resolve_hard_link(fs, n))
goto fail_link;
assert(n->mode == FSTREE_MODE_HARD_LINK_RESOLVED);
it = n->data.target_node;
if (S_ISDIR(it->mode) && it->data.dir.visited)
goto fail_link_loop;
} else if (S_ISDIR(n->mode)) {
n->data.dir.visited = true;
for (it = n->data.dir.children; it != NULL; it = it->next) {
if (resolve_hard_links_dfs(fs, it))
return -1;
}
n->data.dir.visited = false;
}
return 0;
fail_link: {
char *path = fstree_get_path(n);
fprintf(stderr, "Resolving hard link '%s' -> '%s': %s\n",
path == NULL ? n->name : path, n->data.target,
strerror(errno));
free(path);
}
return -1;
fail_link_loop: {
char *npath = fstree_get_path(n);
char *tpath = fstree_get_path(it);
fprintf(stderr, "Hard link loop detected in '%s' -> '%s'\n",
npath == NULL ? n->name : npath,
tpath == NULL ? it->name : tpath);
free(npath);
free(tpath);
}
return -1;
}
static void sort_recursive(tree_node_t *n)
{
n->data.dir.children = tree_node_list_sort(n->data.dir.children);
for (n = n->data.dir.children; n != NULL; n = n->next) {
if (S_ISDIR(n->mode))
sort_recursive(n);
}
}
static file_info_t *file_list_dfs(tree_node_t *n)
{
if (S_ISREG(n->mode)) {
n->data.file.next = NULL;
return &n->data.file;
}
if (S_ISDIR(n->mode)) {
file_info_t *list = NULL, *last = NULL;
for (n = n->data.dir.children; n != NULL; n = n->next) {
if (list == NULL) {
list = file_list_dfs(n);
if (list == NULL)
continue;
last = list;
} else {
last->next = file_list_dfs(n);
}
while (last->next != NULL)
last = last->next;
}
return list;
}
return NULL;
}
static void map_inodes_dfs(fstree_t *fs, tree_node_t *n)
{
if (n->mode == FSTREE_MODE_HARD_LINK_RESOLVED)
return;
fs->inodes[n->inode_num - 1] = n;
if (S_ISDIR(n->mode)) {
for (n = n->data.dir.children; n != NULL; n = n->next)
map_inodes_dfs(fs, n);
}
}
static void reorder_hard_links(fstree_t *fs)
{
size_t i, j, tgt_idx;
tree_node_t *it, *tgt;
for (i = 0; i < fs->unique_inode_count; ++i) {
if (!S_ISDIR(fs->inodes[i]->mode))
continue;
it = fs->inodes[i]->data.dir.children;
for (; it != NULL; it = it->next) {
if (it->mode != FSTREE_MODE_HARD_LINK_RESOLVED)
continue;
tgt = it->data.target_node;
tgt_idx = tgt->inode_num - 1;
if (tgt_idx <= i)
continue;
/* TODO ? */
assert(!S_ISDIR(tgt->mode));
for (j = tgt_idx; j > i; --j) {
fs->inodes[j] = fs->inodes[j - 1];
fs->inodes[j]->inode_num += 1;
}
fs->inodes[i] = tgt;
tgt->inode_num = i + 1;
++i;
}
}
}
int fstree_post_process(fstree_t *fs)
{
sort_recursive(fs->root);
if (resolve_hard_links_dfs(fs, fs->root))
return -1;
fs->unique_inode_count = 0;
alloc_inode_num_dfs(fs, fs->root);
fs->root->inode_num = fs->unique_inode_count + 1;
fs->unique_inode_count += 1;
fs->inodes = calloc(sizeof(fs->inodes[0]), fs->unique_inode_count);
if (fs->inodes == NULL) {
perror("Allocating inode list");
return -1;
}
map_inodes_dfs(fs, fs->root);
reorder_hard_links(fs);
fs->files = file_list_dfs(fs->root);
return 0;
}