Ticket #3730: 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.

Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
This commit is contained in:
Mooffie 2016-11-18 13:14:18 +02:00 committed by Andrew Borodin
parent 4b28a7e76e
commit 13a805bd79
5 changed files with 442 additions and 1 deletions

View File

@ -638,6 +638,7 @@ tests/src/Makefile
tests/src/filemanager/Makefile
tests/src/editor/Makefile
tests/src/editor/test-data.txt
tests/src/extfs-helpers-listcmd/Makefile
])
fi

View File

@ -1,6 +1,6 @@
PACKAGE_STRING = "/src"
SUBDIRS = . filemanager
SUBDIRS = . filemanager extfs-helpers-listcmd
if USE_INTERNAL_EDIT
SUBDIRS += editor

View File

@ -0,0 +1,20 @@
PACKAGE_STRING = "/src/extfs-helpers-listcmd"
AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
# This lets mc_parse_ls_l.c override MC's message() without the linker
# complaining about multiple definitions.
AM_LDFLAGS = @TESTS_LDFLAGS@
LIBS = $(top_builddir)/lib/libmc.la
# Programs/scripts to build on 'make check'.
check_PROGRAMS = mc_parse_ls_l
# Tests to run on 'make check'
TESTS = run
mc_parse_ls_l_SOURCES = \
mc_parse_ls_l.c
EXTRA_DIST = run

View File

@ -0,0 +1,405 @@
/*
A parser for file-listings formatted like 'ls -l'.
Copyright (C) 2016
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 <http://www.gnu.org/licenses/>.
*/
/** \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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#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;
/*** 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;
/* forward declarations */
static gboolean
parse_format_name_argument (const gchar * option_name, const gchar * value, gpointer data,
GError ** error);
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 '<<uid>>'/'<<gid>>' 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.", "<ls|yaml>"},
{NULL, '\0', 0, 0, NULL, NULL, NULL} /* Make the compiler shut up by initializing everything. */
};
/*** 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);
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 "<<uid>>" if the same as current user.
*/
static const char *
symbolic_uid (uid_t uid)
{
return (opt_symbolic_ids && uid == getuid ())? "<<uid>>" : my_itoa ((int) uid);
}
/* --------------------------------------------------------------------------------------------- */
static const char *
symbolic_gid (gid_t gid)
{
return (opt_symbolic_ids && gid == getgid ())? "<<gid>>" : 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 time)
{
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", time);
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;
}
/* ------------------------------------------------------------------------------ */

View File

@ -0,0 +1,15 @@
#!/bin/sh
echo
echo "This is a placeholder."
echo "It will be replaced in the future with a script that does the real"
echo "work of testing the helpers."
echo
echo "Here's a silly test to check whether 'ls', had it been an extfs helper,"
echo "works properly:"
echo
LC_ALL=C ls -l | ./mc_parse_ls_l
# (LC_ALL=C is meant to prevent date formats MC can't parse. This might
# still not be foolproof, but that's just a demonstration.)