root/tests/src/vfs/extfs/helpers-list/mc_parse_ls_l.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. parse_format_name_argument
  2. parse_command_line
  3. my_itoa
  4. symbolic_uid
  5. symbolic_gid
  6. string_date
  7. message
  8. yaml_dump_stbuf
  9. yaml_dump_string
  10. yaml_dump_record
  11. ls_dump_stbuf
  12. ls_dump_record
  13. process_ls_line
  14. process_input
  15. main

   1 /*
   2    A parser for file-listings formatted like 'ls -l'.
   3 
   4    Copyright (C) 2016-2025
   5    Free Software Foundation, Inc.
   6 
   7    This file is part of the Midnight Commander.
   8 
   9    The Midnight Commander is free software: you can redistribute it
  10    and/or modify it under the terms of the GNU General Public License as
  11    published by the Free Software Foundation, either version 3 of the License,
  12    or (at your option) any later version.
  13 
  14    The Midnight Commander is distributed in the hope that it will be useful,
  15    but WITHOUT ANY WARRANTY; without even the implied warranty of
  16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17    GNU General Public License for more details.
  18 
  19    You should have received a copy of the GNU General Public License
  20    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  21  */
  22 
  23 /** \file
  24  *  \brief A parser for file-listings formatted like 'ls -l'.
  25  *
  26  * This program parses file-listings the same way MC does.
  27  * It's basically a wrapper around vfs_parse_ls_lga().
  28  * You can feed it the output of any of extfs's helpers.
  29  *
  30  * After parsing, the data is written out to stdout. The output
  31  * format can be either YAML or a format similar to 'ls -l'.
  32  *
  33  * This program is the main tool our tester script is going to use.
  34  */
  35 
  36 #include <config.h>
  37 
  38 #include <stdio.h>
  39 #include <stdlib.h>
  40 
  41 #include "lib/global.h"
  42 
  43 #include "lib/vfs/utilvfs.h"  // vfs_parse_ls_lga()
  44 #include "lib/util.h"         // string_perm()
  45 #include "lib/strutil.h"
  46 #include "lib/timefmt.h"  // FMT_LOCALTIME
  47 #include "lib/widget.h"   // for the prototype of message() only
  48 
  49 /*** global variables ****************************************************************************/
  50 
  51 /*** file scope macro definitions ****************************************************************/
  52 
  53 /*** file scope type declarations ****************************************************************/
  54 
  55 typedef enum
  56 {
  57     FORMAT_YAML,
  58     FORMAT_LS
  59 } output_format_t;
  60 
  61 /*** forward declarations (file scope functions) *************************************************/
  62 
  63 static gboolean parse_format_name_argument (const gchar *option_name, const gchar *value,
  64                                             gpointer data, GError **error);
  65 
  66 /*** file scope variables ************************************************************************/
  67 
  68 /* Command-line options. */
  69 static gboolean opt_drop_mtime = FALSE;
  70 static gboolean opt_drop_ids = FALSE;
  71 static gboolean opt_symbolic_ids = FALSE;
  72 static output_format_t opt_output_format = FORMAT_LS;
  73 
  74 /* Misc. */
  75 static int error_count = 0;
  76 
  77 static GOptionEntry entries[] = {
  78     {
  79         "drop-mtime",
  80         0,
  81         0,
  82         G_OPTION_ARG_NONE,
  83         &opt_drop_mtime,
  84         "Don't include mtime in the output.",
  85         NULL,
  86     },
  87     {
  88         "drop-ids",
  89         0,
  90         0,
  91         G_OPTION_ARG_NONE,
  92         &opt_drop_ids,
  93         "Don't include uid/gid in the output.",
  94         NULL,
  95     },
  96     {
  97         "symbolic-ids",
  98         0,
  99         0,
 100         G_OPTION_ARG_NONE,
 101         &opt_symbolic_ids,
 102         "Print the strings '<<uid>>'/'<<gid>>' instead of the numeric IDs when they match the "
 103         "process' uid/gid.",
 104         NULL,
 105     },
 106     {
 107         "format",
 108         'f',
 109         0,
 110         G_OPTION_ARG_CALLBACK,
 111         parse_format_name_argument,
 112         "Output format. Default: ls.",
 113         "<ls|yaml>",
 114     },
 115     G_OPTION_ENTRY_NULL,
 116 };
 117 
 118 /* --------------------------------------------------------------------------------------------- */
 119 /*** file scope functions ************************************************************************/
 120 /* --------------------------------------------------------------------------------------------- */
 121 /**
 122  * Command-line handling.
 123  */
 124 /* --------------------------------------------------------------------------------------------- */
 125 
 126 static gboolean
 127 parse_format_name_argument (const gchar *option_name, const gchar *value, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 128                             GError **error)
 129 {
 130     (void) option_name;
 131     (void) data;
 132 
 133     if (strcmp (value, "yaml") == 0)
 134         opt_output_format = FORMAT_YAML;
 135     else if (strcmp (value, "ls") == 0)
 136         opt_output_format = FORMAT_LS;
 137     else
 138     {
 139         g_set_error (error, MC_ERROR, G_OPTION_ERROR_FAILED, "unknown output format '%s'", value);
 140         return FALSE;
 141     }
 142 
 143     return TRUE;
 144 }
 145 
 146 /* --------------------------------------------------------------------------------------------- */
 147 
 148 static gboolean
 149 parse_command_line (int *argc, char **argv[])
     /* [previous][next][first][last][top][bottom][index][help]  */
 150 {
 151     GError *error = NULL;
 152     GOptionContext *context;
 153 
 154     context = g_option_context_new ("- Parses its input, which is expected to be in a format "
 155                                     "similar to 'ls -l', and writes the result to stdout.");
 156     g_option_context_add_main_entries (context, entries, NULL);
 157     if (!g_option_context_parse (context, argc, argv, &error))
 158     {
 159         g_print ("option parsing failed: %s\n", error->message);
 160         g_error_free (error);
 161 
 162         return FALSE;
 163     }
 164 
 165     return TRUE;
 166 }
 167 
 168 /* --------------------------------------------------------------------------------------------- */
 169 /**
 170  * Utility functions.
 171  */
 172 /* --------------------------------------------------------------------------------------------- */
 173 
 174 static const char *
 175 my_itoa (int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 176 {
 177     static char buf[BUF_SMALL];
 178 
 179     sprintf (buf, "%d", i);
 180     return buf;
 181 }
 182 
 183 /* --------------------------------------------------------------------------------------------- */
 184 
 185 /**
 186  * Returns the uid as-is, or as "<<uid>>" if the same as current user.
 187  */
 188 static const char *
 189 symbolic_uid (uid_t uid)
     /* [previous][next][first][last][top][bottom][index][help]  */
 190 {
 191     return (opt_symbolic_ids && uid == getuid ()) ? "<<uid>>" : my_itoa ((int) uid);
 192 }
 193 
 194 /* --------------------------------------------------------------------------------------------- */
 195 
 196 static const char *
 197 symbolic_gid (gid_t gid)
     /* [previous][next][first][last][top][bottom][index][help]  */
 198 {
 199     return (opt_symbolic_ids && gid == getgid ()) ? "<<gid>>" : my_itoa ((int) gid);
 200 }
 201 
 202 /* --------------------------------------------------------------------------------------------- */
 203 
 204 static const char *
 205 string_date (time_t t)
     /* [previous][next][first][last][top][bottom][index][help]  */
 206 {
 207     static char buf[BUF_SMALL];
 208 
 209     /*
 210      * If we ever want to be able to re-parse the output of this program,
 211      * we'll need to use the American brain-damaged MM-DD-YYYY instead of
 212      * YYYY-MM-DD because vfs_parse_ls_lga() doesn't currently recognize
 213      * the latter.
 214      */
 215     FMT_LOCALTIME (buf, sizeof buf, "%Y-%m-%d %H:%M:%S", t);
 216     return buf;
 217 }
 218 
 219 /* --------------------------------------------------------------------------------------------- */
 220 
 221 /**
 222  * Override MC's message().
 223  *
 224  * vfs_parse_ls_lga() calls this on error. Since MC's uses tty/widgets, it
 225  * will crash us. We replace it with a plain version.
 226  */
 227 void
 228 message (int flags, const char *title, const char *text, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 229 {
 230     char *p;
 231     va_list ap;
 232 
 233     (void) flags;
 234     (void) title;
 235 
 236     va_start (ap, text);
 237     p = g_strdup_vprintf (text, ap);
 238     va_end (ap);
 239     printf ("message(): vfs_parse_ls_lga(): parsing error at: %s\n", p);
 240     g_free (p);
 241 }
 242 
 243 /* --------------------------------------------------------------------------------------------- */
 244 /**
 245  * YAML output format.
 246  */
 247 /* --------------------------------------------------------------------------------------------- */
 248 
 249 static void
 250 yaml_dump_stbuf (const struct stat *st)
     /* [previous][next][first][last][top][bottom][index][help]  */
 251 {
 252     // Various casts and flags here were taken/inspired by info_show_info()
 253     printf ("  perm: %s\n", string_perm (st->st_mode));
 254     if (!opt_drop_ids)
 255     {
 256         printf ("  uid: %s\n", symbolic_uid (st->st_uid));
 257         printf ("  gid: %s\n", symbolic_gid (st->st_gid));
 258     }
 259     printf ("  size: %" PRIuMAX "\n", (uintmax_t) st->st_size);
 260     printf ("  nlink: %d\n", (int) st->st_nlink);
 261     if (!opt_drop_mtime)
 262         printf ("  mtime: %s\n", string_date (st->st_mtime));
 263 }
 264 
 265 /* --------------------------------------------------------------------------------------------- */
 266 
 267 static void
 268 yaml_dump_string (const char *name, const char *val)
     /* [previous][next][first][last][top][bottom][index][help]  */
 269 {
 270     char *q;
 271 
 272     q = g_shell_quote (val);
 273     printf ("  %s: %s\n", name, q);
 274     g_free (q);
 275 }
 276 
 277 /* --------------------------------------------------------------------------------------------- */
 278 
 279 static void
 280 yaml_dump_record (gboolean success, const char *input_line, const struct stat *st,
     /* [previous][next][first][last][top][bottom][index][help]  */
 281                   const char *filename, const char *linkname)
 282 {
 283     printf ("-\n");  // Start new item in the list.
 284 
 285     if (success)
 286     {
 287         if (filename != NULL)
 288             yaml_dump_string ("name", filename);
 289         if (linkname != NULL)
 290             yaml_dump_string ("linkname", linkname);
 291         yaml_dump_stbuf (st);
 292     }
 293     else
 294     {
 295         yaml_dump_string ("cannot parse input line", input_line);
 296     }
 297 }
 298 
 299 /* --------------------------------------------------------------------------------------------- */
 300 /**
 301  * 'ls' output format.
 302  */
 303 /* --------------------------------------------------------------------------------------------- */
 304 
 305 static void
 306 ls_dump_stbuf (const struct stat *st)
     /* [previous][next][first][last][top][bottom][index][help]  */
 307 {
 308     // Various casts and flags here were taken/inspired by info_show_info()
 309     printf ("%s %3d ", string_perm (st->st_mode), (int) st->st_nlink);
 310     if (!opt_drop_ids)
 311     {
 312         printf ("%8s ", symbolic_uid (st->st_uid));
 313         printf ("%8s ", symbolic_gid (st->st_gid));
 314     }
 315     printf ("%10" PRIuMAX " ", (uintmax_t) st->st_size);
 316     if (!opt_drop_mtime)
 317         printf ("%s ", string_date (st->st_mtime));
 318 }
 319 
 320 /* --------------------------------------------------------------------------------------------- */
 321 
 322 static void
 323 ls_dump_record (gboolean success, const char *input_line, const struct stat *st,
     /* [previous][next][first][last][top][bottom][index][help]  */
 324                 const char *filename, const char *linkname)
 325 {
 326     if (success)
 327     {
 328         ls_dump_stbuf (st);
 329         if (filename != NULL)
 330             printf ("%s", filename);
 331         if (linkname != NULL)
 332             printf (" -> %s", linkname);
 333         printf ("\n");
 334     }
 335     else
 336     {
 337         printf ("cannot parse input line: '%s'\n", input_line);
 338     }
 339 }
 340 
 341 /* ------------------------------------------------------------------------------ */
 342 /**
 343  * Main code.
 344  */
 345 /* ------------------------------------------------------------------------------ */
 346 
 347 static void
 348 process_ls_line (const char *line)
     /* [previous][next][first][last][top][bottom][index][help]  */
 349 {
 350     struct stat st;
 351     char *filename, *linkname;
 352     gboolean success;
 353 
 354     memset (&st, 0, sizeof st);
 355     filename = NULL;
 356     linkname = NULL;
 357 
 358     success = vfs_parse_ls_lga (line, &st, &filename, &linkname, NULL);
 359 
 360     if (!success)
 361         error_count++;
 362 
 363     if (opt_output_format == FORMAT_YAML)
 364         yaml_dump_record (success, line, &st, filename, linkname);
 365     else
 366         ls_dump_record (success, line, &st, filename, linkname);
 367 
 368     g_free (filename);
 369     g_free (linkname);
 370 }
 371 
 372 /* ------------------------------------------------------------------------------ */
 373 
 374 static void
 375 process_input (FILE *input)
     /* [previous][next][first][last][top][bottom][index][help]  */
 376 {
 377     char line[BUF_4K];
 378 
 379     while (fgets (line, sizeof line, input) != NULL)
 380     {
 381         str_rstrip_eol (line);                 // Not mandatory. Makes error messages nicer.
 382         if (strncmp (line, "total ", 6) == 0)  // Convenience only: makes 'ls -l' parse cleanly.
 383             continue;
 384         process_ls_line (line);
 385     }
 386 }
 387 
 388 /* ------------------------------------------------------------------------------ */
 389 
 390 int
 391 main (int argc, char *argv[])
     /* [previous][next][first][last][top][bottom][index][help]  */
 392 {
 393     FILE *input;
 394 
 395     if (!parse_command_line (&argc, &argv))
 396         return EXIT_FAILURE;
 397 
 398     if (argc >= 2)
 399     {
 400         input = fopen (argv[1], "r");
 401         if (input == NULL)
 402         {
 403             perror (argv[1]);
 404             return EXIT_FAILURE;
 405         }
 406     }
 407     else
 408     {
 409         input = stdin;
 410     }
 411 
 412     process_input (input);
 413 
 414     return (error_count > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
 415 }
 416 
 417 /* ------------------------------------------------------------------------------ */

/* [previous][next][first][last][top][bottom][index][help]  */