diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:26:58 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:26:58 +0300 |
| commit | 4328365a80faebd5963112936b3d5daf0440d6e8 (patch) | |
| tree | f50bc9edec18ffa8c77445fcb3619113bc85eff5 /corebinutils/ls/util.c | |
| parent | ec6a123cffbe492c576ec1ad545d5296321a86e1 (diff) | |
| parent | 06b170dd48138a26fdfe1b822ba9846a26a2fa0f (diff) | |
| download | Project-Tick-4328365a80faebd5963112936b3d5daf0440d6e8.tar.gz Project-Tick-4328365a80faebd5963112936b3d5daf0440d6e8.zip | |
Add 'corebinutils/ls/' from commit '06b170dd48138a26fdfe1b822ba9846a26a2fa0f'
git-subtree-dir: corebinutils/ls
git-subtree-mainline: ec6a123cffbe492c576ec1ad545d5296321a86e1
git-subtree-split: 06b170dd48138a26fdfe1b822ba9846a26a2fa0f
Diffstat (limited to 'corebinutils/ls/util.c')
| -rw-r--r-- | corebinutils/ls/util.c | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/corebinutils/ls/util.c b/corebinutils/ls/util.c new file mode 100644 index 0000000000..2cd73d51a8 --- /dev/null +++ b/corebinutils/ls/util.c @@ -0,0 +1,810 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 2026 + * Project Tick. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/sysmacros.h> + +#include <ctype.h> +#include <errno.h> +#include <err.h> +#include <grp.h> +#include <inttypes.h> +#include <limits.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> + +#include "ls.h" +#include "extern.h" + +static int print_name_literal(const char *name); +static int print_name_printable(const char *name); +static int print_name_octal(const char *name, bool c_escape); +static size_t measure_name_literal(const char *name); +static size_t measure_name_printable(const char *name); +static size_t measure_name_octal(const char *name, bool c_escape); +static char *lookup_user(uid_t uid); +static char *lookup_group(gid_t gid); +static char *humanize_u64(uint64_t value); + +void * +xmalloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + if (ptr == NULL) + err(1, "malloc"); + return (ptr); +} + +void * +xreallocarray(void *ptr, size_t nmemb, size_t size) +{ + void *new_ptr; + + if (size != 0 && nmemb > SIZE_MAX / size) + errx(1, "allocation overflow"); + new_ptr = realloc(ptr, nmemb * size); + if (new_ptr == NULL) + err(1, "realloc"); + return (new_ptr); +} + +char * +xstrdup(const char *src) +{ + char *copy; + + copy = strdup(src); + if (copy == NULL) + err(1, "strdup"); + return (copy); +} + +char * +xasprintf(const char *fmt, ...) +{ + va_list ap; + char *buf; + int len; + + va_start(ap, fmt); + len = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + if (len < 0) + errx(1, "vsnprintf"); + buf = xmalloc((size_t)len + 1); + va_start(ap, fmt); + if (vsnprintf(buf, (size_t)len + 1, fmt, ap) != len) { + va_end(ap); + errx(1, "vsnprintf"); + } + va_end(ap); + return (buf); +} + +char * +join_path(const char *parent, const char *name) +{ + size_t parent_len, name_len; + bool need_sep; + char *path; + + parent_len = strlen(parent); + name_len = strlen(name); + need_sep = parent_len != 0 && parent[parent_len - 1] != '/'; + path = xmalloc(parent_len + (need_sep ? 1 : 0) + name_len + 1); + memcpy(path, parent, parent_len); + if (need_sep) + path[parent_len++] = '/'; + memcpy(path + parent_len, name, name_len + 1); + return (path); +} + +void +free_entry(struct entry *entry) +{ + if (entry == NULL) + return; + free(entry->name); + free(entry->path); + free(entry->user); + free(entry->group); + free(entry->link_target); + memset(entry, 0, sizeof(*entry)); +} + +void +free_entry_list(struct entry_list *list) +{ + size_t i; + + if (list == NULL) + return; + for (i = 0; i < list->len; i++) + free_entry(&list->items[i]); + free(list->items); + list->items = NULL; + list->len = 0; + list->cap = 0; +} + +void +append_entry(struct entry_list *list, struct entry *entry) +{ + if (list->len == list->cap) { + list->cap = list->cap == 0 ? 16 : list->cap * 2; + list->items = xreallocarray(list->items, list->cap, + sizeof(*list->items)); + } + list->items[list->len++] = *entry; + memset(entry, 0, sizeof(*entry)); +} + +size_t +numeric_width(uintmax_t value) +{ + size_t width; + + for (width = 1; value >= 10; width++) + value /= 10; + return (width); +} + +static int +print_name_literal(const char *name) +{ + mbstate_t state; + wchar_t wc; + size_t clen; + int width; + int total; + + memset(&state, 0, sizeof(state)); + total = 0; + while ((clen = mbrtowc(&wc, name, MB_LEN_MAX, &state)) != 0) { + if (clen == (size_t)-1) { + memset(&state, 0, sizeof(state)); + putchar((unsigned char)*name++); + total++; + continue; + } + if (clen == (size_t)-2) { + total += printf("%s", name); + break; + } + fwrite(name, 1, clen, stdout); + name += clen; + width = wcwidth(wc); + if (width > 0) + total += width; + } + return (total); +} + +static int +print_name_printable(const char *name) +{ + mbstate_t state; + wchar_t wc; + size_t clen; + int width; + int total; + + memset(&state, 0, sizeof(state)); + total = 0; + while ((clen = mbrtowc(&wc, name, MB_LEN_MAX, &state)) != 0) { + if (clen == (size_t)-1 || clen == (size_t)-2) { + putchar('?'); + total++; + if (clen == (size_t)-1) { + memset(&state, 0, sizeof(state)); + name++; + } + break; + } + if (!iswprint(wc)) { + putchar('?'); + name += clen; + total++; + continue; + } + fwrite(name, 1, clen, stdout); + name += clen; + width = wcwidth(wc); + if (width > 0) + total += width; + } + return (total); +} + +static int +print_name_octal(const char *name, bool c_escape) +{ + static const char escapes[] = "\\\\\"\"\aa\bb\ff\nn\rr\tt\vv"; + mbstate_t state; + wchar_t wc; + size_t clen; + unsigned char ch; + const char *mapped; + int printable; + int total; + int i; + + memset(&state, 0, sizeof(state)); + total = 0; + while ((clen = mbrtowc(&wc, name, MB_LEN_MAX, &state)) != 0) { + printable = clen != (size_t)-1 && clen != (size_t)-2 && + iswprint(wc) && wc != L'\\' && wc != L'\"'; + if (printable) { + fwrite(name, 1, clen, stdout); + name += clen; + i = wcwidth(wc); + if (i > 0) + total += i; + continue; + } + if (c_escape && clen != (size_t)-1 && clen != (size_t)-2 && +#if WCHAR_MIN < 0 + wc >= 0 && +#endif + wc <= (wchar_t)UCHAR_MAX && + (mapped = strchr(escapes, (char)wc)) != NULL) { + putchar('\\'); + putchar(mapped[1]); + name += clen; + total += 2; + continue; + } + if (clen == (size_t)-2) + clen = strlen(name); + else if (clen == (size_t)-1) { + clen = 1; + memset(&state, 0, sizeof(state)); + } + for (i = 0; i < (int)clen; i++) { + ch = (unsigned char)name[i]; + printf("\\%03o", ch); + total += 4; + } + name += clen; + } + return (total); +} + +int +print_name(const struct options *opt, const char *name) +{ + switch (opt->name_mode) { + case NAME_PRINTABLE: + return (print_name_printable(name)); + case NAME_OCTAL: + return (print_name_octal(name, false)); + case NAME_ESCAPE: + return (print_name_octal(name, true)); + case NAME_LITERAL: + default: + return (print_name_literal(name)); + } +} + +static size_t +measure_name_literal(const char *name) +{ + mbstate_t state; + wchar_t wc; + size_t clen; + size_t total; + int width; + + memset(&state, 0, sizeof(state)); + total = 0; + while ((clen = mbrtowc(&wc, name, MB_LEN_MAX, &state)) != 0) { + if (clen == (size_t)-1) { + memset(&state, 0, sizeof(state)); + name++; + total++; + continue; + } + if (clen == (size_t)-2) { + total += strlen(name); + break; + } + name += clen; + width = wcwidth(wc); + if (width > 0) + total += (size_t)width; + } + return (total); +} + +static size_t +measure_name_printable(const char *name) +{ + mbstate_t state; + wchar_t wc; + size_t clen; + size_t total; + int width; + + memset(&state, 0, sizeof(state)); + total = 0; + while ((clen = mbrtowc(&wc, name, MB_LEN_MAX, &state)) != 0) { + if (clen == (size_t)-1 || clen == (size_t)-2) { + total++; + if (clen == (size_t)-1) { + memset(&state, 0, sizeof(state)); + name++; + } + break; + } + if (!iswprint(wc)) { + name += clen; + total++; + continue; + } + name += clen; + width = wcwidth(wc); + if (width > 0) + total += (size_t)width; + } + return (total); +} + +static size_t +measure_name_octal(const char *name, bool c_escape) +{ + static const char escapes[] = "\\\\\"\"\aa\bb\ff\nn\rr\tt\vv"; + mbstate_t state; + wchar_t wc; + size_t clen; + size_t total; + const char *mapped; + int width; + + memset(&state, 0, sizeof(state)); + total = 0; + while ((clen = mbrtowc(&wc, name, MB_LEN_MAX, &state)) != 0) { + if (clen == (size_t)-2) { + total += 4 * strlen(name); + break; + } + if (clen == (size_t)-1) { + memset(&state, 0, sizeof(state)); + name++; + total += 4; + continue; + } + if (iswprint(wc) && wc != L'\\' && wc != L'\"') { + width = wcwidth(wc); + if (width > 0) + total += (size_t)width; + name += clen; + continue; + } + if (c_escape && +#if WCHAR_MIN < 0 + wc >= 0 && +#endif + wc <= (wchar_t)UCHAR_MAX && + (mapped = strchr(escapes, (char)wc)) != NULL) { + name += clen; + total += 2; + continue; + } + name += clen; + total += 4 * clen; + } + return (total); +} + +size_t +measure_name(const struct options *opt, const char *name) +{ + switch (opt->name_mode) { + case NAME_PRINTABLE: + return (measure_name_printable(name)); + case NAME_OCTAL: + return (measure_name_octal(name, false)); + case NAME_ESCAPE: + return (measure_name_octal(name, true)); + case NAME_LITERAL: + default: + return (measure_name_literal(name)); + } +} + +char +file_type_char(const struct entry *entry, bool slash_only) +{ + mode_t mode; + + mode = entry->st.st_mode; + if (slash_only) + return (entry->is_dir ? '/' : '\0'); + if (entry->is_dir) + return ('/'); + if (S_ISFIFO(mode)) + return ('|'); + if (S_ISLNK(mode)) + return ('@'); + if (S_ISSOCK(mode)) + return ('='); + if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return ('*'); + return ('\0'); +} + +void +mode_string(const struct entry *entry, char buf[12]) +{ + mode_t mode; + + mode = entry->st.st_mode; + buf[0] = S_ISDIR(mode) ? 'd' : + S_ISLNK(mode) ? 'l' : + S_ISCHR(mode) ? 'c' : + S_ISBLK(mode) ? 'b' : + S_ISFIFO(mode) ? 'p' : + S_ISSOCK(mode) ? 's' : '-'; + buf[1] = (mode & S_IRUSR) ? 'r' : '-'; + buf[2] = (mode & S_IWUSR) ? 'w' : '-'; + buf[3] = (mode & S_ISUID) ? ((mode & S_IXUSR) ? 's' : 'S') : + ((mode & S_IXUSR) ? 'x' : '-'); + buf[4] = (mode & S_IRGRP) ? 'r' : '-'; + buf[5] = (mode & S_IWGRP) ? 'w' : '-'; + buf[6] = (mode & S_ISGID) ? ((mode & S_IXGRP) ? 's' : 'S') : + ((mode & S_IXGRP) ? 'x' : '-'); + buf[7] = (mode & S_IROTH) ? 'r' : '-'; + buf[8] = (mode & S_IWOTH) ? 'w' : '-'; + buf[9] = (mode & S_ISVTX) ? ((mode & S_IXOTH) ? 't' : 'T') : + ((mode & S_IXOTH) ? 'x' : '-'); + buf[10] = ' '; + buf[11] = '\0'; +} + +char * +format_uintmax(uintmax_t value, bool thousands) +{ + char tmp[64]; + size_t len; + size_t out_len; + size_t commas; + size_t i; + char *out; + + snprintf(tmp, sizeof(tmp), "%ju", value); + if (!thousands) + return (xstrdup(tmp)); + len = strlen(tmp); + commas = len > 0 ? (len - 1) / 3 : 0; + out_len = len + commas; + out = xmalloc(out_len + 1); + out[out_len] = '\0'; + for (i = 0; i < len; i++) { + out[out_len - 1 - i - i / 3] = tmp[len - 1 - i]; + if (i / 3 != (i + 1) / 3 && i + 1 < len) + out[out_len - 1 - i - i / 3 - 1] = ','; + } + return (out); +} + +char * +format_block_count(blkcnt_t blocks, long units, bool thousands) +{ + uintmax_t scaled; + + if (blocks < 0) + blocks = 0; + if (units <= 0) + units = 1; + scaled = ((uintmax_t)blocks + (uintmax_t)units - 1) / (uintmax_t)units; + return (format_uintmax(scaled, thousands)); +} + +static char * +humanize_u64(uint64_t value) +{ + static const char suffixes[] = "BKMGTPE"; + double scaled; + unsigned idx; + + scaled = (double)value; + idx = 0; + while (scaled >= 1024.0 && idx + 1 < sizeof(suffixes) - 1) { + scaled /= 1024.0; + idx++; + } + if (idx == 0) + return (xasprintf("%" PRIu64 "B", value)); + if (scaled >= 10.0) + return (xasprintf("%.0f%c", scaled, suffixes[idx])); + return (xasprintf("%.1f%c", scaled, suffixes[idx])); +} + +char * +format_entry_size(const struct entry *entry, bool human, bool thousands) +{ + uint64_t size; + + if (S_ISCHR(entry->st.st_mode) || S_ISBLK(entry->st.st_mode)) + return (xasprintf("%u,%u", major(entry->st.st_rdev), + minor(entry->st.st_rdev))); + size = entry->st.st_size < 0 ? 0 : (uint64_t)entry->st.st_size; + if (human) + return (humanize_u64(size)); + return (format_uintmax(size, thousands)); +} + +static char * +lookup_user(uid_t uid) +{ + struct passwd pwd; + struct passwd *result; + char *name; + char *buf; + long len; + int error; + + len = sysconf(_SC_GETPW_R_SIZE_MAX); + if (len < 1024) + len = 1024; + buf = xmalloc((size_t)len); + while ((error = getpwuid_r(uid, &pwd, buf, (size_t)len, &result)) == ERANGE) { + len *= 2; + buf = xreallocarray(buf, (size_t)len, 1); + } + if (error != 0 || result == NULL) { + free(buf); + return (format_uintmax(uid, false)); + } + name = xstrdup(pwd.pw_name); + free(buf); + return (name); +} + +static char * +lookup_group(gid_t gid) +{ + struct group grp; + struct group *result; + char *name; + char *buf; + long len; + int error; + + len = sysconf(_SC_GETGR_R_SIZE_MAX); + if (len < 1024) + len = 1024; + buf = xmalloc((size_t)len); + while ((error = getgrgid_r(gid, &grp, buf, (size_t)len, &result)) == ERANGE) { + len *= 2; + buf = xreallocarray(buf, (size_t)len, 1); + } + if (error != 0 || result == NULL) { + free(buf); + return (format_uintmax(gid, false)); + } + name = xstrdup(grp.gr_name); + free(buf); + return (name); +} + +int +ensure_owner_group(struct entry *entry, const struct options *opt) +{ + if (entry->user != NULL && entry->group != NULL) + return (0); + free(entry->user); + free(entry->group); + if (opt->numeric_ids) { + entry->user = format_uintmax(entry->st.st_uid, false); + entry->group = format_uintmax(entry->st.st_gid, false); + } else { + entry->user = lookup_user(entry->st.st_uid); + entry->group = lookup_group(entry->st.st_gid); + } + return (0); +} + +int +ensure_link_target(struct entry *entry) +{ + ssize_t len; + size_t cap; + char *buf; + + if (entry->link_target != NULL || !entry->is_symlink) + return (0); + cap = 128; + buf = xmalloc(cap); + for (;;) { + len = readlink(entry->path, buf, cap - 1); + if (len < 0) { + free(buf); + return (-1); + } + if ((size_t)len < cap - 1) { + buf[len] = '\0'; + entry->link_target = buf; + return (0); + } + cap *= 2; + buf = xreallocarray(buf, cap, 1); + } +} + +bool +selected_time(const struct context *ctx, const struct entry *entry, + struct timespec *ts) +{ + switch (ctx->opt.time_field) { + case TIME_ATIME: + *ts = entry->st.st_atim; + return (true); + case TIME_CTIME: + *ts = entry->st.st_ctim; + return (true); + case TIME_BTIME: + if (entry->btime.available) + *ts = entry->btime.ts; + else + *ts = entry->st.st_mtim; + return (true); + case TIME_MTIME: + default: + *ts = entry->st.st_mtim; + return (true); + } +} + +char * +format_entry_time(const struct context *ctx, const struct entry *entry) +{ + struct timespec ts; + struct tm tm; + char buf[128]; + const char *fmt; + bool recent; + + selected_time(ctx, entry, &ts); + if (localtime_r(&ts.tv_sec, &tm) == NULL) + return (xstrdup("<bad-time>")); + if (ctx->opt.time_format != NULL) + fmt = ctx->opt.time_format; + else if (ctx->opt.show_full_time) + fmt = "%b %e %H:%M:%S %Y"; + else { + recent = ts.tv_sec + (365 / 2) * 86400 > ctx->now && + ts.tv_sec < ctx->now + (365 / 2) * 86400; + fmt = recent ? "%b %e %H:%M" : "%b %e %Y"; + } + if (strftime(buf, sizeof(buf), fmt, &tm) == 0) + return (xstrdup("<bad-format>")); + return (xstrdup(buf)); +} + +const char * +color_start(const struct context *ctx, const struct entry *entry) +{ + mode_t mode; + + if (!ctx->opt.colorize) + return (""); + mode = entry->st.st_mode; + if (S_ISDIR(mode)) + return ("\033[01;34m"); + if (S_ISLNK(mode)) + return ("\033[01;36m"); + if (S_ISFIFO(mode)) + return ("\033[33m"); + if (S_ISSOCK(mode)) + return ("\033[01;35m"); + if (S_ISBLK(mode) || S_ISCHR(mode)) + return ("\033[01;33m"); + if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return ("\033[01;32m"); + return (""); +} + +const char * +color_end(const struct context *ctx) +{ + return (ctx->opt.colorize ? "\033[0m" : ""); +} + +int +natural_version_compare(const char *left, const char *right) +{ + const unsigned char *a; + const unsigned char *b; + + a = (const unsigned char *)left; + b = (const unsigned char *)right; + while (*a != '\0' || *b != '\0') { + if (isdigit(*a) && isdigit(*b)) { + const unsigned char *a0; + const unsigned char *b0; + size_t len_a; + size_t len_b; + int cmp; + + a0 = a; + b0 = b; + while (*a == '0') + a++; + while (*b == '0') + b++; + len_a = 0; + while (isdigit(a[len_a])) + len_a++; + len_b = 0; + while (isdigit(b[len_b])) + len_b++; + if (len_a != len_b) + return (len_a < len_b ? -1 : 1); + cmp = memcmp(a, b, len_a); + if (cmp != 0) + return (cmp < 0 ? -1 : 1); + while (isdigit(*a0)) + a0++; + while (isdigit(*b0)) + b0++; + if ((size_t)(a0 - (const unsigned char *)left) != + (size_t)(b0 - (const unsigned char *)right)) { + if ((a0 - a) != (b0 - b)) + return ((a0 - a) > (b0 - b) ? -1 : 1); + } + a = a0; + b = b0; + continue; + } + if (*a != *b) + return (*a < *b ? -1 : 1); + if (*a != '\0') + a++; + if (*b != '\0') + b++; + } + return (0); +} |
