From 31aaaca68b15fab61bf8a5e2b0187111fae27f46 Mon Sep 17 00:00:00 2001 From: Thorsten Töpper Date: Sat, 2 Aug 2025 00:02:57 +0200 Subject: symlinks: destination can be resolved --- src/data_management.c | 57 ++++++++++++++++++++ src/dir_monitor.c | 5 ++ src/options.c | 10 ++++ src/output.c | 140 ++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 180 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/data_management.c b/src/data_management.c index 1ee8134..a6d8a0b 100644 --- a/src/data_management.c +++ b/src/data_management.c @@ -9,7 +9,10 @@ #include #include #include +#include +#include #include +#include #include "output.h" #include "data_management.h" @@ -50,6 +53,7 @@ inline struct list_node *create_node(char *fname, struct stat *ln_stat) { length = 255; } node->next = NULL; + node->symlink_target = NULL; explicit_bzero(node->fname, 256); memcpy(node->fname, fname, length); memcpy(&(node->ln_stat), ln_stat, sizeof(struct stat)); @@ -64,10 +68,16 @@ inline void destroy_list(struct list_head *list) { while (list->first != NULL) { ptr = list->first->next; if (ptr == NULL) { + if (list->first->symlink_target != NULL) { + free(list->first->symlink_target); + } free(list->first); break; } list->first->next = ptr->next; + if (ptr->symlink_target != NULL) { + free(ptr->symlink_target); + } free(ptr); } free(list); @@ -193,6 +203,35 @@ inline struct list_head *create_list_sort_reversed(struct list_head *list) { return lh; } +inline char *get_symlink_target_name(char *symlink, struct stat st) { + char *name = NULL; + ssize_t name_length = 0; + size_t tmp = 0; + + if ( symlink == NULL || (! S_ISLNK(st.st_mode)) ) { + return NULL; + } + + /* Suppressing warning __off_t to size_t only with the failsafe check */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" + tmp = ( (st.st_size<0) ? 0 : st.st_size+1 ); +#pragma GCC diagnostic pop + if (tmp == 0) { return NULL; } + + if ((name = calloc(tmp, sizeof(char))) == NULL) { + LOGERR("ERROR: Failed to allocate heap memory.\n"); + return NULL; + } + + if ((name_length = readlink(symlink, name, tmp)) == -1) { + LOGERR("ERROR: readlink(\"%s\", name, %lu) failed: %s (errno %d)\n", + symlink, (st.st_size+1), strerror(errno), errno); + free(name); + return NULL; + } + return name; +} /* This function contains the only time critical code. The loop over * the directory content. @@ -252,6 +291,9 @@ struct list_head *get_data_from_directory(char *path) { LOGERR("ERROR: Skipping entry %s\n", de->d_name); continue; } + if (option_resolve_symlinks && S_ISLNK(stat_res.st_mode)) { + tmp->symlink_target = get_symlink_target_name(fullpath, stat_res); + } /* Performance: Put every node on the stack and do the sorting afterwards */ tmp->next = lnstack; lnstack = tmp; @@ -286,3 +328,18 @@ struct list_head *get_data_from_directory(char *path) { return list; } +bool contains_resolved_symlinks(struct list_head *list) { + struct list_node *ptr = NULL; + + if (list == NULL) { return false; } + + ptr = list->first; + while (ptr != NULL) { + if (ptr->symlink_target != NULL) + return true; + ptr = ptr->next; + } + + return false; +} + diff --git a/src/dir_monitor.c b/src/dir_monitor.c index 935aa74..419b9f4 100644 --- a/src/dir_monitor.c +++ b/src/dir_monitor.c @@ -39,6 +39,11 @@ int main(int argc, char **argv) { list = get_data_from_directory("."); } + /* Make output code simpler, check whether there were any symlinks resolved */ + if (option_resolve_symlinks) { + option_resolve_symlinks = contains_resolved_symlinks(list); + } + if (option_format_string == NULL) { print_list(list); } else { diff --git a/src/options.c b/src/options.c index b975794..235792d 100644 --- a/src/options.c +++ b/src/options.c @@ -23,6 +23,7 @@ struct option long_options[] = { { "long-timestamp", no_argument, 0, 0 }, { "print-boxed", no_argument, 0, 0 }, { "print-header", no_argument, 0, 0 }, + { "resolve-symlinks", no_argument, 0, 0 }, { "reverse-sort", no_argument, 0, 0 }, { "show-hidden-entries", no_argument, 0, 0 }, { "sort-by", required_argument, 0, 0 }, @@ -34,6 +35,7 @@ bool option_sort_reverse_order = false; enum esort_type option_sort_type = SORT_BY_SIZE; bool option_print_boxed_table = false; bool option_print_header = false; +bool option_resolve_symlinks = false; bool option_show_hidden_entries = false; bool option_timestamp_long = false; char *option_format_string = NULL; @@ -63,6 +65,8 @@ void usage(char *executable) { "Print a boxed table"); fprintf(stderr, " %-25s %2s %10s - %s\n", "--print-header", "-H", "", "Print a header above the columns"); + fprintf(stderr, " %-25s %2s %10s - %s\n", "--resolve-symlinks", "", "", + "the destination (name) of a symlink is resolved"); fprintf(stderr, " %-25s %2s %10s - %s\n", "--reverse-sort", "", "", "Sort reversed"); fprintf(stderr, " %-25s %2s %10s - %s\n", "--show-hidden-entries", "-a", "", @@ -81,6 +85,7 @@ void usage(char *executable) { fprintf(stderr, " C - change time\n"); fprintf(stderr, " G - group name\n"); fprintf(stderr, " g - group id\n"); + fprintf(stderr, " l - symlink (--resolve-symlinks required)\n"); fprintf(stderr, " M - modification time\n"); fprintf(stderr, " n - file name\n"); fprintf(stderr, " p - permissions\n"); @@ -111,6 +116,11 @@ void set_option(const char *option_name, char *option_argument) { return; } + if (strcmp("resolve-symlinks", option_name) == 0) { + option_resolve_symlinks = true; + return; + } + if (strcmp("reverse-sort", option_name) == 0) { option_sort_reverse_order = true; return; diff --git a/src/output.c b/src/output.c index f94e3c9..a928140 100644 --- a/src/output.c +++ b/src/output.c @@ -37,22 +37,26 @@ #define out_vsep() fputc(box_vsep, stdout) #define out_print_newline() fputc('\n', stdout) -#define out_print_fname(x) printf(" %s ", x->fname) -#define out_print_uid(x) printf(" %4u ", x->ln_stat.st_gid) -#define out_print_gid(x) printf(" %4u ", x->ln_stat.st_uid) +void out_print_fname(struct list_node *ptr, size_t longest_fname); +void out_print_gid(struct list_node *ptr); void out_print_group_name(struct list_node *ptr); void out_print_permissions(struct list_node *ptr); void out_print_size(struct list_node *ptr); +void out_print_symlink(struct list_node *ptr, size_t longest_fname); void out_print_time(time_t tv); void out_print_time_by_option(struct list_node *ptr); void out_print_type(struct list_node *ptr); +void out_print_uid(struct list_node *ptr); void out_print_user_name(struct list_node *ptr); -void out_print_format_string_header(const char *format, size_t longest_fname); +void out_print_format_string_header(const char *format, + size_t longest_fname, size_t longest_symlink); -size_t estimate_formatted_line_length(const char *format, size_t longest_fname); +size_t estimate_formatted_line_length(const char *format, + size_t longest_fname, size_t longest_symlink); size_t get_longest_filename(struct list_head *list); +size_t get_longest_symlink(struct list_head *list); char *global_sep_line = NULL; @@ -102,7 +106,7 @@ inline int fputc_width_x(char c, size_t x, FILE *fdout) { inline size_t get_longest_filename(struct list_head *list) { size_t rc = 4, tmp = 0; struct list_node *ptr = NULL; - if (list == NULL) { return 0; } + if (list == NULL) { return rc; } ptr = list->first; while (ptr != NULL) { tmp = strlen(ptr->fname); @@ -112,8 +116,66 @@ inline size_t get_longest_filename(struct list_head *list) { return rc; } +/* basic 7 due to formatted output */ +inline size_t get_longest_symlink(struct list_head *list) { + size_t rc = 7, tmp = 0; + struct list_node *ptr = NULL; + if (list == NULL) { return rc; } + ptr = list->first; + while(ptr != NULL) { + if (ptr->symlink_target != NULL) { + tmp = strlen(ptr->symlink_target); + } else { tmp = 0; } + if (tmp>rc) { rc = tmp; } + ptr = ptr->next; + } + return rc; +} + /* === FORMATTING RELATED FUNCTIONS === */ + +inline void out_print_uid(struct list_node *ptr) { + printf(" %4u ", ptr->ln_stat.st_gid); + if (option_print_boxed_table) { out_vsep(); } +} + + +inline void out_print_gid(struct list_node *ptr) { + printf(" %4u ", ptr->ln_stat.st_uid); + if (option_print_boxed_table) { out_vsep(); } +} + + +inline void out_print_fname(struct list_node *ptr, size_t longest_fname) { + size_t i = 0; + printf(" %s ", ptr->fname); + if (option_print_boxed_table) { + for (i=0; i<(longest_fname - strlen(ptr->fname)); i++) { + fputc(' ', stdout); + } + out_vsep(); + } +} + + +inline void out_print_symlink(struct list_node *ptr, size_t longest_symlink) { + size_t i = 0, sll = 0; + if (ptr->symlink_target != NULL) { + sll = strlen(ptr->symlink_target); + printf(" %s ", ptr->symlink_target); + } else { + printf(" "); + } + if (option_print_boxed_table) { + for (i=0; i<(longest_symlink - sll); i++) { + fputc(' ', stdout); + } + out_vsep(); + } +} + + /* Be aware: getting the name via uid takes a lot of time depending on the * systems setup. local, NIS, LDAP ... * Caching happens outside the executable, minimal here for skipping funcion calls. */ @@ -152,6 +214,7 @@ inline void out_print_group_name(struct list_node *ptr) { explicit_bzero(prev_group_name, 64); memcpy(prev_group_name, group->gr_name, group_name_length); printf(" %-10s ", prev_group_name); + if (option_print_boxed_table) { out_vsep(); } } /* Reminder: resolving may take some time even with cache in libraries, @@ -191,6 +254,7 @@ inline void out_print_user_name(struct list_node *ptr) { explicit_bzero(prev_user_name, 64); memcpy(prev_user_name, pwd->pw_name, user_name_length); printf(" %-10s ", prev_user_name); + if (option_print_boxed_table) { out_vsep(); } } inline void out_print_size(struct list_node *ptr) { @@ -208,6 +272,7 @@ inline void out_print_size(struct list_node *ptr) { } } printf(" %8ld %3s ", tmp, unit); + if (option_print_boxed_table) { out_vsep(); } } inline void out_print_time(time_t tv) { @@ -222,6 +287,7 @@ inline void out_print_time(time_t tv) { strftime(timestamp, sizeof(timestamp), "%H:%M:%S", tm); printf(" %-8s ", timestamp); } + if (option_print_boxed_table) { out_vsep(); } } inline void out_print_time_by_option(struct list_node *ptr) { @@ -282,6 +348,7 @@ inline void out_print_type(struct list_node *ptr) { } /* The ones longer than 12 characters are rare. */ printf(" %-12s ", entry); + if (option_print_boxed_table) { out_vsep(); } } inline void out_print_permissions(struct list_node *ptr) { @@ -327,11 +394,14 @@ inline void out_print_permissions(struct list_node *ptr) { mode |= S_ISUID; } printf(" %4o ", mode); + if (option_print_boxed_table) { out_vsep(); } } +/* === END OF FORMATTING FUNCTIONS === */ + /* Interpret the format string and based on it generate a size which will be * able to store everything including visual additions. Use strlen later. */ -inline size_t estimate_formatted_line_length(const char *format, size_t longest_fname) { +inline size_t estimate_formatted_line_length(const char *format, size_t longest_fname, size_t longest_symlink) { size_t rc = 0, i=0, format_len=0; static int login_name_max = -1; if (login_name_max < 0) { @@ -347,6 +417,7 @@ inline size_t estimate_formatted_line_length(const char *format, size_t longest_ format_len = strnlen(format, DM_OUT_FORMAT_MAX_LEN); for (i=0; ifirst; @@ -568,57 +656,45 @@ void print_list_formatted(const char *format, struct list_head *list) { for (i=0; i < format_len; i++) { switch (format[i]) { case 'n': - out_print_fname(ptr); - if (option_print_boxed_table) { - for (j=0; j<(longest_fname - strlen(ptr->fname)); j++) { - fputc(' ', stdout); - } - out_vsep(); + out_print_fname(ptr, longest_fname); + break; + case 'l': + if (option_resolve_symlinks) { + out_print_symlink(ptr, longest_symlink); } break; case 's': out_print_size(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 't': out_print_time_by_option(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 'A': out_print_time(ptr->ln_stat.st_atim.tv_sec); - if (option_print_boxed_table) { out_vsep(); } break; case 'C': out_print_time(ptr->ln_stat.st_ctim.tv_sec); - if (option_print_boxed_table) { out_vsep(); } break; case 'M': out_print_time(ptr->ln_stat.st_mtim.tv_sec); - if (option_print_boxed_table) { out_vsep(); } break; case 'T': out_print_type(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 'u': out_print_uid(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 'g': out_print_gid(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 'U': out_print_user_name(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 'G': out_print_group_name(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case 'p': out_print_permissions(ptr); - if (option_print_boxed_table) { out_vsep(); } break; case ' ': /* just ignore this without warning */ break; -- cgit v1.2.3-70-g09d2