| | #include "../../unity/unity.h" |
| |
|
| | #include <stdio.h> |
| | #include <stdlib.h> |
| | #include <string.h> |
| | #include <stdbool.h> |
| | #include <unistd.h> |
| | #include <fcntl.h> |
| | #include <errno.h> |
| | #include <sys/stat.h> |
| | #include <sys/types.h> |
| | #include <dirent.h> |
| | #include <limits.h> |
| |
|
| | |
| | |
| |
|
| | |
| | static char *make_temp_dir(void) |
| | { |
| | char *tmpl = malloc(64); |
| | if (!tmpl) return NULL; |
| | snprintf(tmpl, 64, "/tmp/du_ut_%ld_XXXXXX", (long)getpid()); |
| | if (!mkdtemp(tmpl)) |
| | { |
| | free(tmpl); |
| | return NULL; |
| | } |
| | return tmpl; |
| | } |
| |
|
| | |
| | static void join_path(char *buf, size_t bufsz, const char *a, const char *b) |
| | { |
| | size_t la = strlen(a); |
| | snprintf(buf, bufsz, "%s%s%s", a, (la && a[la-1] == '/') ? "" : "/", b); |
| | } |
| |
|
| | |
| | static int create_file_with_size(const char *path, size_t size) |
| | { |
| | int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644); |
| | if (fd < 0) return -1; |
| | size_t remaining = size; |
| | 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) |
| | { |
| | int e = errno; |
| | close(fd); |
| | errno = e; |
| | return -1; |
| | } |
| | remaining -= (size_t)w; |
| | } |
| | if (close(fd) < 0) return -1; |
| | return 0; |
| | } |
| |
|
| | |
| | static int remove_tree(const char *path) |
| | { |
| | struct stat st; |
| | if (lstat(path, &st) != 0) |
| | return -1; |
| |
|
| | if (S_ISDIR(st.st_mode)) |
| | { |
| | DIR *d = opendir(path); |
| | if (!d) return -1; |
| | struct dirent *de; |
| | while ((de = readdir(d)) != NULL) |
| | { |
| | if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) |
| | continue; |
| | char child[PATH_MAX]; |
| | join_path(child, sizeof child, path, de->d_name); |
| | if (remove_tree(child) != 0) |
| | { |
| | |
| | } |
| | } |
| | closedir(d); |
| | if (rmdir(path) != 0) |
| | return -1; |
| | return 0; |
| | } |
| | else |
| | { |
| | return unlink(path); |
| | } |
| | } |
| |
|
| | |
| | static void reset_du_state(void) |
| | { |
| | |
| | opt_all = false; |
| | apparent_size = true; |
| | opt_count_all = false; |
| | hash_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_format = NULL; |
| | time_style = NULL; |
| | output_block_size = 1; |
| | exclude = NULL; |
| | duinfo_init(&tot_dui); |
| | prev_level = 0; |
| | } |
| |
|
| | |
| | struct CaptureResult { |
| | char *data; |
| | size_t len; |
| | bool ok; |
| | }; |
| |
|
| | |
| | |
| | static struct CaptureResult run_du_and_capture(char **files, int bit_flags) |
| | { |
| | struct CaptureResult res = { NULL, 0, false }; |
| |
|
| | fflush(stdout); |
| | int pipefd[2]; |
| | if (pipe(pipefd) != 0) |
| | { |
| | return res; |
| | } |
| |
|
| | int saved = dup(STDOUT_FILENO); |
| | if (saved < 0) |
| | { |
| | close(pipefd[0]); |
| | close(pipefd[1]); |
| | return res; |
| | } |
| |
|
| | if (dup2(pipefd[1], STDOUT_FILENO) < 0) |
| | { |
| | close(saved); |
| | close(pipefd[0]); |
| | close(pipefd[1]); |
| | return res; |
| | } |
| | close(pipefd[1]); |
| |
|
| | bool ok = du_files(files, bit_flags); |
| |
|
| | fflush(stdout); |
| |
|
| | |
| | dup2(saved, STDOUT_FILENO); |
| | close(saved); |
| |
|
| | |
| | char *buf = NULL; |
| | size_t cap = 0; |
| | size_t len = 0; |
| | char tmp[4096]; |
| | ssize_t r; |
| | while ((r = read(pipefd[0], tmp, sizeof tmp)) > 0) |
| | { |
| | if (len + (size_t)r > cap) |
| | { |
| | size_t newcap = cap ? cap * 2 : 8192; |
| | while (newcap < len + (size_t)r) newcap *= 2; |
| | char *nb = realloc(buf, newcap); |
| | if (!nb) |
| | { |
| | free(buf); |
| | close(pipefd[0]); |
| | res.data = NULL; |
| | res.len = 0; |
| | res.ok = ok; |
| | return res; |
| | } |
| | buf = nb; |
| | cap = newcap; |
| | } |
| | memcpy(buf + len, tmp, (size_t)r); |
| | len += (size_t)r; |
| | } |
| | close(pipefd[0]); |
| |
|
| | res.data = buf; |
| | res.len = len; |
| | res.ok = ok; |
| | return res; |
| | } |
| |
|
| | |
| | |
| | |
| | static unsigned long long find_size_for_path(const char *buf, size_t len, |
| | char term, const char *path) |
| | { |
| | size_t i = 0; |
| | size_t pathlen = strlen(path); |
| | while (i < len) |
| | { |
| | size_t line_start = i; |
| | |
| | while (i < len && buf[i] != term) i++; |
| | size_t line_end = i; |
| | if (line_end > line_start) |
| | { |
| | |
| | const char *tab = memchr(buf + line_start, '\t', line_end - line_start); |
| | if (tab) |
| | { |
| | size_t num_len = (size_t)(tab - (buf + line_start)); |
| | size_t p_len = line_end - (size_t)(tab - buf) - 1; |
| | const char *p = tab + 1; |
| | if (p_len == pathlen && memcmp(p, path, pathlen) == 0) |
| | { |
| | char numstr[64]; |
| | size_t cpy = num_len < sizeof numstr - 1 ? num_len : sizeof numstr - 1; |
| | memcpy(numstr, buf + line_start, cpy); |
| | numstr[cpy] = '\0'; |
| | char *endp = NULL; |
| | unsigned long long v = strtoull(numstr, &endp, 10); |
| | if (endp && *endp == '\0') |
| | return v; |
| | } |
| | } |
| | } |
| | |
| | if (i < len) i++; |
| | } |
| | return (unsigned long long)(~0ULL); |
| | } |
| |
|
| | |
| | void setUp(void) { |
| | |
| | } |
| | void tearDown(void) { |
| | |
| | } |
| |
|
| | |
| |
|
| | static void test_du_files_empty_input_no_output(void) |
| | { |
| | reset_du_state(); |
| |
|
| | char *files[] = { NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| | TEST_ASSERT_EQUAL_UINT64(0, cr.len); |
| | free(cr.data); |
| | } |
| |
|
| | static void test_du_files_single_file_apparent_size(void) |
| | { |
| | reset_du_state(); |
| |
|
| | char *td = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(td); |
| |
|
| | char fpath[PATH_MAX]; |
| | join_path(fpath, sizeof fpath, td, "one.txt"); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(fpath, 7)); |
| |
|
| | char *files[] = { fpath, NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| |
|
| | |
| | unsigned long long sz = find_size_for_path(cr.data, cr.len, '\n', fpath); |
| | TEST_ASSERT_NOT_EQUAL_UINT64((unsigned long long)(~0ULL), sz); |
| | TEST_ASSERT_EQUAL_UINT64(7ULL, sz); |
| |
|
| | free(cr.data); |
| | remove_tree(td); |
| | free(td); |
| | } |
| |
|
| | static void test_du_files_directory_sums_no_all(void) |
| | { |
| | reset_du_state(); |
| |
|
| | char *td = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(td); |
| |
|
| | |
| | char apath[PATH_MAX], bpath[PATH_MAX], sdir[PATH_MAX], cpath[PATH_MAX]; |
| | join_path(apath, sizeof apath, td, "a"); |
| | join_path(bpath, sizeof bpath, td, "b"); |
| | join_path(sdir, sizeof sdir, td, "s"); |
| | TEST_ASSERT_EQUAL_INT(0, mkdir(sdir, 0755)); |
| | join_path(cpath, sizeof cpath, sdir, "c"); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(apath, 3)); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(bpath, 4)); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(cpath, 5)); |
| |
|
| | char *files[] = { td, NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| |
|
| | |
| | unsigned long long sz_s = find_size_for_path(cr.data, cr.len, '\n', sdir); |
| | unsigned long long sz_td = find_size_for_path(cr.data, cr.len, '\n', td); |
| |
|
| | TEST_ASSERT_NOT_EQUAL_UINT64((unsigned long long)(~0ULL), sz_s); |
| | TEST_ASSERT_NOT_EQUAL_UINT64((unsigned long long)(~0ULL), sz_td); |
| | TEST_ASSERT_EQUAL_UINT64(5ULL, sz_s); |
| | TEST_ASSERT_EQUAL_UINT64(12ULL, sz_td); |
| |
|
| | free(cr.data); |
| | remove_tree(td); |
| | free(td); |
| | } |
| |
|
| | static void test_du_files_opt_all_includes_files(void) |
| | { |
| | reset_du_state(); |
| | opt_all = true; |
| |
|
| | char *td = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(td); |
| |
|
| | char apath[PATH_MAX], bpath[PATH_MAX], sdir[PATH_MAX], cpath[PATH_MAX]; |
| | join_path(apath, sizeof apath, td, "a"); |
| | join_path(bpath, sizeof bpath, td, "b"); |
| | join_path(sdir, sizeof sdir, td, "s"); |
| | TEST_ASSERT_EQUAL_INT(0, mkdir(sdir, 0755)); |
| | join_path(cpath, sizeof cpath, sdir, "c"); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(apath, 3)); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(bpath, 4)); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(cpath, 5)); |
| |
|
| | char *files[] = { td, NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| |
|
| | |
| | unsigned long long sz_a = find_size_for_path(cr.data, cr.len, '\n', apath); |
| | unsigned long long sz_b = find_size_for_path(cr.data, cr.len, '\n', bpath); |
| | unsigned long long sz_c = find_size_for_path(cr.data, cr.len, '\n', cpath); |
| | unsigned long long sz_s = find_size_for_path(cr.data, cr.len, '\n', sdir); |
| | unsigned long long sz_td = find_size_for_path(cr.data, cr.len, '\n', td); |
| |
|
| | TEST_ASSERT_EQUAL_UINT64(3ULL, sz_a); |
| | TEST_ASSERT_EQUAL_UINT64(4ULL, sz_b); |
| | TEST_ASSERT_EQUAL_UINT64(5ULL, sz_c); |
| | TEST_ASSERT_EQUAL_UINT64(5ULL, sz_s); |
| | TEST_ASSERT_EQUAL_UINT64(12ULL, sz_td); |
| |
|
| | free(cr.data); |
| | remove_tree(td); |
| | free(td); |
| | } |
| |
|
| | static void test_du_files_max_depth_zero(void) |
| | { |
| | reset_du_state(); |
| | max_depth = 0; |
| |
|
| | char *td = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(td); |
| |
|
| | char sdir[PATH_MAX], cpath[PATH_MAX]; |
| | join_path(sdir, sizeof sdir, td, "s"); |
| | TEST_ASSERT_EQUAL_INT(0, mkdir(sdir, 0755)); |
| | join_path(cpath, sizeof cpath, sdir, "c"); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(cpath, 5)); |
| |
|
| | char *files[] = { td, NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| |
|
| | |
| | unsigned long long sz_td = find_size_for_path(cr.data, cr.len, '\n', td); |
| | unsigned long long sz_s = find_size_for_path(cr.data, cr.len, '\n', sdir); |
| | unsigned long long sz_c = find_size_for_path(cr.data, cr.len, '\n', cpath); |
| |
|
| | TEST_ASSERT_NOT_EQUAL_UINT64((unsigned long long)(~0ULL), sz_td); |
| | TEST_ASSERT_EQUAL_UINT64((unsigned long long)(~0ULL), sz_s); |
| | TEST_ASSERT_EQUAL_UINT64((unsigned long long)(~0ULL), sz_c); |
| | TEST_ASSERT_EQUAL_UINT64(5ULL, sz_td); |
| |
|
| | free(cr.data); |
| | remove_tree(td); |
| | free(td); |
| | } |
| |
|
| | static void test_du_files_threshold_filtering(void) |
| | { |
| | reset_du_state(); |
| | opt_all = true; |
| | opt_threshold = 4; |
| |
|
| | char *td = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(td); |
| |
|
| | char a[PATH_MAX], b[PATH_MAX], cdir[PATH_MAX], c[PATH_MAX]; |
| | join_path(a, sizeof a, td, "a"); |
| | join_path(b, sizeof b, td, "b"); |
| | join_path(cdir, sizeof cdir, td, "cdir"); |
| | TEST_ASSERT_EQUAL_INT(0, mkdir(cdir, 0755)); |
| | join_path(c, sizeof c, cdir, "c"); |
| |
|
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(a, 3)); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(b, 4)); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(c, 5)); |
| |
|
| | char *files[] = { td, NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| |
|
| | |
| | unsigned long long sz_a = find_size_for_path(cr.data, cr.len, '\n', a); |
| | unsigned long long sz_b = find_size_for_path(cr.data, cr.len, '\n', b); |
| | unsigned long long sz_c = find_size_for_path(cr.data, cr.len, '\n', c); |
| | unsigned long long sz_cdir = find_size_for_path(cr.data, cr.len, '\n', cdir); |
| | unsigned long long sz_td = find_size_for_path(cr.data, cr.len, '\n', td); |
| |
|
| | TEST_ASSERT_EQUAL_UINT64((unsigned long long)(~0ULL), sz_a); |
| | TEST_ASSERT_EQUAL_UINT64(4ULL, sz_b); |
| | TEST_ASSERT_EQUAL_UINT64(5ULL, sz_c); |
| | TEST_ASSERT_EQUAL_UINT64(5ULL, sz_cdir); |
| | TEST_ASSERT_EQUAL_UINT64(9ULL, sz_td); |
| |
|
| | free(cr.data); |
| | remove_tree(td); |
| | free(td); |
| | } |
| |
|
| | static void test_du_files_nul_terminated_output(void) |
| | { |
| | reset_du_state(); |
| | opt_nul_terminate_output = true; |
| |
|
| | char *td = make_temp_dir(); |
| | TEST_ASSERT_NOT_NULL(td); |
| |
|
| | char f[PATH_MAX]; |
| | join_path(f, sizeof f, td, "nulf"); |
| | TEST_ASSERT_EQUAL_INT(0, create_file_with_size(f, 10)); |
| |
|
| | char *files[] = { f, NULL }; |
| | struct CaptureResult cr = run_du_and_capture(files, FTS_PHYSICAL | FTS_NOCHDIR); |
| |
|
| | TEST_ASSERT_TRUE(cr.ok); |
| | TEST_ASSERT_TRUE(cr.len >= 2); |
| | |
| | TEST_ASSERT_EQUAL_UINT8(0, (unsigned char)cr.data[cr.len - 1]); |
| |
|
| | |
| | unsigned long long sz = find_size_for_path(cr.data, cr.len, '\0', f); |
| | TEST_ASSERT_EQUAL_UINT64(10ULL, sz); |
| |
|
| | free(cr.data); |
| | remove_tree(td); |
| | free(td); |
| | } |
| |
|
| | int main(void) |
| | { |
| | UNITY_BEGIN(); |
| |
|
| | RUN_TEST(test_du_files_empty_input_no_output); |
| | RUN_TEST(test_du_files_single_file_apparent_size); |
| | RUN_TEST(test_du_files_directory_sums_no_all); |
| | RUN_TEST(test_du_files_opt_all_includes_files); |
| | RUN_TEST(test_du_files_max_depth_zero); |
| | RUN_TEST(test_du_files_threshold_filtering); |
| | RUN_TEST(test_du_files_nul_terminated_output); |
| |
|
| | return UNITY_END(); |
| | } |