/* SPDX-License-Identifier: Apache-2.0 */ /* Copyright 2025 Thorsten Töpper * * vim:ts=4:sw=4:expandtab */ #include #include #include #include #include #include #include #include "output.h" #include "data_management.h" #include "options.h" /* === DEFINITIONS === */ #define out_vsep fputc('|', stdout) #define out_print_newline() fputc('\n', stdout) #define out_print_fname(x) printf(" %s ", x->fname) void out_print_size(struct list_node *ptr); 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); /* === IMPLEMENTATION === */ inline int fputc_all_cols(char c, FILE *fdout) { struct winsize terminal; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &terminal) == -1) { if (errno != 25) { LOGERR("ERROR: ioctl(STDOUT_FILENO, TIOCGWINSZ,...) failed: %s (errno %d)\n", strerror(errno), errno); } terminal.ws_col = 72; } DBGTRC("DEBUG: terminal.ws_col = %d / terminal.ws_row = %d\n", terminal.ws_col, terminal.ws_row); /* Somehow not always ws_col == 0, with optimization compiler flags and executed by watch. */ if (terminal.ws_col == 0) { terminal.ws_col = 72; } return fputc_width_x(c, terminal.ws_col, fdout); } /* Not the most performant way, but during output the time critical tasks are already done. */ inline int fputc_width_x(char c, size_t x, FILE *fdout) { size_t i=0; if (fdout == NULL) { LOGERR("ERROR: No output stream given.\n"); return EOF; } if ( ! isprint(c) ) { c = '?'; } for (i=0; i_fileno); return EOF; } } return 1; } /* === FORMATTING RELATED FUNCTIONS === */ inline void out_print_size(struct list_node *ptr) { printf(" %8ld %2s ", ((ptr->ln_stat.st_size>=1024) ? (ptr->ln_stat.st_size/1024) : ptr->ln_stat.st_size), ((ptr->ln_stat.st_size >= 1024) ? "kB" : "")); } inline void out_print_time(time_t tv) { struct tm *tm = NULL; char timestamp[128]; tm = localtime(&tv); if (option_timestamp_long) { strftime(timestamp, sizeof(timestamp), "%Y%m%d %H:%M:%S %Z", tm); printf(" %20s ", timestamp); } else { strftime(timestamp, sizeof(timestamp), "%H:%M:%S", tm); printf(" %8s ", timestamp); } } inline void out_print_time_by_option(struct list_node *ptr) { time_t time_value = 0; switch (option_time_field) { case 'm': time_value = ptr->ln_stat.st_mtim.tv_sec; break; case 'a': time_value = ptr->ln_stat.st_atim.tv_sec; break; case 'c': time_value = ptr->ln_stat.st_ctim.tv_sec; break; default: LOGERR("WARNING: option_time_field has invalid value %c 0x%02X - going by default m\n", option_time_field, option_time_field); option_time_field = 'm'; time_value = ptr->ln_stat.st_mtim.tv_sec; break; }; out_print_time(time_value); } inline void out_print_type(struct list_node *ptr) { char entry[16] = ""; /* regular file, directory and symlinks are most common */ if (S_ISREG(ptr->ln_stat.st_mode)) { sprintf(entry, "regular file"); } else if (S_ISDIR(ptr->ln_stat.st_mode)) { sprintf(entry, "directory"); } else if (S_ISLNK(ptr->ln_stat.st_mode)) { sprintf(entry, "symlink"); } else if (S_ISBLK(ptr->ln_stat.st_mode)) { sprintf(entry, "block"); } else if (S_ISCHR(ptr->ln_stat.st_mode)) { sprintf(entry, "char"); } else if (S_ISFIFO(ptr->ln_stat.st_mode)) { sprintf(entry, "fifo"); } else if (S_ISSOCK(ptr->ln_stat.st_mode)) { sprintf(entry, "socket"); /* The following macros operate on the complete struct stat */ } else if (S_TYPEISMQ(&(ptr->ln_stat))) { sprintf(entry, "message queue"); } else if (S_TYPEISSEM(&(ptr->ln_stat))) { sprintf(entry, "semaphore"); } else if (S_TYPEISSHM(&(ptr->ln_stat))) { sprintf(entry, "shared memory"); #if 0 /* Although mentioned in the POSIX man page, not part of glibc */ } else if (S_TYPEISTMO(&(ptr->ln_stat))) { sprintf(entry, "memory object"); #endif } else { sprintf(entry, "unidentified"); } /* The ones longer than 12 characters are rare. */ printf(" %-12s ", entry); } /* === END OF FORMATTING FUNCTIONS === */ /* The most simple default output, formatted output is planned */ void print_list(struct list_head *list) { struct list_head *lh = list; struct list_node *ptr; size_t total_size = 0; if (list == NULL) return; if (option_sort_reverse_order) { lh = create_list_sort_reversed(list); /* be tolerant */ if (lh == NULL) { lh = list; } } ptr = lh->first; while (ptr != NULL) { out_print_size(ptr); out_print_time_by_option(ptr); out_print_fname(ptr); out_print_newline(); /* Linux: Neither man stat(2) nor stat(3type) declare why struct stat field st_size * would get a negative value assigned. The type off_t masks __off_t or __off64_t * which themselves mask bits/types.h __SLONGWORD_TYPE on x86_64 that's long int. * Haven't checked libc/kernel code but regardless why, I assume that a negative * value would imply a corrupt filesystem. */ total_size += (ptr->ln_stat.st_size>0) ? (unsigned long int)ptr->ln_stat.st_size : 0; ptr = ptr->next; } fputc_all_cols('=', stdout); printf("\nTotal size: %lu %s\n", ((total_size>1024) ? total_size/1024 : total_size), ((total_size >= 1024) ? "kB" : "")); if (lh != list) destroy_list(lh); } /* For now with a large walk around common format strings and prevention of attack vectors. * n - name * s - file size * T - type * * Time related * t - timestamp set by option_time_field * A - timestamp from atim.tv_sec * C - timestamp from ctim.tv_sec * M - timestamp from mtim.tv_sec */ void print_list_formatted(const char *format, struct list_head *list) { struct list_head *lh = list; struct list_node *ptr; size_t format_len = 0, i = 0; if (format == NULL || format[0] == '\0') { LOGERR("ERROR: No format string given"); return; } if (list == NULL || list->first == NULL) { LOGERR("ERROR: No data to print.\n"); return; } if (option_sort_reverse_order) { lh = create_list_sort_reversed(list); /* be tolerant */ if (lh == NULL) { lh = list; } } /* Note: the maximum length needs to be adjusted if the strings * shall contain more complicated settings */ format_len = strnlen(format, 80); ptr = lh->first; while (ptr != NULL) { for (i=0; i < format_len; i++) { switch (format[i]) { case 'n': out_print_fname(ptr); break; case 's': out_print_size(ptr); break; case 't': out_print_time_by_option(ptr); break; case 'A': out_print_time(ptr->ln_stat.st_atim.tv_sec); break; case 'C': out_print_time(ptr->ln_stat.st_ctim.tv_sec); break; case 'M': out_print_time(ptr->ln_stat.st_mtim.tv_sec); break; case 'T': out_print_type(ptr); break; case ' ': /* just ignore this without warning */ break; default: LOGERR("WARNING: Invalid character 0x%02X ignoring it\n", format[i]); break; }; } out_print_newline(); ptr = ptr->next; } if (lh != list) destroy_list(lh); }