mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-22 12:32:40 +03:00
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:
parent
4b28a7e76e
commit
13a805bd79
@ -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
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
PACKAGE_STRING = "/src"
|
||||
|
||||
SUBDIRS = . filemanager
|
||||
SUBDIRS = . filemanager extfs-helpers-listcmd
|
||||
|
||||
if USE_INTERNAL_EDIT
|
||||
SUBDIRS += editor
|
||||
|
20
tests/src/extfs-helpers-listcmd/Makefile.am
Normal file
20
tests/src/extfs-helpers-listcmd/Makefile.am
Normal 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
|
405
tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c
Normal file
405
tests/src/extfs-helpers-listcmd/mc_parse_ls_l.c
Normal 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;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------ */
|
15
tests/src/extfs-helpers-listcmd/run
Executable file
15
tests/src/extfs-helpers-listcmd/run
Executable 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.)
|
Loading…
Reference in New Issue
Block a user