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