int main(int argc, char *argv[]) { size_t path_len, total_files; off_t bytes_wasted, total_wasted; char path_buffer[PATH_MAX_LEN], *hash_value; struct file_entry_t *file_entry, *trie_entry; SListIterator slist_iterator; SetIterator set_iterator; /* Step 0: Session data */ struct file_info_t file_info; clear_info(&file_info); /* Step 1: Parse arguments */ while (--argc) { /* Being unable to record implies insufficient resources */ if (!record(argv[argc], &file_info)){ fprintf(stderr, "[FATAL] out of memory\n"); destroy_info(&file_info); return (EXIT_FAILURE); } } /* Step 2: Fully explore any directories specified */ #ifndef NDEBUG printf("[DEBUG] Creating file list...\n"); #endif while (slist_length(file_info.file_stack) > 0) { /* Pick off the top of the file stack */ file_entry = (struct file_entry_t *)(slist_data(file_info.file_stack)); slist_remove_entry(&file_info.file_stack, file_info.file_stack); assert(file_entry->type == DIRECTORY); /* Copy the basename to a buffer */ memset(path_buffer, '\0', PATH_MAX_LEN); path_len = strnlen(file_entry->path, PATH_MAX_LEN); memcpy(path_buffer, file_entry->path, path_len); /* Ignore cases that would cause overflow */ if (path_len < PATH_MAX_LEN) { /* Append a trailing slash */ path_buffer[path_len] = '/'; /* Record all contents (may push onto file stack or one of the lists) */ DIR *directory = opendir(file_entry->path); if (traverse(&file_info, directory, path_buffer, ++path_len)) { fprintf(stderr, "[FATAL] out of memory\n"); destroy_info(&file_info); return (EXIT_FAILURE); } else if (closedir(directory)) { fprintf(stderr, "[WARNING] '%s' (close failed)\n", file_entry->path); } } /* Discard this entry */ destroy_entry(file_entry); } /* Step 3: Warn about any ignored files */ if (slist_length(file_info.bad_files) > 0) { slist_iterate(&file_info.bad_files, &slist_iterator); while (slist_iter_has_more(&slist_iterator)) { file_entry = slist_iter_next(&slist_iterator); fprintf(stderr, "[WARNING] '%s' ", file_entry->path); switch (file_entry->type) { case INVALID: ++file_info.invalid_files; fprintf(stderr, "(invalid file)\n"); break; case INACCESSIBLE: ++file_info.protected_files; fprintf(stderr, "(protected file)\n"); break; default: ++file_info.irregular_files; fprintf(stderr, "(irregular file)\n"); break; } } fprintf(stderr, "[WARNING] %lu file(s) ignored\n", (long unsigned)(num_errors(&file_info))); } #ifndef NDEBUG if (num_errors(&file_info) > 0) { fprintf(stderr, "[FATAL] cannot parse entire file tree\n"); destroy_info(&file_info); return (EXIT_FAILURE); } printf("[DEBUG] Found %lu / %lu valid files\n", (unsigned long)(num_files(&file_info)), (unsigned long)(file_info.total_files)); #endif /* Step 4: Begin the filtering process */ #ifndef NDEBUG printf("[DEBUG] Creating file table...\n"); #endif if (slist_length(file_info.good_files) > 0) { file_info.hash_trie = trie_new(); file_info.shash_trie = trie_new(); optimize_filter(&file_info); /* Extract each file from the list (they should all be regular) */ slist_iterate(&file_info.good_files, &slist_iterator); while (slist_iter_has_more(&slist_iterator)) { file_entry = slist_iter_next(&slist_iterator); assert(file_entry->type == REGULAR); /* Perform a "shallow" hash of the file */ hash_value = hash_entry(file_entry, SHALLOW); #ifndef NDEBUG printf("[SHASH] %s\t*%s\n", file_entry->path, hash_value); #endif /* Check to see if we might have seen this file before */ if (bloom_filter_query(file_info.shash_filter, hash_value)) { /* Get the full hash of the new file */ hash_value = hash_entry(file_entry, FULL); #ifndef NDEBUG printf("[+HASH] %s\t*%s\n", file_entry->path, hash_value); #endif archive(&file_info, file_entry); /* Check to see if bloom failed us */ trie_entry = trie_lookup(file_info.shash_trie, file_entry->shash); if (trie_entry == TRIE_NULL) { #ifndef NDEBUG printf("[DEBUG] '%s' (false positive)\n", file_entry->path); #endif trie_insert(file_info.shash_trie, file_entry->shash, file_entry); } else { /* Get the full hash of the old file */ hash_value = hash_entry(trie_entry, FULL); #ifndef NDEBUG if (hash_value) { printf("[-HASH] %s\t*%s\n", trie_entry->path, hash_value); } #endif archive(&file_info, trie_entry); } } else { /* Add a record of this shash to the filter */ bloom_filter_insert(file_info.shash_filter, hash_value); trie_insert(file_info.shash_trie, hash_value, file_entry); } } persist("bloom_store", &file_info); } /* Step 5: Output results and cleanup before exit */ printf("[EXTRA] Found %lu sets of duplicates...\n", (unsigned long)(slist_length(file_info.duplicates))); slist_iterate(&file_info.duplicates, &slist_iterator); for (total_files = total_wasted = bytes_wasted = 0; slist_iter_has_more(&slist_iterator); total_wasted += bytes_wasted) { Set *set = slist_iter_next(&slist_iterator); int size = set_num_entries(set); if (size < 2) { continue; } printf("[EXTRA] %lu files (w/ same hash):\n", (unsigned long)(size)); set_iterate(set, &set_iterator); for (bytes_wasted = 0; set_iter_has_more(&set_iterator); bytes_wasted += file_entry->size, ++total_files) { file_entry = set_iter_next(&set_iterator); printf("\t%s (%lu bytes)\n", file_entry->path, (unsigned long)(file_entry->size)); } } printf("[EXTRA] %lu bytes in %lu files (wasted)\n", (unsigned long)(total_wasted), (unsigned long)(total_files)); destroy_info(&file_info); return (EXIT_SUCCESS); }
/* Main execution loop in live mode. Builds the list of processes, * collects statistics, and prints using curses. Repeats after some * delay, also catching key presses. */ static int live_mode(struct process_list* proc_list, screen_t* screen) { WINDOW* help_win = NULL; WINDOW* error_win = NULL; fd_set fds; struct process** p; int num_iter = 0; int with_colors = 0; int pos; /* start curses */ initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); /* Prepare help window */ help_win = prepare_help_win(screen); if (has_colors()) { /* initialize curses colors */ with_colors = 1; start_color(); init_pair(1, COLOR_BLACK, COLOR_WHITE); init_pair(2, COLOR_WHITE, COLOR_BLACK); init_pair(3, COLOR_GREEN, COLOR_BLACK); init_pair(4, COLOR_YELLOW, COLOR_BLACK); init_pair(5, COLOR_RED, COLOR_BLACK); attron(COLOR_PAIR(0)); } tv.tv_sec = 0; tv.tv_usec = 200000; /* 200 ms for first iteration */ header = gen_header(screen, &options, COLS - 1, active_col); pos = screen_pos(screen); for(num_iter=0; !options.max_iter || num_iter<options.max_iter; num_iter++) { int i, zz, printed, num_fd, num_dead; /* print various info */ erase(); mvprintw(0, 0, "tiptop -"); if ((num_errors() > 0) && (COLS >= 37)) mvprintw(LINES-1, 30, "[errors]"); if ((options.config_file == 1) && (COLS >= 60)) mvprintw(0, COLS-60, "[conf]"); if ((options.euid == 0) && (COLS >= 54)) mvprintw(0, COLS-54, "[root]"); if ((options.watch_uid != -1) && (COLS >= 48)) mvprintw(0, COLS-48, "[uid]"); if ((options.only_pid || options.only_name) && (COLS >= 43)) mvprintw(0, COLS-43, "[pid]"); if (options.show_kernel && (COLS >= 38)) mvprintw(0, COLS-38, "[kernel]"); if (options.sticky && (COLS >= 30)) mvprintw(0, COLS-30, "[sticky]"); if (options.show_threads && (COLS >= 22)) mvprintw(0, COLS-22, "[threads]"); if (options.idle && (COLS >= 13)) mvprintw(0, COLS-13, "[idle]"); if (options.debug && (COLS >= 7)) mvprintw(0, COLS-7, "[debug]"); if (options.show_epoch && (COLS >= 18)) mvprintw(LINES-1, COLS-18, "Epoch: %u", time(NULL)); if (options.show_timestamp) mvprintw(LINES-1, 0, "Iteration: %u", num_iter); /* print main header */ if (with_colors) attron(COLOR_PAIR(1)); mvprintw(3, 0, "%s", header); for(zz=strlen(header); zz < COLS-1; zz++) printw(" "); printw("\n"); if (with_colors) attroff(COLOR_PAIR(1)); /* update the list of processes/threads and accumulate info if needed */ num_dead = update_proc_list(proc_list, screen, &options); if (!options.show_threads) accumulate_stats(proc_list); p = proc_list->proc_ptrs; /* prepare for select */ FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); /* generate the text version of all rows */ build_rows(proc_list, screen, COLS - 1); /* sort by %CPU */ qsort(p, proc_list->num_tids, sizeof(struct process*), sorting_fun); printed = 0; /* Iterate over all threads */ for(i=0; i < proc_list->num_tids; i++) { if (p[i]->skip) continue; /* highlight watched process, if any */ if (with_colors) { if (p[i]->dead) { attron(COLOR_PAIR(5)); } else if ((p[i]->tid == options.watch_pid) || (options.watch_name && options.show_cmdline && strstr(p[i]->cmdline, options.watch_name)) || (options.watch_name && !options.show_cmdline && strstr(p[i]->name, options.watch_name))) attron(COLOR_PAIR(3)); } if (options.show_threads || (p[i]->pid == p[i]->tid)) { printw("%s\n", p[i]->txt); printed++; } if (with_colors) attroff(COLOR_PAIR(3)); if (printed >= LINES - 5) /* stop printing at bottom of window */ break; } mvprintw(1, 0, "Tasks: %3d total, %3d displayed", proc_list->num_tids, printed); if (options.sticky) printw(", %3d dead", num_dead); /* print the screen name, make sure it fits, or truncate */ if (with_colors) attron(COLOR_PAIR(4)); if (35 + 20 + 11 + strlen(screen->name) < COLS) { mvprintw(1, COLS - 11 - strlen(screen->name), "screen %2d: %s\n", pos, screen->name); } else if (COLS >= 35 + 20 + 11) { char screen_str[50] = { 0 }; snprintf(screen_str, sizeof(screen_str) - 1, "%s\n", screen->name); screen_str[COLS - 35 - 20 - 11] = '\0'; /* truncate */ mvprintw(1, 35+20, "screen %2d: %s", pos, screen_str); } if (with_colors) attroff(COLOR_PAIR(4)); /* print message if any */ if (message) { if (with_colors) attron(COLOR_PAIR(1)); mvprintw(2, 0, "%s", message); if (with_colors) attroff(COLOR_PAIR(1)); message = NULL; /* reset message */ } refresh(); /* display everything */ if (options.error) { if (options.error == 1) { options.error = 2; show_error_win(error_win, printed); } else show_error_win(error_win, -1); } if (options.help) show_help_win(help_win, screen); if ((num_dead) && (!options.sticky)) compact_proc_list(proc_list); /* wait some delay, or until a key is pressed */ num_fd = select(1 + STDIN_FILENO, &fds, NULL, NULL, &tv); if (num_fd > 0) { int c = handle_key(); if (c == 'q') break; if (c == '>') { if (active_col < screen->num_columns ) active_col++; free(header); header = gen_header(screen, &options, COLS - 1, active_col); } if (c == '<') { if (active_col > -1) active_col--; free(header); header = gen_header(screen, &options, COLS - 1, active_col); } if (c == 'H') { if (options.show_threads) { reset_values(proc_list); message = "Show threads On"; } else message = "Show threads Off"; } if (c == 'U') { free(header); header = gen_header(screen, &options, COLS - 1, active_col); } if ((c == '+') || (c == '-') || (c == KEY_LEFT) || (c == KEY_RIGHT)) return c; if ((c == 'u') || (c == 'K') || (c == 'p')) /* need to rebuild tasks list */ return c; if (c == 'e') { if (options.error > 0) { options.error = 0; delwin(error_win); error_win = NULL; } else options.error = 1; } } tv.tv_sec = options.delay; tv.tv_usec = (options.delay - tv.tv_sec) * 1000000.0; } free(header); delwin(help_win); endwin(); /* stop curses */ return 'q'; }