#include #include #include #include #include #include #include #include #include #include #include #define BUFFER_LEN 4096 static void handle_event(int inotify_instance, int *watched, char **filenames, int *counter, size_t watched_len); static char** filenames_in_dir(char *path, size_t *return_len); static char** filenames_in_path_envvar(size_t *return_len); static void free_null_terminated_pointer_array(char **ptr); static void print_event(uint32_t event); static void save_result(char **filenames, int* counter, size_t arr_len); /* This code is a bit over-commented in general */ int main(int argc, char * const argv[]) { /* Get null terminated list of files to watch, create counter of same size */ char **file_names; int *use_counter, *watched; size_t files_len; switch (argc) { case 1: file_names = filenames_in_path_envvar(&files_len); break; case 2: file_names = filenames_in_dir(argv[1], &files_len); break; default: errx(EXIT_FAILURE, "Usage: `%s `. If dir is empty, directories in PATH will be monitored", argv[0]); } if (file_names == NULL) err(EXIT_FAILURE, "Error when finding files"); use_counter = calloc(files_len, sizeof(int)); if( use_counter == NULL ) err(EXIT_FAILURE, "Failed to allocate %zu bytes for use counter", files_len*sizeof(int)); /* Create file descriptor for accessing the inotify API */ int inotify_instance = inotify_init(); if ( inotify_instance == -1 ) err(EXIT_FAILURE, "Failed to initialize inotify instance"); /* Mark files for events */ watched = calloc(files_len, sizeof(int)); if( watched == NULL ) err(EXIT_FAILURE, "Failed to allocate %zu bytes for watch descriptors", files_len*sizeof(int)); for(size_t i=0; file_names[i] != NULL; i++) { watched[i] = inotify_add_watch(inotify_instance, file_names[i], IN_ACCESS); if(watched[i] == -1) err(EXIT_FAILURE, "Cannot watch %s", file_names[i]); } /* Prepare for polling */ struct pollfd poll_fds[2]; nfds_t fd_count = 2; poll_fds[0].fd = STDIN_FILENO; poll_fds[0].events = POLLIN; poll_fds[1].fd = inotify_instance; poll_fds[1].events = POLLIN; /* Wait for events and/or terminal input */ int poll_num; fprintf(stderr, "Listening for events... (Press enter to end)\n"); while (1) { poll_num = poll(poll_fds, fd_count, -1); if( poll_num == -1 && errno != EINTR) err(EXIT_FAILURE, "Failed to poll"); if (poll_num == 0) continue; if (poll_fds[0].revents & POLLIN) { char c; while (read(STDIN_FILENO, &c, 1) > 0 && c != '\n') /* noop */; save_result(file_names, use_counter, files_len); break; } if( poll_fds[1].revents & POLLIN ) { handle_event(inotify_instance, watched, file_names, use_counter, files_len); } } close(inotify_instance); free_null_terminated_pointer_array(file_names); free(watched); free(use_counter); return EXIT_SUCCESS; } /* Free allocated memory pointed to by pointers in 'ptr', then free 'ptr' itself */ static void free_null_terminated_pointer_array(char **ptr) { for(char **p = ptr; *p != NULL; p++ ){ free(*p); } free(ptr); } /* Returns a null terminated array of strings for every file in PATH */ static char** filenames_in_path_envvar(size_t *return_len) { size_t n_files, i, output_size; char **filenames, **output, *saveptr, *path, *token; void *tmp; path = strdup(getenv("PATH")); saveptr = NULL; output_size = 1024; output = malloc(output_size * sizeof(char*)); if (output == NULL) { warn("Failed to allocate %zu bytes", output_size); return NULL; } token = strtok_r(path, ":", &saveptr); i = 0; while (token != NULL) { n_files = 0; filenames = filenames_in_dir(token, &n_files); n_files -= 1; if ( filenames == NULL ) { fprintf(stderr, "failed to get filenames in %s\n", token); token = strtok_r(NULL, ":", &saveptr); continue; } if (i + n_files > output_size) { while(i + n_files > output_size) output_size *= 2; tmp = realloc(output, output_size * sizeof(char*)); if (tmp == NULL) { warn("failed to reallocate %zu bytes", output_size * sizeof(char*)); return NULL; } output = (char**) tmp; } memcpy(output+i, filenames, n_files * sizeof(char*)); i += n_files; free(filenames); token = strtok_r(NULL, ":", &saveptr); } output_size = i + 1; tmp = realloc(output, output_size * sizeof(char*)); if (tmp != NULL){ output = (char**)tmp; } output[i] = NULL; free(path); *return_len = output_size; return output; } /* Returns null terminated array of filepaths in directory */ static char** filenames_in_dir(char *path, size_t *return_len) { size_t result_size, n_items=0; char **result; void *tmp; struct dirent *dir_entity; DIR *directory_stream; result_size = 64; result = malloc(result_size * sizeof(char*)); if( result == NULL ){ warn("Failed to malloc %zu bytes", result_size); return NULL; } directory_stream = opendir(path); if (directory_stream == NULL) { warn("Failed to open directory %s", path); goto fail; } n_items = 0; errno = 0; // To distinguish end of stream from an error, // set errno to zero before calling readdir() while ((dir_entity = readdir(directory_stream)) != NULL) { if(n_items >= result_size){ result_size *= 2; result = realloc(result, result_size * sizeof(char*)); } if (dir_entity->d_type == DT_REG ) { result[n_items] = calloc(strlen(path) + strlen(dir_entity->d_name) + 2, sizeof(char)); result[n_items] = strcat(result[n_items], path); result[n_items] = strcat(result[n_items], "/"); result[n_items] = strcat(result[n_items], dir_entity->d_name); n_items += 1; } // dirent *dir_entity is statically allocated, dont free()! } result_size = n_items+1; tmp = realloc(result, result_size * sizeof(char*)); if ( tmp == NULL ) { warn("Failed to realloc %zu bytes", result_size); return NULL; } result = (char**)tmp; result[n_items] = NULL; if ( errno != 0 ){ warn("Error when reading directory %s", path); goto fail; } if ( closedir(directory_stream) == -1 ) warn("Failed to close directory"); *return_len = result_size; return result; fail: for(size_t i=0; ilen) { event = (struct inotify_event *) p; for (int i=0; i < watched_len; i++) { if( watched[i] == event->wd ) { fprintf(stderr, "%s ", filenames[i]); counter[i] += 1; break; } } } } /* Prints event mask values in a human-readable format */ static void print_event(uint32_t event) { if(event & IN_ACCESS){ fprintf(stderr,"IN_ACCESS "); } if(event & IN_ATTRIB){ fprintf(stderr,"IN_ATTRIB "); } if(event & IN_CLOSE_WRITE){ fprintf(stderr,"IN_CLOSE_WRITE "); } if(event & IN_CLOSE_NOWRITE){ fprintf(stderr,"IN_CLOSE_NOWRITE "); } if(event & IN_CREATE){ fprintf(stderr,"IN_CREATE "); } if(event & IN_DELETE){ fprintf(stderr,"IN_DELETE "); } if(event & IN_DELETE_SELF){ fprintf(stderr,"IN_DELETE_SELF "); } if(event & IN_MODIFY){ fprintf(stderr,"IN_MODIFY "); } if(event & IN_MOVE_SELF){ fprintf(stderr,"IN_MOVE_SELF "); } if(event & IN_MOVED_FROM){ fprintf(stderr,"IN_MOVED_FROM "); } if(event & IN_OPEN){ fprintf(stderr,"IN_OPEN "); } } /* Saves number of recorded events for each executable t 'uses.log' */ static void save_result(char **filenames, int* counter, size_t arr_len) { FILE *savefile; savefile = fopen("uses.log", "w"); if (savefile == NULL) { warn("Failed to save to file"); return; } for (size_t i=0; i