Ticket #3730: 3730-0001-extfs-introduce-a-command-line-tool-for-parsing-file-lists.patch

File 3730-0001-extfs-introduce-a-command-line-tool-for-parsing-file-lists.patch, 15.0 KB (added by mooffie, 7 years ago)
  • configure.ac

    From 4a23d5ac09ad46a3fa15c5e19880c941b488ff80 Mon Sep 17 00:00:00 2001
    From: Mooffie <mooffie@gmail.com>
    Date: Fri, 18 Nov 2016 13:14:18 +0200
    Subject: [PATCH 01/11] extfs: introduce a command-line tool for parsing file
     lists.
    
    We introduce 'mc_parse_ls_l', a tool for parsing file-listings in format
    similar to that of 'ls -l'. This format is used by the various extfs helpers.
    
    We'll use this tool, in the next commit, to build a tester.
    ---
     configure.ac                                    |   1 +
     tests/src/Makefile.am                           |   2 +-
     tests/src/extfs-helpers-listcmd/Makefile.am     |  20 ++
     tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c | 400 ++++++++++++++++++++++++
     tests/src/extfs-helpers-listcmd/run             |  15 +
     5 files changed, 437 insertions(+), 1 deletion(-)
     create mode 100644 tests/src/extfs-helpers-listcmd/Makefile.am
     create mode 100644 tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c
     create mode 100755 tests/src/extfs-helpers-listcmd/run
    
    diff --git a/configure.ac b/configure.ac
    index 0442333..a8d144e 100644
    a b tests/src/Makefile 
    638638tests/src/filemanager/Makefile 
    639639tests/src/editor/Makefile 
    640640tests/src/editor/test-data.txt 
     641tests/src/extfs-helpers-listcmd/Makefile 
    641642]) 
    642643fi 
    643644 
  • tests/src/Makefile.am

    diff --git a/tests/src/Makefile.am b/tests/src/Makefile.am
    index 74eecf6..0ab1c6d 100644
    a b  
    11PACKAGE_STRING = "/src" 
    22 
    3 SUBDIRS = . filemanager 
     3SUBDIRS = . filemanager extfs-helpers-listcmd 
    44 
    55if USE_INTERNAL_EDIT 
    66SUBDIRS += editor 
  • new file tests/src/extfs-helpers-listcmd/Makefile.am

    diff --git a/tests/src/extfs-helpers-listcmd/Makefile.am b/tests/src/extfs-helpers-listcmd/Makefile.am
    new file mode 100644
    index 0000000..043fa8a
    - +  
     1PACKAGE_STRING = "/src/extfs-helpers-listcmd" 
     2 
     3AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) 
     4 
     5# This lets mc_parse_ls_l.c override MC's message() without the linker 
     6# complaining about multiple definitions. 
     7AM_LDFLAGS = @TESTS_LDFLAGS@ 
     8 
     9LIBS = $(top_builddir)/lib/libmc.la 
     10 
     11# Programs/scripts to build on 'make check'. 
     12check_PROGRAMS = mc_parse_ls_l 
     13 
     14# Tests to run on 'make check' 
     15TESTS = run 
     16 
     17mc_parse_ls_l_SOURCES = \ 
     18        mc_parse_ls_l.c 
     19 
     20EXTRA_DIST = run 
  • new file tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c

    diff --git a/tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c b/tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c
    new file mode 100644
    index 0000000..6f0fc8b
    - +  
     1/* 
     2   A parser for file-listings formatted like 'ls -l'. 
     3 
     4   Copyright (C) 2016 
     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 <http://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/timefmt.h"        /* FMT_LOCALTIME */ 
     46#include "lib/widget.h"         /* for the prototype of message() only */ 
     47 
     48/*** global variables ****************************************************************************/ 
     49 
     50/*** file scope macro definitions ****************************************************************/ 
     51 
     52/*** file scope type declarations ****************************************************************/ 
     53 
     54typedef enum 
     55{ 
     56    FORMAT_YAML, 
     57    FORMAT_LS 
     58} output_format_t; 
     59 
     60/*** file scope variables ************************************************************************/ 
     61 
     62/* Command-line options. */ 
     63static gboolean opt_drop_mtime = FALSE; 
     64static gboolean opt_drop_ids = FALSE; 
     65static gboolean opt_symbolic_ids = FALSE; 
     66static output_format_t opt_output_format = FORMAT_LS; 
     67 
     68/* Misc. */ 
     69static int error_count = 0; 
     70 
     71/* forward declarations */ 
     72static gboolean 
     73parse_format_name_argument (const gchar * option_name, const gchar * value, gpointer data, 
     74                            GError ** error); 
     75 
     76static GOptionEntry entries[] = { 
     77    {"drop-mtime", 0, 0, G_OPTION_ARG_NONE, &opt_drop_mtime, "Don't include mtime in the output.", 
     78     NULL}, 
     79    {"drop-ids", 0, 0, G_OPTION_ARG_NONE, &opt_drop_ids, "Don't include uid/gid in the output.", 
     80     NULL}, 
     81    {"symbolic-ids", 0, 0, G_OPTION_ARG_NONE, &opt_symbolic_ids, 
     82     "Print the strings '<<uid>>'/'<<gid>>' instead of the numeric IDs when they match the process' uid/gid.", 
     83     NULL}, 
     84    {"format", 'f', 0, G_OPTION_ARG_CALLBACK, parse_format_name_argument, 
     85     "Output format. Default: ls.", "<ls|yaml>"}, 
     86    {NULL, '\0', 0, 0, NULL, NULL, NULL}        /* Make the compiler shut up by initializing everything. */ 
     87}; 
     88 
     89/*** file scope functions ************************************************************************/ 
     90/* --------------------------------------------------------------------------------------------- */ 
     91/** 
     92 * Command-line handling. 
     93 */ 
     94/* --------------------------------------------------------------------------------------------- */ 
     95 
     96static gboolean 
     97parse_format_name_argument (const gchar * option_name, const gchar * value, gpointer data, 
     98                            GError ** error) 
     99{ 
     100    (void) option_name; 
     101    (void) data; 
     102 
     103    if (strcmp (value, "yaml") == 0) 
     104        opt_output_format = FORMAT_YAML; 
     105    else if (strcmp (value, "ls") == 0) 
     106        opt_output_format = FORMAT_LS; 
     107    else 
     108    { 
     109        g_set_error (error, MC_ERROR, G_OPTION_ERROR_FAILED, "unknown output format '%s'", value); 
     110        return FALSE; 
     111    } 
     112 
     113    return TRUE; 
     114} 
     115 
     116/* --------------------------------------------------------------------------------------------- */ 
     117 
     118static gboolean 
     119parse_command_line (int *argc, char **argv[]) 
     120{ 
     121    GError *error = NULL; 
     122    GOptionContext *context; 
     123 
     124    context = 
     125        g_option_context_new 
     126        ("- Parses its input, which is expected to be in a format similar to 'ls -l', and writes the result to stdout."); 
     127    g_option_context_add_main_entries (context, entries, NULL); 
     128    if (!g_option_context_parse (context, argc, argv, &error)) 
     129    { 
     130        g_print ("option parsing failed: %s\n", error->message); 
     131        return FALSE; 
     132    } 
     133 
     134    return TRUE; 
     135} 
     136 
     137/* --------------------------------------------------------------------------------------------- */ 
     138/** 
     139 * Utility functions. 
     140 */ 
     141/* --------------------------------------------------------------------------------------------- */ 
     142 
     143static const char * 
     144my_itoa (int i) 
     145{ 
     146    static char buf[BUF_SMALL]; 
     147    sprintf (buf, "%d", i); 
     148    return buf; 
     149} 
     150 
     151/* --------------------------------------------------------------------------------------------- */ 
     152 
     153/** 
     154 * Returns the uid as-is, or as "<<uid>>" if the same as current user. 
     155 */ 
     156static const char * 
     157symbolic_uid (uid_t uid) 
     158{ 
     159    return (opt_symbolic_ids && uid == getuid ())? "<<uid>>" : my_itoa ((int) uid); 
     160} 
     161 
     162/* --------------------------------------------------------------------------------------------- */ 
     163 
     164static const char * 
     165symbolic_gid (gid_t gid) 
     166{ 
     167    return (opt_symbolic_ids && gid == getgid ())? "<<gid>>" : my_itoa ((int) gid); 
     168} 
     169 
     170/* --------------------------------------------------------------------------------------------- */ 
     171 
     172/** 
     173 * Cuts off a string's line-end (as in Perl). 
     174 */ 
     175static void 
     176chomp (char *s) 
     177{ 
     178    int i; 
     179 
     180    i = strlen (s); 
     181 
     182    /* Code taken from vfs_parse_ls_lga(), with modifications. */ 
     183    if ((--i >= 0) && (s[i] == '\r' || s[i] == '\n')) 
     184        s[i] = '\0'; 
     185    if ((--i >= 0) && (s[i] == '\r' || s[i] == '\n')) 
     186        s[i] = '\0'; 
     187} 
     188 
     189/* --------------------------------------------------------------------------------------------- */ 
     190 
     191static const char * 
     192string_date (time_t time) 
     193{ 
     194    static char buf[BUF_SMALL]; 
     195    /* 
     196     * If we ever want to be able to re-parse the output of this program, 
     197     * we'll need to use the American brain-damaged MM-DD-YYYY instead of 
     198     * YYYY-MM-DD because vfs_parse_ls_lga() doesn't currently recognize 
     199     * the latter. 
     200     */ 
     201    FMT_LOCALTIME (buf, sizeof buf, "%Y-%m-%d %H:%M:%S", time); 
     202    return buf; 
     203} 
     204 
     205/* --------------------------------------------------------------------------------------------- */ 
     206 
     207/** 
     208 * Override MC's message(). 
     209 * 
     210 * vfs_parse_ls_lga() calls this on error. Since MC's uses tty/widgets, it 
     211 * will crash us. We replace it with a plain version. 
     212 */ 
     213void 
     214message (int flags, const char *title, const char *text, ...) 
     215{ 
     216    char *p; 
     217    va_list ap; 
     218 
     219    (void) flags; 
     220    (void) title; 
     221 
     222    va_start (ap, text); 
     223    p = g_strdup_vprintf (text, ap); 
     224    va_end (ap); 
     225    printf ("message(): vfs_parse_ls_lga(): parsing error at: %s\n", p); 
     226    g_free (p); 
     227} 
     228 
     229/* --------------------------------------------------------------------------------------------- */ 
     230/** 
     231 * YAML output format. 
     232 */ 
     233/* --------------------------------------------------------------------------------------------- */ 
     234 
     235static void 
     236yaml_dump_stbuf (const struct stat *st) 
     237{ 
     238    /* Various casts and flags here were taken/inspired by info_show_info(). */ 
     239    printf ("  perm: %s\n", string_perm (st->st_mode)); 
     240    if (!opt_drop_ids) 
     241    { 
     242        printf ("  uid: %s\n", symbolic_uid (st->st_uid)); 
     243        printf ("  gid: %s\n", symbolic_gid (st->st_gid)); 
     244    } 
     245    printf ("  size: %" PRIuMAX "\n", (uintmax_t) st->st_size); 
     246    printf ("  nlink: %d\n", (int) st->st_nlink); 
     247    if (!opt_drop_mtime) 
     248        printf ("  mtime: %s\n", string_date (st->st_mtime)); 
     249} 
     250 
     251/* --------------------------------------------------------------------------------------------- */ 
     252 
     253static void 
     254yaml_dump_string (const char *name, const char *val) 
     255{ 
     256    char *q = g_shell_quote (val); 
     257    printf ("  %s: %s\n", name, q); 
     258    g_free (q); 
     259} 
     260 
     261/* --------------------------------------------------------------------------------------------- */ 
     262 
     263static void 
     264yaml_dump_record (gboolean success, const char *input_line, const struct stat *st, 
     265                  const char *filename, const char *linkname) 
     266{ 
     267    printf ("-\n");             /* Start new item in the list. */ 
     268 
     269    if (success) 
     270    { 
     271        if (filename) 
     272            yaml_dump_string ("name", filename); 
     273        if (linkname) 
     274            yaml_dump_string ("linkname", linkname); 
     275        yaml_dump_stbuf (st); 
     276    } 
     277    else 
     278    { 
     279        yaml_dump_string ("cannot parse input line", input_line); 
     280    } 
     281} 
     282 
     283/* --------------------------------------------------------------------------------------------- */ 
     284/** 
     285 * 'ls' output format. 
     286 */ 
     287/* --------------------------------------------------------------------------------------------- */ 
     288 
     289static void 
     290ls_dump_stbuf (const struct stat *st) 
     291{ 
     292    /* Various casts and flags here were taken/inspired by info_show_info(). */ 
     293    printf ("%s %3d ", string_perm (st->st_mode), (int) st->st_nlink); 
     294    if (!opt_drop_ids) 
     295    { 
     296        printf ("%8s ", symbolic_uid (st->st_uid)); 
     297        printf ("%8s ", symbolic_gid (st->st_gid)); 
     298    } 
     299    printf ("%10" PRIuMAX " ", (uintmax_t) st->st_size); 
     300    if (!opt_drop_mtime) 
     301        printf ("%s ", string_date (st->st_mtime)); 
     302} 
     303 
     304/* --------------------------------------------------------------------------------------------- */ 
     305 
     306static void 
     307ls_dump_record (gboolean success, const char *input_line, const struct stat *st, 
     308                const char *filename, const char *linkname) 
     309{ 
     310    if (success) 
     311    { 
     312        ls_dump_stbuf (st); 
     313        if (filename) 
     314            printf ("%s", filename); 
     315        if (linkname) 
     316            printf (" -> %s", linkname); 
     317        printf ("\n"); 
     318    } 
     319    else 
     320    { 
     321        printf ("cannot parse input line: '%s'\n", input_line); 
     322    } 
     323} 
     324 
     325/* ------------------------------------------------------------------------------ */ 
     326/** 
     327 * Main code. 
     328 */ 
     329/* ------------------------------------------------------------------------------ */ 
     330 
     331static void 
     332process_ls_line (const char *line) 
     333{ 
     334    struct stat st; 
     335    char *filename, *linkname; 
     336    gboolean success; 
     337 
     338    memset (&st, 0, sizeof st); 
     339    filename = NULL; 
     340    linkname = NULL; 
     341 
     342    success = vfs_parse_ls_lga (line, &st, &filename, &linkname, NULL); 
     343 
     344    if (!success) 
     345        error_count++; 
     346 
     347    if (opt_output_format == FORMAT_YAML) 
     348        yaml_dump_record (success, line, &st, filename, linkname); 
     349    else 
     350        ls_dump_record (success, line, &st, filename, linkname); 
     351 
     352    g_free (filename); 
     353    g_free (linkname); 
     354} 
     355 
     356/* ------------------------------------------------------------------------------ */ 
     357 
     358static void 
     359process_input (FILE * input) 
     360{ 
     361    char line[BUF_4K]; 
     362 
     363    while (fgets (line, sizeof line, input) != NULL) 
     364    { 
     365        chomp (line);           /* Not mandatory. Makes error messages nicer. */ 
     366        if (strncmp (line, "total ", 6) == 0)   /* Convenience only: makes 'ls -l' parse cleanly. */ 
     367            continue; 
     368        process_ls_line (line); 
     369    } 
     370} 
     371 
     372/* ------------------------------------------------------------------------------ */ 
     373 
     374int 
     375main (int argc, char *argv[]) 
     376{ 
     377    FILE *input; 
     378 
     379    if (!parse_command_line (&argc, &argv)) 
     380        return EXIT_FAILURE; 
     381 
     382    if (argc >= 2) 
     383    { 
     384        if ((input = fopen (argv[1], "r")) == NULL) 
     385        { 
     386            perror (argv[1]); 
     387            return EXIT_FAILURE; 
     388        } 
     389    } 
     390    else 
     391    { 
     392        input = stdin; 
     393    } 
     394 
     395    process_input (input); 
     396 
     397    return (error_count > 0) ? EXIT_FAILURE : EXIT_SUCCESS; 
     398} 
     399 
     400/* ------------------------------------------------------------------------------ */ 
  • new file tests/src/extfs-helpers-listcmd/run

    diff --git a/tests/src/extfs-helpers-listcmd/run b/tests/src/extfs-helpers-listcmd/run
    new file mode 100755
    index 0000000..7f905e0
    - +  
     1#!/bin/sh 
     2 
     3echo 
     4echo "This is a placeholder." 
     5echo "It will be replaced in the future with a script that does the real" 
     6echo "work of testing the helpers." 
     7echo 
     8echo "Here's a silly test to check whether 'ls', had it been an extfs helper," 
     9echo "works properly:" 
     10echo 
     11 
     12LC_ALL=C ls -l | ./mc_parse_ls_l 
     13 
     14# (LC_ALL=C is meant to prevent date formats MC can't parse. This might 
     15# still not be foolproof, but that's just a demonstration.)