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