| #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(); |
| } |