1 /*-
2  * Copyright (c) 2011-2021 Ganael LAPLANCHE <ganael.laplanche@martymac.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include "fpart.h"
28 #include "types.h"
29 #include "utils.h"
30 #include "options.h"
31 #include "partition.h"
32 #include "file_entry.h"
33 #include "dispatch.h"
34 
35 /* NULL, exit(3) */
36 #include <stdlib.h>
37 
38 /* strtoumax(3) */
39 #include <limits.h>
40 #include <inttypes.h>
41 
42 /* fprintf(3), fopen(3), fclose(3), fgets(3), foef(3) */
43 #include <stdio.h>
44 
45 /* getopt(3) */
46 #include <unistd.h>
47 #if !defined(__SunOS_5_9)
48 #include <getopt.h>
49 #endif
50 
51 /* strlen(3) */
52 #include <string.h>
53 
54 /* bzero(3) */
55 #include <strings.h>
56 
57 /* errno */
58 #include <errno.h>
59 
60 /* assert(3) */
61 #include <assert.h>
62 
63 /* Print version */
64 static void
version(void)65 version(void)
66 {
67     fprintf(stderr, "fpart v" FPART_VERSION "\n"
68         "Copyright (c) 2011-2021 "
69         "Ganael LAPLANCHE <ganael.laplanche@martymac.org>\n"
70         "WWW: http://contribs.martymac.org\n");
71     fprintf(stderr, "Build options: debug=");
72 #if defined(DEBUG)
73     fprintf(stderr, "yes, fts=");
74 #else
75     fprintf(stderr, "no, fts=");
76 #endif
77 #if defined(EMBED_FTS)
78     fprintf(stderr, "embedded\n");
79 #else
80     fprintf(stderr, "system\n");
81 #endif
82 }
83 
84 /* Print usage */
85 static void
usage(void)86 usage(void)
87 {
88     fprintf(stderr, "Usage: fpart [OPTIONS] -n num | -f files | -s size "
89         "[FILE or DIR...]\n");
90     fprintf(stderr, "Sort and pack files into partitions.\n");
91     fprintf(stderr, "\n");
92     fprintf(stderr, "General options:\n");
93     fprintf(stderr, "  -h\tthis help\n");
94     fprintf(stderr, "  -V\tprint version\n");
95     fprintf(stderr, "\n");
96     fprintf(stderr, "Partition control:\n");
97     fprintf(stderr, "  -n\tpack files into <num> partitions\n");
98     fprintf(stderr, "  -f\tlimit partitions to <files> files or directories\n");
99     fprintf(stderr, "  -s\tlimit partitions to <size> bytes\n");
100     fprintf(stderr, "\n");
101     fprintf(stderr, "Input control:\n");
102     fprintf(stderr, "  -i\tread file list from <infile> "
103         "(stdin if '-' is specified)\n");
104     fprintf(stderr, "  -a\tinput contains arbitrary values "
105         "(do not crawl filesystem)\n");
106     fprintf(stderr, "\n");
107     fprintf(stderr, "Output control:\n");
108     fprintf(stderr, "  -o\toutput partitions to <outfile> template "
109         "(stdout if '-' is specified)\n");
110     fprintf(stderr, "  -0\tend filenames with a null (\\0) character when "
111         "using option -o\n");
112     fprintf(stderr, "  -e\tadd ending slash to directories\n");
113     fprintf(stderr, "  -v\tverbose mode (may be specified more than once to "
114         "increase verbosity)\n");
115     fprintf(stderr, "\n");
116     fprintf(stderr, "Filesystem crawling control:\n");
117     fprintf(stderr, "  -l\tfollow symbolic links\n");
118     fprintf(stderr, "  -b\tdo not cross filesystem boundaries\n");
119     fprintf(stderr, "  -y\tinclude files matching <pattern> only (may be "
120         "specified more than once)\n");
121 #if defined(_HAS_FNM_CASEFOLD)
122     fprintf(stderr, "  -Y\tsame as -y, but ignore case\n");
123 #endif
124     fprintf(stderr, "  -x\texclude files matching <pattern> (may be specified "
125         "more than once)\n");
126 #if defined(_HAS_FNM_CASEFOLD)
127     fprintf(stderr, "  -X\tsame as -x, but ignore case\n");
128 #endif
129     fprintf(stderr, "\n");
130     fprintf(stderr, "Directory handling:\n");
131     fprintf(stderr, "  -z\tpack empty directories too "
132         "(default: pack files only)\n");
133     fprintf(stderr, "  -zz\ttreat un-readable directories as empty\n");
134     fprintf(stderr, "  -zzz\tpack all directories (as empty)\n");
135     fprintf(stderr, "  -d\tpack directories instead of files after a certain "
136         "<depth>\n");
137     fprintf(stderr, "  -D\tpack leaf directories (i.e. containing files only, "
138         "implies -z)\n");
139     fprintf(stderr, "  -E\tpack directories instead of files (implies -D)\n");
140     fprintf(stderr, "\n");
141     fprintf(stderr, "Live mode:\n");
142     fprintf(stderr, "  -L\tlive mode: generate partitions during filesystem "
143         "crawling\n");
144     fprintf(stderr, "  -w\tpre-partition hook: execute <cmd> at partition "
145         "start\n");
146     fprintf(stderr, "  -W\tpost-partition hook: execute <cmd> at partition "
147         "end\n");
148     fprintf(stderr, "\n");
149     fprintf(stderr, "Size handling:\n");
150     fprintf(stderr, "  -p\tpreload each partition with <num> bytes\n");
151     fprintf(stderr, "  -q\toverload each file with <num> bytes\n");
152     fprintf(stderr, "  -r\tround each file size up to next <num> bytes "
153         "multiple\n");
154     fprintf(stderr, "\n");
155     fprintf(stderr, "Example: fpart -n 3 -o var-parts /var\n");
156     fprintf(stderr, "\n");
157     fprintf(stderr, "Please report bugs to Ganael LAPLANCHE "
158         "<ganael.laplanche@martymac.org>\n");
159     return;
160 }
161 
162 /* Handle one argument (either a path to crawl or an arbitrary
163    value) and update file entries (head)
164    - returns != 0 if a critical error occurred
165    - returns with head set to the last element added
166    - updates totalfiles with the number of elements added */
167 static int
handle_argument(char * argument,fnum_t * totalfiles,struct file_entry ** head,struct program_options * options)168 handle_argument(char *argument, fnum_t *totalfiles, struct file_entry **head,
169     struct program_options *options)
170 {
171     assert(argument != NULL);
172     assert(totalfiles != NULL);
173     assert(head != NULL);
174     assert(options != NULL);
175 
176     if(options->arbitrary_values == OPT_ARBITRARYVALUES) {
177     /* handle arbitrary values */
178         fsize_t input_size = 0;
179         char *input_path = NULL;
180         if_not_malloc(input_path, strlen(argument) + 1,
181             return (1);
182         )
183 
184         if(sscanf(argument, "%ju %[^\n]", &input_size, input_path) == 2) {
185             if(handle_file_entry(head, input_path, input_size, options) == 0)
186                 (*totalfiles)++;
187             else {
188                 fprintf(stderr, "%s(): cannot add file entry\n", __func__);
189                 free(input_path);
190                 return (1);
191             }
192         }
193         else
194             fprintf(stderr, "error parsing input values: %s\n", argument);
195 
196         /* cleanup */
197         free(input_path);
198     }
199     else {
200     /* handle paths, must examine filesystem */
201         char *input_path = NULL;
202         size_t input_path_len = strlen(argument);
203         size_t malloc_size = input_path_len + 1;
204         if_not_malloc(input_path, malloc_size,
205             return (1);
206         )
207         snprintf(input_path, malloc_size, "%s", argument);
208 
209         /* remove multiple ending slashes */
210         while((input_path_len > 1) &&
211             (input_path[input_path_len - 1] == '/')  &&
212             (input_path[input_path_len - 2] == '/')) {
213             input_path[input_path_len - 1] = '\0';
214             input_path_len--;
215         }
216 
217         /* crawl path */
218         if(input_path[0] != '\0') {
219 #if defined(DEBUG)
220             fprintf(stderr, "init_file_entries(): examining %s\n",
221                 input_path);
222 #endif
223             if(init_file_entries(input_path, head, totalfiles, options) != 0) {
224                 fprintf(stderr, "%s(): cannot initialize file entries\n",
225                     __func__);
226                 free(input_path);
227                 return (1);
228             }
229         }
230 
231         /* cleanup */
232         free(input_path);
233     }
234 
235     return (0);
236 }
237 
238 /* Handle options parsing
239    - initializes options structure using argc and argv (through pointers)
240    - returns a value defined by the mask below */
241 static int
handle_options(struct program_options * options,int * argcp,char *** argvp)242 handle_options(struct program_options *options, int *argcp, char ***argvp)
243 {
244     /* Return code mask */
245 #define FPART_OPTS_OK       0 /* OK */
246 #define FPART_OPTS_NOK      1 /* Error */
247 #define FPART_OPTS_EXIT     2 /* exit(3) required */
248 #define FPART_OPTS_USAGE    4 /* usage() call required */
249 #define FPART_OPTS_VERSION  8 /* version() call required */
250 
251     assert(options != NULL);
252     assert(argcp != NULL);
253     assert(*argcp > 0);
254     assert(argvp != NULL);
255     assert(*argvp != NULL);
256 
257     /* Options handling */
258     extern char *optarg;
259     extern int optind;
260     int ch;
261     while((ch = getopt(*argcp, *argvp,
262 #if defined(_HAS_FNM_CASEFOLD)
263         "hVn:f:s:i:ao:0evlby:Y:x:X:zd:DELw:W:p:q:r:"
264 #else
265         "hVn:f:s:i:ao:0evlby:x:zd:DELw:W:p:q:r:"
266 #endif
267         )) != -1) {
268         switch(ch) {
269             case 'h':
270                 return (FPART_OPTS_USAGE | FPART_OPTS_OK | FPART_OPTS_EXIT);
271             case 'V':
272                 return (FPART_OPTS_VERSION | FPART_OPTS_OK | FPART_OPTS_EXIT);
273             case 'n':
274             {
275                 uintmax_t num_parts = str_to_uintmax(optarg, 0);
276                 if(num_parts == 0) {
277                     fprintf(stderr,
278                         "Option -n requires a value greater than 0.\n");
279                     return (FPART_OPTS_USAGE |
280                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
281                 }
282                 options->num_parts = (pnum_t)num_parts;
283                 break;
284             }
285             case 'f':
286             {
287                 uintmax_t max_entries = str_to_uintmax(optarg, 0);
288                 if(max_entries == 0) {
289                     fprintf(stderr,
290                         "Option -f requires a value greater than 0.\n");
291                     return (FPART_OPTS_USAGE |
292                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
293                 }
294                 options->max_entries = (fnum_t)max_entries;
295                 break;
296             }
297             case 's':
298             {
299                 uintmax_t max_size = str_to_uintmax(optarg, 1);
300                 if(max_size == 0) {
301                     fprintf(stderr,
302                         "Option -s requires a value greater than 0.\n");
303                     return (FPART_OPTS_USAGE |
304                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
305                 }
306                 options->max_size = (fsize_t)max_size;
307                 break;
308             }
309             case 'i':
310             {
311                 /* check for empty argument */
312                 if(strlen(optarg) == 0)
313                     break;
314                 /* replace previous filename if '-i' specified multiple times */
315                 if(options->in_filename != NULL)
316                     free(options->in_filename);
317                 options->in_filename = abs_path(optarg);
318                 if(options->in_filename == NULL) {
319                     fprintf(stderr, "%s(): cannot determine absolute path for "
320                         "file '%s'\n", __func__, optarg);
321                     return (FPART_OPTS_NOK | FPART_OPTS_EXIT);
322                 }
323                 break;
324             }
325             case 'a':
326                 options->arbitrary_values = OPT_ARBITRARYVALUES;
327                 break;
328             case 'o':
329             {
330                 /* check for empty argument */
331                 if(strlen(optarg) == 0)
332                     break;
333                 /* replace previous filename if '-o' specified multiple times */
334                 if(options->out_filename != NULL)
335                     free(options->out_filename);
336                 /* '-' goes to stdout */
337                 if((optarg[0] == '-') && (optarg[1] == '\0')) {
338                     options->out_filename = NULL;
339                 } else {
340                     options->out_filename = abs_path(optarg);
341                     if(options->out_filename == NULL) {
342                         fprintf(stderr, "%s(): cannot determine absolute path "
343                             "for file '%s'\n", __func__, optarg);
344                         return (FPART_OPTS_NOK | FPART_OPTS_EXIT);
345                     }
346                 }
347                 break;
348             }
349             case '0':
350                 options->out_zero = OPT_OUT0;
351                 break;
352             case 'e':
353                 options->add_slash = OPT_ADDSLASH;
354                 break;
355             case 'v':
356                 options->verbose++;
357                 break;
358             case 'l':
359                 options->follow_symbolic_links = OPT_FOLLOWSYMLINKS;
360                 break;
361             case 'b':
362                 options->cross_fs_boundaries = OPT_NOCROSSFSBOUNDARIES;
363                 break;
364             case 'y':
365             case 'Y':   /* needs _HAS_FNM_CASEFOLD */
366             case 'x':
367             case 'X':   /* needs _HAS_FNM_CASEFOLD */
368             {
369                 char ***dst_list = NULL;
370                 unsigned int *dst_num = NULL;
371                 switch(ch) {
372                     case 'y':
373                         dst_list = &(options->include_files);
374                         dst_num = &(options->ninclude_files);
375                     break;
376                     case 'Y':
377                         dst_list = &(options->include_files_ci);
378                         dst_num = &(options->ninclude_files_ci);
379                     break;
380                     case 'x':
381                         dst_list = &(options->exclude_files);
382                         dst_num = &(options->nexclude_files);
383                     break;
384                     case 'X':
385                         dst_list = &(options->exclude_files_ci);
386                         dst_num = &(options->nexclude_files_ci);
387                     break;
388                 }
389                 /* check for empty argument */
390                 if(strlen(optarg) == 0)
391                     break;
392                 /* push string */
393                 if(str_push(dst_list, dst_num, optarg) != 0)
394                     return (FPART_OPTS_NOK | FPART_OPTS_EXIT);
395                 break;
396             }
397             case 'z':
398                 options->dirs_include++;
399                 break;
400             case 'd':
401             {
402                 char *endptr = NULL;
403                 long dir_depth = strtol(optarg, &endptr, 10);
404                 /* refuse values < 0 (-1 being used to disable this option) */
405                 if((endptr == optarg) || (*endptr != '\0') || (dir_depth < 0))
406                     return (FPART_OPTS_USAGE |
407                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
408                 options->dir_depth = (int)dir_depth;
409                 break;
410             }
411             case 'D':
412                 options->leaf_dirs = OPT_LEAFDIRS;
413                 break;
414             case 'E':
415                 options->dirs_only = OPT_DIRSONLY;
416                 options->leaf_dirs = OPT_LEAFDIRS;
417                 break;
418             case 'L':
419                 options->live_mode = OPT_LIVEMODE;
420                 break;
421             case 'w':
422             {
423                 /* check for empty argument */
424                 size_t malloc_size = strlen(optarg) + 1;
425                 if(malloc_size <= 1)
426                     break;
427                 /* replace previous hook if '-w' specified multiple times */
428                 if(options->pre_part_hook != NULL)
429                     free(options->pre_part_hook);
430                 if_not_malloc(options->pre_part_hook, malloc_size,
431                     return (FPART_OPTS_NOK | FPART_OPTS_EXIT);
432                 )
433                 snprintf(options->pre_part_hook, malloc_size, "%s", optarg);
434                 break;
435             }
436             case 'W':
437             {
438                 /* check for empty argument */
439                 size_t malloc_size = strlen(optarg) + 1;
440                 if(malloc_size <= 1)
441                     break;
442                 /* replace previous hook if '-W' specified multiple times */
443                 if(options->post_part_hook != NULL)
444                     free(options->post_part_hook);
445                 if_not_malloc(options->post_part_hook, malloc_size,
446                     return (FPART_OPTS_NOK | FPART_OPTS_EXIT);
447                 )
448                 snprintf(options->post_part_hook, malloc_size, "%s", optarg);
449                 break;
450             }
451             case 'p':
452             {
453                 uintmax_t preload_size = str_to_uintmax(optarg, 1);
454                 if(preload_size == 0) {
455                     fprintf(stderr,
456                         "Option -p requires a value greater than 0.\n");
457                     return (FPART_OPTS_USAGE |
458                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
459                 }
460                 options->preload_size = (fsize_t)preload_size;
461                 break;
462             }
463             case 'q':
464             {
465                 uintmax_t overload_size = str_to_uintmax(optarg, 1);
466                 if(overload_size == 0) {
467                     fprintf(stderr,
468                         "Option -q requires a value greater than 0.\n");
469                     return (FPART_OPTS_USAGE |
470                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
471                 }
472                 options->overload_size = (fsize_t)overload_size;
473                 break;
474             }
475             case 'r':
476             {
477                 uintmax_t round_size = str_to_uintmax(optarg, 1);
478                 if(round_size <= 1) {
479                     fprintf(stderr,
480                         "Option -r requires a value greater than 1.\n");
481                     return (FPART_OPTS_USAGE |
482                         FPART_OPTS_NOK | FPART_OPTS_EXIT);
483                 }
484                 options->round_size = (fsize_t)round_size;
485                 break;
486             }
487             case '?':
488             default:
489                 return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
490         }
491     }
492     *argcp -= optind;
493     *argvp += optind;
494 
495     /* check for options consistency */
496     if((options->num_parts == DFLT_OPT_NUM_PARTS) &&
497         (options->max_entries == DFLT_OPT_MAX_ENTRIES) &&
498         (options->max_size == DFLT_OPT_MAX_SIZE)) {
499         fprintf(stderr, "Please specify either -n, -f or -s.\n");
500         return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
501     }
502 
503     if((options->num_parts != DFLT_OPT_NUM_PARTS) &&
504         ((options->max_entries != DFLT_OPT_MAX_ENTRIES) ||
505                     (options->max_size != DFLT_OPT_MAX_SIZE) ||
506                     (options->live_mode != DFLT_OPT_LIVEMODE))) {
507         fprintf(stderr,
508             "Option -n is incompatible with options -f, -s and -L.\n");
509         return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
510     }
511 
512     if(options->arbitrary_values == OPT_ARBITRARYVALUES) {
513         if((options->add_slash != DFLT_OPT_ADDSLASH) ||
514             (options->follow_symbolic_links != DFLT_OPT_FOLLOWSYMLINKS) ||
515             (options->cross_fs_boundaries != DFLT_OPT_CROSSFSBOUNDARIES) ||
516             (options->include_files != NULL) ||
517             (options->include_files_ci != NULL) ||
518             (options->exclude_files != NULL) ||
519             (options->exclude_files_ci != NULL) ||
520             (options->dirs_include != DFLT_OPT_DIRSINCLUDE) ||
521             (options->dir_depth != DFLT_OPT_DIR_DEPTH) ||
522             (options->leaf_dirs != DFLT_OPT_LEAFDIRS) ||
523             (options->dirs_only != DFLT_OPT_DIRSONLY)) {
524             fprintf(stderr,
525                 "Option -a is incompatible with crawling-related options.\n");
526             return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
527         }
528     }
529 
530     if((options->out_zero == OPT_OUT0) &&
531         options->out_filename == NULL) {
532         fprintf(stderr,
533             "Option -0 is valid only when used with option -o.\n");
534         return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
535     }
536 
537     /* We do not want to mix -E and -d as directory sizes are computed
538        differently for those options: -E produces a single-depth total while -d
539        computes a recursive total */
540     if((options->dirs_only == OPT_DIRSONLY) &&
541         (options->dir_depth != DFLT_OPT_DIR_DEPTH)) {
542         fprintf(stderr,
543             "Option -E is incompatible with option -d.\n");
544         return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
545     }
546 
547     /* Options -D and -E imply empty dirs request (option -z) */
548     if(options->leaf_dirs == OPT_LEAFDIRS)
549         options->dirs_include = max(options->dirs_include, OPT_EMPTYDIRS);
550 
551     if((options->live_mode == OPT_NOLIVEMODE) &&
552         ((options->pre_part_hook != NULL) ||
553         (options->post_part_hook != NULL))) {
554         fprintf(stderr,
555             "Hooks can only be used with option -L.\n");
556         return (FPART_OPTS_USAGE | FPART_OPTS_NOK | FPART_OPTS_EXIT);
557     }
558 
559     if((options->in_filename == NULL) && (*argcp <= 0)) {
560         /* no file specified, force stdin */
561         char *opt_input = "-";
562         size_t malloc_size = strlen(opt_input) + 1;
563         if_not_malloc(options->in_filename, malloc_size,
564             return (FPART_OPTS_NOK | FPART_OPTS_EXIT);
565         )
566         snprintf(options->in_filename, malloc_size, "%s", opt_input);
567     }
568 
569     return (FPART_OPTS_OK);
570 }
571 
main(int argc,char ** argv)572 int main(int argc, char **argv)
573 {
574     fnum_t totalfiles = 0;
575 
576 /******************
577   Handle options
578  ******************/
579 
580     /* Program options */
581     struct program_options options;
582 
583     /* Set default options */
584     init_options(&options);
585 
586     /* Parse and initialize options */
587     int options_init_res = handle_options(&options, &argc, &argv);
588 
589     if(options_init_res & FPART_OPTS_USAGE)
590         usage();
591     if(options_init_res & FPART_OPTS_VERSION)
592         version();
593     if(options_init_res & FPART_OPTS_EXIT) {
594         uninit_options(&options);
595         exit(options_init_res & FPART_OPTS_NOK ?
596             EXIT_FAILURE : EXIT_SUCCESS);
597     }
598 
599 /**************
600   Handle stdin
601 ***************/
602 
603     /* our main double-linked file list */
604     struct file_entry *head = NULL;
605 
606     if(options.verbose >= OPT_VERBOSE)
607         fprintf(stderr, "Examining filesystem...\n");
608 
609     /* work on each file provided through input file (or stdin) */
610     if(options.in_filename != NULL) {
611         /* handle fd opening */
612         FILE *in_fp = NULL;
613         if((options.in_filename[0] == '-') &&
614             (options.in_filename[1] == '\0')) {
615             /* working from stdin */
616             in_fp = stdin;
617         }
618         else {
619             /* working from a filename */
620             if((in_fp = fopen(options.in_filename, "r")) == NULL) {
621                 fprintf(stderr, "%s: %s\n", options.in_filename,
622                     strerror(errno));
623                 uninit_options(&options);
624                 exit(EXIT_FAILURE);
625             }
626         }
627 
628         /* read fd and do the work */
629         char line[MAX_LINE_LENGTH];
630         char *line_end_p = NULL;
631         bzero(line, MAX_LINE_LENGTH);
632         while(fgets(line, MAX_LINE_LENGTH, in_fp) != NULL) {
633             /* replace '\n' with '\0' */
634             if ((line_end_p = strchr(line, '\n')) != NULL)
635                 *line_end_p = '\0';
636 
637             if(handle_argument(line, &totalfiles, &head, &options) != 0) {
638                 uninit_file_entries(head, &options);
639                 uninit_options(&options);
640                 exit(EXIT_FAILURE);
641             }
642 
643             /* cleanup */
644             bzero(line, MAX_LINE_LENGTH);
645         }
646 
647         /* check for error reading input */
648         if(ferror(in_fp) != 0) {
649             fprintf(stderr, "error reading from input stream\n");
650         }
651 
652         /* cleanup */
653         if(in_fp != NULL)
654             fclose(in_fp);
655     }
656 
657 /******************
658   Handle arguments
659 *******************/
660 
661     /* now, work on each path provided as arguments */
662     int i;
663     for(i = 0 ; i < argc ; i++) {
664         if(handle_argument(argv[i], &totalfiles, &head, &options) != 0) {
665             uninit_file_entries(head, &options);
666             uninit_options(&options);
667             exit(EXIT_FAILURE);
668         }
669     }
670 
671 /****************
672   Display status
673 *****************/
674 
675     /* come back to the first element */
676     rewind_list(head);
677 
678     /* no file found or live mode */
679     if((totalfiles == 0) || (options.live_mode == OPT_LIVEMODE)) {
680         uninit_file_entries(head, &options);
681         /* display status */
682         if(options.verbose >= OPT_VERBOSE)
683             fprintf(stderr, "%ju file(s) found.\n", totalfiles);
684         uninit_options(&options);
685         exit(EXIT_SUCCESS);
686     }
687 
688     /* display status */
689     if(options.verbose >= OPT_VERBOSE) {
690         fprintf(stderr, "%ju file(s) found.\n", totalfiles);
691         fprintf(stderr, "Sorting entries...\n");
692     }
693 
694 /************************************************
695   Sort entries with a fixed number of partitions
696 *************************************************/
697 
698     /* our list of partitions */
699     struct partition *part_head = NULL;
700     pnum_t num_parts = options.num_parts;
701 
702     /* sort files with a fixed size of partitions */
703     if(options.num_parts != DFLT_OPT_NUM_PARTS) {
704         /* create a fixed-size array of pointers to sort */
705         struct file_entry **file_entry_p = NULL;
706 
707         if_not_malloc(file_entry_p, sizeof(struct file_entry *) * totalfiles,
708             uninit_file_entries(head, &options);
709             uninit_options(&options);
710             exit(EXIT_FAILURE);
711         )
712 
713         /* initialize array */
714         init_file_entry_p(file_entry_p, totalfiles, head);
715 
716         /* sort array */
717         qsort(&file_entry_p[0], totalfiles, sizeof(struct file_entry *),
718             &sort_file_entry_p);
719 
720         /* create a double_linked list of partitions
721            which will hold dispatched files */
722         if(add_partitions(&part_head, options.num_parts, &options) != 0) {
723             fprintf(stderr, "%s(): cannot init list of partitions\n",
724                 __func__);
725             uninit_partitions(part_head);
726             free(file_entry_p);
727             uninit_file_entries(head, &options);
728             uninit_options(&options);
729             exit(EXIT_FAILURE);
730         }
731         /* come back to the first element */
732         rewind_list(part_head);
733 
734         /* dispatch files */
735         if(dispatch_file_entry_p_by_size
736             (file_entry_p, totalfiles, part_head, options.num_parts) != 0) {
737             fprintf(stderr, "%s(): unable to dispatch file entries\n",
738                 __func__);
739             uninit_partitions(part_head);
740             free(file_entry_p);
741             uninit_file_entries(head, &options);
742             uninit_options(&options);
743             exit(EXIT_FAILURE);
744         }
745 
746         /* re-dispatch empty files */
747         if(dispatch_empty_file_entries
748             (head, totalfiles, part_head, options.num_parts) != 0) {
749             fprintf(stderr, "%s(): unable to dispatch empty file entries\n",
750                 __func__);
751             uninit_partitions(part_head);
752             free(file_entry_p);
753             uninit_file_entries(head, &options);
754             uninit_options(&options);
755             exit(EXIT_FAILURE);
756         }
757 
758         /* cleanup */
759         free(file_entry_p);
760     }
761 
762 /***************************************************
763   Sort entries with a variable number of partitions
764 ****************************************************/
765 
766     /* sort files with a file number or size limit per-partitions.
767        In this case, partitions are dynamically-created */
768     else {
769         if((num_parts = dispatch_file_entries_by_limits
770             (head, &part_head, options.max_entries, options.max_size,
771             &options)) == 0) {
772             fprintf(stderr, "%s(): unable to dispatch file entries\n",
773                 __func__);
774             uninit_partitions(part_head);
775             uninit_file_entries(head, &options);
776             uninit_options(&options);
777             exit(EXIT_FAILURE);
778         }
779         /* come back to the first element
780            (we may have exited with part_head set to partition 1,
781            after default partition) */
782         rewind_list(part_head);
783     }
784 
785 /***********************
786   Print result and exit
787 ************************/
788 
789     /* print result summary */
790     print_partitions(part_head);
791 
792     if(options.verbose >= OPT_VERBOSE)
793         fprintf(stderr, "Writing output lists...\n");
794 
795     /* print file entries */
796     print_file_entries(head, num_parts, &options);
797 
798     if(options.verbose >= OPT_VERBOSE)
799         fprintf(stderr, "Cleaning up...\n");
800 
801     /* free stuff */
802     uninit_partitions(part_head);
803     uninit_file_entries(head, &options);
804     uninit_options(&options);
805     exit(EXIT_SUCCESS);
806 }
807