/* A parser for file-listings formatted like 'ls -l'. Copyright (C) 2016-2024 Free Software Foundation, Inc. This file is part of the Midnight Commander. The Midnight Commander is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The Midnight Commander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /** \file * \brief A parser for file-listings formatted like 'ls -l'. * * This program parses file-listings the same way MC does. * It's basically a wrapper around vfs_parse_ls_lga(). * You can feed it the output of any of extfs's helpers. * * After parsing, the data is written out to stdout. The output * format can be either YAML or a format similar to 'ls -l'. * * This program is the main tool our tester script is going to use. */ #include #include #include #include "lib/global.h" #include "lib/vfs/utilvfs.h" /* vfs_parse_ls_lga() */ #include "lib/util.h" /* string_perm() */ #include "lib/timefmt.h" /* FMT_LOCALTIME */ #include "lib/widget.h" /* for the prototype of message() only */ /*** global variables ****************************************************************************/ /*** file scope macro definitions ****************************************************************/ /*** file scope type declarations ****************************************************************/ typedef enum { FORMAT_YAML, FORMAT_LS } output_format_t; /*** forward declarations (file scope functions) *************************************************/ static gboolean parse_format_name_argument (const gchar * option_name, const gchar * value, gpointer data, GError ** error); /*** file scope variables ************************************************************************/ /* Command-line options. */ static gboolean opt_drop_mtime = FALSE; static gboolean opt_drop_ids = FALSE; static gboolean opt_symbolic_ids = FALSE; static output_format_t opt_output_format = FORMAT_LS; /* Misc. */ static int error_count = 0; static GOptionEntry entries[] = { {"drop-mtime", 0, 0, G_OPTION_ARG_NONE, &opt_drop_mtime, "Don't include mtime in the output.", NULL}, {"drop-ids", 0, 0, G_OPTION_ARG_NONE, &opt_drop_ids, "Don't include uid/gid in the output.", NULL}, {"symbolic-ids", 0, 0, G_OPTION_ARG_NONE, &opt_symbolic_ids, "Print the strings '<>'/'<>' instead of the numeric IDs when they match the process' uid/gid.", NULL}, {"format", 'f', 0, G_OPTION_ARG_CALLBACK, parse_format_name_argument, "Output format. Default: ls.", ""}, G_OPTION_ENTRY_NULL }; /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ /** * Command-line handling. */ /* --------------------------------------------------------------------------------------------- */ static gboolean parse_format_name_argument (const gchar * option_name, const gchar * value, gpointer data, GError ** error) { (void) option_name; (void) data; if (strcmp (value, "yaml") == 0) opt_output_format = FORMAT_YAML; else if (strcmp (value, "ls") == 0) opt_output_format = FORMAT_LS; else { g_set_error (error, MC_ERROR, G_OPTION_ERROR_FAILED, "unknown output format '%s'", value); return FALSE; } return TRUE; } /* --------------------------------------------------------------------------------------------- */ static gboolean parse_command_line (int *argc, char **argv[]) { GError *error = NULL; GOptionContext *context; context = g_option_context_new ("- Parses its input, which is expected to be in a format similar to 'ls -l', and writes the result to stdout."); g_option_context_add_main_entries (context, entries, NULL); if (!g_option_context_parse (context, argc, argv, &error)) { g_print ("option parsing failed: %s\n", error->message); g_error_free (error); return FALSE; } return TRUE; } /* --------------------------------------------------------------------------------------------- */ /** * Utility functions. */ /* --------------------------------------------------------------------------------------------- */ static const char * my_itoa (int i) { static char buf[BUF_SMALL]; sprintf (buf, "%d", i); return buf; } /* --------------------------------------------------------------------------------------------- */ /** * Returns the uid as-is, or as "<>" if the same as current user. */ static const char * symbolic_uid (uid_t uid) { return (opt_symbolic_ids && uid == getuid ())? "<>" : my_itoa ((int) uid); } /* --------------------------------------------------------------------------------------------- */ static const char * symbolic_gid (gid_t gid) { return (opt_symbolic_ids && gid == getgid ())? "<>" : my_itoa ((int) gid); } /* --------------------------------------------------------------------------------------------- */ /** * Cuts off a string's line-end (as in Perl). */ static void chomp (char *s) { int i; i = strlen (s); /* Code taken from vfs_parse_ls_lga(), with modifications. */ if ((--i >= 0) && (s[i] == '\r' || s[i] == '\n')) s[i] = '\0'; if ((--i >= 0) && (s[i] == '\r' || s[i] == '\n')) s[i] = '\0'; } /* --------------------------------------------------------------------------------------------- */ static const char * string_date (time_t t) { static char buf[BUF_SMALL]; /* * If we ever want to be able to re-parse the output of this program, * we'll need to use the American brain-damaged MM-DD-YYYY instead of * YYYY-MM-DD because vfs_parse_ls_lga() doesn't currently recognize * the latter. */ FMT_LOCALTIME (buf, sizeof buf, "%Y-%m-%d %H:%M:%S", t); return buf; } /* --------------------------------------------------------------------------------------------- */ /** * Override MC's message(). * * vfs_parse_ls_lga() calls this on error. Since MC's uses tty/widgets, it * will crash us. We replace it with a plain version. */ void message (int flags, const char *title, const char *text, ...) { char *p; va_list ap; (void) flags; (void) title; va_start (ap, text); p = g_strdup_vprintf (text, ap); va_end (ap); printf ("message(): vfs_parse_ls_lga(): parsing error at: %s\n", p); g_free (p); } /* --------------------------------------------------------------------------------------------- */ /** * YAML output format. */ /* --------------------------------------------------------------------------------------------- */ static void yaml_dump_stbuf (const struct stat *st) { /* Various casts and flags here were taken/inspired by info_show_info(). */ printf (" perm: %s\n", string_perm (st->st_mode)); if (!opt_drop_ids) { printf (" uid: %s\n", symbolic_uid (st->st_uid)); printf (" gid: %s\n", symbolic_gid (st->st_gid)); } printf (" size: %" PRIuMAX "\n", (uintmax_t) st->st_size); printf (" nlink: %d\n", (int) st->st_nlink); if (!opt_drop_mtime) printf (" mtime: %s\n", string_date (st->st_mtime)); } /* --------------------------------------------------------------------------------------------- */ static void yaml_dump_string (const char *name, const char *val) { char *q; q = g_shell_quote (val); printf (" %s: %s\n", name, q); g_free (q); } /* --------------------------------------------------------------------------------------------- */ static void yaml_dump_record (gboolean success, const char *input_line, const struct stat *st, const char *filename, const char *linkname) { printf ("-\n"); /* Start new item in the list. */ if (success) { if (filename != NULL) yaml_dump_string ("name", filename); if (linkname != NULL) yaml_dump_string ("linkname", linkname); yaml_dump_stbuf (st); } else { yaml_dump_string ("cannot parse input line", input_line); } } /* --------------------------------------------------------------------------------------------- */ /** * 'ls' output format. */ /* --------------------------------------------------------------------------------------------- */ static void ls_dump_stbuf (const struct stat *st) { /* Various casts and flags here were taken/inspired by info_show_info(). */ printf ("%s %3d ", string_perm (st->st_mode), (int) st->st_nlink); if (!opt_drop_ids) { printf ("%8s ", symbolic_uid (st->st_uid)); printf ("%8s ", symbolic_gid (st->st_gid)); } printf ("%10" PRIuMAX " ", (uintmax_t) st->st_size); if (!opt_drop_mtime) printf ("%s ", string_date (st->st_mtime)); } /* --------------------------------------------------------------------------------------------- */ static void ls_dump_record (gboolean success, const char *input_line, const struct stat *st, const char *filename, const char *linkname) { if (success) { ls_dump_stbuf (st); if (filename != NULL) printf ("%s", filename); if (linkname != NULL) printf (" -> %s", linkname); printf ("\n"); } else { printf ("cannot parse input line: '%s'\n", input_line); } } /* ------------------------------------------------------------------------------ */ /** * Main code. */ /* ------------------------------------------------------------------------------ */ static void process_ls_line (const char *line) { struct stat st; char *filename, *linkname; gboolean success; memset (&st, 0, sizeof st); filename = NULL; linkname = NULL; success = vfs_parse_ls_lga (line, &st, &filename, &linkname, NULL); if (!success) error_count++; if (opt_output_format == FORMAT_YAML) yaml_dump_record (success, line, &st, filename, linkname); else ls_dump_record (success, line, &st, filename, linkname); g_free (filename); g_free (linkname); } /* ------------------------------------------------------------------------------ */ static void process_input (FILE * input) { char line[BUF_4K]; while (fgets (line, sizeof line, input) != NULL) { chomp (line); /* Not mandatory. Makes error messages nicer. */ if (strncmp (line, "total ", 6) == 0) /* Convenience only: makes 'ls -l' parse cleanly. */ continue; process_ls_line (line); } } /* ------------------------------------------------------------------------------ */ int main (int argc, char *argv[]) { FILE *input; if (!parse_command_line (&argc, &argv)) return EXIT_FAILURE; if (argc >= 2) { input = fopen (argv[1], "r"); if (input == NULL) { perror (argv[1]); return EXIT_FAILURE; } } else { input = stdin; } process_input (input); return (error_count > 0) ? EXIT_FAILURE : EXIT_SUCCESS; } /* ------------------------------------------------------------------------------ */