|
|
#include "../../unity/unity.h" |
|
|
|
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <fcntl.h> |
|
|
#include <errno.h> |
|
|
#include <sys/stat.h> |
|
|
#include <limits.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char g_tmpdir[PATH_MAX]; |
|
|
|
|
|
static void reset_du_state_defaults(void) |
|
|
{ |
|
|
|
|
|
apparent_size = true; |
|
|
opt_count_all = false; |
|
|
hash_all = false; |
|
|
opt_all = false; |
|
|
opt_nul_terminate_output = false; |
|
|
print_grand_total = false; |
|
|
opt_separate_dirs = false; |
|
|
max_depth = IDX_MAX; |
|
|
opt_threshold = 0; |
|
|
human_output_opts = 0; |
|
|
opt_inodes = false; |
|
|
opt_time = false; |
|
|
time_type = time_mtime; |
|
|
time_style = NULL; |
|
|
time_format = NULL; |
|
|
output_block_size = 1; |
|
|
exclude = NULL; |
|
|
|
|
|
|
|
|
duinfo_init(&tot_dui); |
|
|
prev_level = 0; |
|
|
|
|
|
|
|
|
di_files = di_set_alloc(); |
|
|
} |
|
|
|
|
|
static int ensure_dir(const char *path) |
|
|
{ |
|
|
if (mkdir(path, 0700) == 0) return 0; |
|
|
if (errno == EEXIST) return 0; |
|
|
return -1; |
|
|
} |
|
|
|
|
|
static int create_file_with_size(const char *path, size_t nbytes) |
|
|
{ |
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); |
|
|
if (fd < 0) return -1; |
|
|
if (nbytes > 0) { |
|
|
|
|
|
size_t remaining = nbytes; |
|
|
char buf[4096]; |
|
|
memset(buf, 'X', sizeof buf); |
|
|
while (remaining > 0) { |
|
|
size_t chunk = remaining < sizeof buf ? remaining : sizeof buf; |
|
|
ssize_t w = write(fd, buf, chunk); |
|
|
if (w < 0) { close(fd); return -1; } |
|
|
remaining -= (size_t)w; |
|
|
} |
|
|
} |
|
|
if (fsync(fd) != 0) { } |
|
|
return close(fd); |
|
|
} |
|
|
|
|
|
static int remove_tree(const char *root) |
|
|
{ |
|
|
char *argv[2]; |
|
|
argv[0] = (char *)root; |
|
|
argv[1] = NULL; |
|
|
FTS *fts = xfts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); |
|
|
if (!fts) return -1; |
|
|
int ret = 0; |
|
|
while (1) { |
|
|
FTSENT *e = fts_read(fts); |
|
|
if (!e) break; |
|
|
switch (e->fts_info) { |
|
|
case FTS_DP: |
|
|
if (rmdir(e->fts_path) != 0) ret = -1; |
|
|
break; |
|
|
case FTS_F: |
|
|
case FTS_SL: |
|
|
case FTS_SLNONE: |
|
|
case FTS_DEFAULT: |
|
|
case FTS_NS: |
|
|
case FTS_NSOK: |
|
|
case FTS_ERR: |
|
|
if (unlink(e->fts_path) != 0) ret = -1; |
|
|
break; |
|
|
default: |
|
|
break; |
|
|
} |
|
|
} |
|
|
if (fts_close(fts) != 0) ret = -1; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
typedef struct { |
|
|
int saved_fd; |
|
|
FILE *tmpf; |
|
|
} capture_ctx_t; |
|
|
|
|
|
static int begin_capture_stdout(capture_ctx_t *ctx) |
|
|
{ |
|
|
fflush(stdout); |
|
|
ctx->saved_fd = dup(fileno(stdout)); |
|
|
if (ctx->saved_fd < 0) return -1; |
|
|
ctx->tmpf = tmpfile(); |
|
|
if (!ctx->tmpf) { close(ctx->saved_fd); ctx->saved_fd = -1; return -1; } |
|
|
if (dup2(fileno(ctx->tmpf), fileno(stdout)) < 0) { |
|
|
fclose(ctx->tmpf); |
|
|
close(ctx->saved_fd); |
|
|
ctx->tmpf = NULL; |
|
|
ctx->saved_fd = -1; |
|
|
return -1; |
|
|
} |
|
|
return 0; |
|
|
} |
|
|
|
|
|
static char *end_capture_stdout(capture_ctx_t *ctx, size_t *out_len) |
|
|
{ |
|
|
|
|
|
fflush(stdout); |
|
|
dup2(ctx->saved_fd, fileno(stdout)); |
|
|
close(ctx->saved_fd); |
|
|
ctx->saved_fd = -1; |
|
|
|
|
|
fflush(ctx->tmpf); |
|
|
long len; |
|
|
char *buf = NULL; |
|
|
if (fseek(ctx->tmpf, 0, SEEK_END) == 0) { |
|
|
len = ftell(ctx->tmpf); |
|
|
if (len < 0) len = 0; |
|
|
if (fseek(ctx->tmpf, 0, SEEK_SET) == 0) { |
|
|
buf = (char *)malloc((size_t)len + 1); |
|
|
if (buf) { |
|
|
size_t rd = fread(buf, 1, (size_t)len, ctx->tmpf); |
|
|
buf[rd] = '\0'; |
|
|
if (out_len) *out_len = rd; |
|
|
} |
|
|
} |
|
|
} |
|
|
fclose(ctx->tmpf); |
|
|
ctx->tmpf = NULL; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
|
|
|
void setUp(void) |
|
|
{ |
|
|
|
|
|
snprintf(g_tmpdir, sizeof g_tmpdir, "/tmp/du_test_%ld_XXXXXX", (long)getpid()); |
|
|
char *p = mkdtemp(g_tmpdir); |
|
|
TEST_ASSERT_MESSAGE(p != NULL, "mkdtemp failed"); |
|
|
} |
|
|
|
|
|
void tearDown(void) |
|
|
{ |
|
|
|
|
|
if (g_tmpdir[0]) { |
|
|
remove_tree(g_tmpdir); |
|
|
|
|
|
g_tmpdir[0] = '\0'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static FTS *open_fts_single(const char *path) |
|
|
{ |
|
|
char *files[2]; |
|
|
files[0] = (char *)path; |
|
|
files[1] = NULL; |
|
|
return xfts_open(files, FTS_PHYSICAL | FTS_NOCHDIR, NULL); |
|
|
} |
|
|
|
|
|
void test_process_file_regular_file_basic(void) |
|
|
{ |
|
|
reset_du_state_defaults(); |
|
|
|
|
|
char path[PATH_MAX]; |
|
|
snprintf(path, sizeof path, "%s/file_basic.bin", g_tmpdir); |
|
|
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 7)); |
|
|
|
|
|
FTS *fts = open_fts_single(path); |
|
|
TEST_ASSERT_NOT_NULL(fts); |
|
|
|
|
|
FTSENT *ent = fts_read(fts); |
|
|
TEST_ASSERT_NOT_NULL(ent); |
|
|
|
|
|
capture_ctx_t cap; memset(&cap, 0, sizeof cap); |
|
|
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap)); |
|
|
|
|
|
|
|
|
bool ok = process_file(fts, ent); |
|
|
|
|
|
size_t out_len = 0; |
|
|
char *out = end_capture_stdout(&cap, &out_len); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
|
|
|
char expected[PATH_MAX + 64]; |
|
|
snprintf(expected, sizeof expected, "7\t%s\n", path); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_UINT64(7u, (unsigned long long)tot_dui.size); |
|
|
|
|
|
free(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
|
|
} |
|
|
|
|
|
void test_process_file_threshold_suppresses_output(void) |
|
|
{ |
|
|
reset_du_state_defaults(); |
|
|
|
|
|
char path[PATH_MAX]; |
|
|
snprintf(path, sizeof path, "%s/file_thresh.bin", g_tmpdir); |
|
|
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 10)); |
|
|
|
|
|
|
|
|
opt_threshold = 11; |
|
|
|
|
|
FTS *fts = open_fts_single(path); |
|
|
TEST_ASSERT_NOT_NULL(fts); |
|
|
FTSENT *ent = fts_read(fts); |
|
|
TEST_ASSERT_NOT_NULL(ent); |
|
|
|
|
|
capture_ctx_t cap; memset(&cap, 0, sizeof cap); |
|
|
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap)); |
|
|
|
|
|
bool ok = process_file(fts, ent); |
|
|
|
|
|
size_t out_len = 0; |
|
|
char *out = end_capture_stdout(&cap, &out_len); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_UINT(0u, (unsigned int)out_len); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_UINT64(10u, (unsigned long long)tot_dui.size); |
|
|
|
|
|
free(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
|
|
} |
|
|
|
|
|
void test_process_file_nul_terminated_and_inodes(void) |
|
|
{ |
|
|
reset_du_state_defaults(); |
|
|
|
|
|
char path[PATH_MAX]; |
|
|
snprintf(path, sizeof path, "%s/file_inodes.bin", g_tmpdir); |
|
|
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 123)); |
|
|
|
|
|
|
|
|
opt_inodes = true; |
|
|
opt_nul_terminate_output = true; |
|
|
|
|
|
FTS *fts = open_fts_single(path); |
|
|
TEST_ASSERT_NOT_NULL(fts); |
|
|
FTSENT *ent = fts_read(fts); |
|
|
TEST_ASSERT_NOT_NULL(ent); |
|
|
|
|
|
capture_ctx_t cap; memset(&cap, 0, sizeof cap); |
|
|
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap)); |
|
|
|
|
|
bool ok = process_file(fts, ent); |
|
|
|
|
|
size_t out_len = 0; |
|
|
char *out = end_capture_stdout(&cap, &out_len); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
|
|
|
size_t expected_len = strlen("1\t") + strlen(path) + 1; |
|
|
TEST_ASSERT_EQUAL_UINT((unsigned int)expected_len, (unsigned int)out_len); |
|
|
|
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT('1', out[0]); |
|
|
TEST_ASSERT_EQUAL_INT('\t', out[1]); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, strncmp(out + 2, path, strlen(path))); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT('\0', out[out_len - 1]); |
|
|
|
|
|
free(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
|
|
} |
|
|
|
|
|
void test_process_file_hash_all_skips_duplicate(void) |
|
|
{ |
|
|
reset_du_state_defaults(); |
|
|
|
|
|
char path[PATH_MAX]; |
|
|
snprintf(path, sizeof path, "%s/file_dup.bin", g_tmpdir); |
|
|
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 3)); |
|
|
|
|
|
|
|
|
hash_all = true; |
|
|
|
|
|
FTS *fts = open_fts_single(path); |
|
|
TEST_ASSERT_NOT_NULL(fts); |
|
|
FTSENT *ent = fts_read(fts); |
|
|
TEST_ASSERT_NOT_NULL(ent); |
|
|
|
|
|
capture_ctx_t cap; memset(&cap, 0, sizeof cap); |
|
|
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap)); |
|
|
|
|
|
|
|
|
bool ok1 = process_file(fts, ent); |
|
|
|
|
|
bool ok2 = process_file(fts, ent); |
|
|
|
|
|
size_t out_len = 0; |
|
|
char *out = end_capture_stdout(&cap, &out_len); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok1); |
|
|
TEST_ASSERT_TRUE(ok2); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
|
|
|
unsigned nl_count = 0; |
|
|
for (size_t i = 0; i < out_len; i++) if (out[i] == '\n') nl_count++; |
|
|
TEST_ASSERT_EQUAL_UINT(1u, nl_count); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_UINT64(3u, (unsigned long long)tot_dui.size); |
|
|
|
|
|
free(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
|
|
} |
|
|
|
|
|
void test_process_file_opt_all_prints_child_file_line(void) |
|
|
{ |
|
|
reset_du_state_defaults(); |
|
|
|
|
|
|
|
|
char dir[PATH_MAX]; |
|
|
snprintf(dir, sizeof dir, "%s/dirA", g_tmpdir); |
|
|
TEST_ASSERT_EQUAL_INT(0, ensure_dir(dir)); |
|
|
|
|
|
char child[PATH_MAX]; |
|
|
snprintf(child, sizeof child, "%s/child.txt", dir); |
|
|
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(child, 42)); |
|
|
|
|
|
opt_all = true; |
|
|
|
|
|
char *files[2]; |
|
|
files[0] = dir; |
|
|
files[1] = NULL; |
|
|
FTS *fts = xfts_open(files, FTS_PHYSICAL | FTS_NOCHDIR, NULL); |
|
|
TEST_ASSERT_NOT_NULL(fts); |
|
|
|
|
|
capture_ctx_t cap; memset(&cap, 0, sizeof cap); |
|
|
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap)); |
|
|
|
|
|
|
|
|
FTSENT *ent; |
|
|
while ((ent = fts_read(fts)) != NULL) { |
|
|
(void)process_file(fts, ent); |
|
|
} |
|
|
|
|
|
size_t out_len = 0; |
|
|
char *out = end_capture_stdout(&cap, &out_len); |
|
|
|
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
|
|
|
|
|
|
char expected[PATH_MAX + 64]; |
|
|
snprintf(expected, sizeof expected, "42\t%s\n", child); |
|
|
TEST_ASSERT_NOT_NULL(strstr(out, expected)); |
|
|
|
|
|
free(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, fts_close(fts)); |
|
|
} |
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_process_file_regular_file_basic); |
|
|
RUN_TEST(test_process_file_threshold_suppresses_output); |
|
|
RUN_TEST(test_process_file_nul_terminated_and_inodes); |
|
|
RUN_TEST(test_process_file_hash_all_skips_duplicate); |
|
|
RUN_TEST(test_process_file_opt_all_prints_child_file_line); |
|
|
return UNITY_END(); |
|
|
} |