1 /* needs to be big, as all enteries have to be in core */
2 #define EX_MAX_R_DATA_INCORE (16 * 1024 * 1024)
3 
4 #include "ex_utils.h"
5 
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <unistd.h>
9 #include <getopt.h>
10 #include <locale.h>
11 
12 #include "opt.h"
13 
14 #include "bag.h"
15 
16 static Bag *names = NULL;
17 
18 static int (*sort_cmp)(const void *, const void *) = bag_cb_sort_key_coll;
19 
20 
ex_dir_sort_process(Vstr_base * s1,Vstr_base * s2,int * parsed_header)21 static int ex_dir_sort_process(Vstr_base *s1, Vstr_base *s2,
22                                int *parsed_header)
23 {
24   size_t scan_pos = 1;
25   size_t scan_len = s2->len;
26 
27   while (scan_len)
28   {
29     size_t pos = 0;
30     size_t len = 0;
31     size_t ns1 = vstr_parse_netstr(s2, scan_pos, scan_len, &pos, &len);
32 
33     if (!ns1)
34     {
35       if (s2->len > EX_MAX_R_DATA_INCORE)
36         errx(EXIT_FAILURE, "input too big");
37       if (len     > EX_MAX_R_DATA_INCORE)
38         errx(EXIT_FAILURE, "bad input");
39 
40       return (FALSE);
41     }
42 
43     if (!*parsed_header)
44     {
45       size_t vpos = 0;
46       size_t vlen = 0;
47       size_t nst  = 0;
48 
49       if (!(nst = vstr_parse_netstr(s2, pos, len, &vpos, &vlen)))
50         errx(EXIT_FAILURE, "bad input");
51       pos += nst; len -= nst;
52       if (!vstr_cmp_cstr_eq(s2, vpos, vlen, "version"))
53         errx(EXIT_FAILURE, "bad input");
54       if (!(nst = vstr_parse_netstr(s2, pos, len, &vpos, &vlen)))
55         errx(EXIT_FAILURE, "bad input");
56       pos += nst; len -= nst;
57       if (!vstr_cmp_cstr_eq(s2, vpos, vlen, "1"))
58         errx(EXIT_FAILURE, "Unsupported version");
59       *parsed_header = TRUE;
60 
61       vstr_add_vstr(s1, s1->len, s2, 1, ns1, 0);
62       vstr_del(s2, 1, ns1);
63       scan_pos = 1;
64       scan_len = s2->len;
65 
66       continue;
67     }
68 
69     while (len)
70     {
71       size_t kpos = 0;
72       size_t klen = 0;
73       size_t vpos = 0;
74       size_t vlen = 0;
75       size_t nst  = 0;
76       char *key = NULL;
77       Vstr_sect_node *val = malloc(sizeof(Vstr_sect_node));
78 
79       if (!val)
80         errno = ENOMEM, err(EXIT_FAILURE, "sort");
81 
82       if (!(nst = vstr_parse_netstr(s2, pos, len, &kpos, &klen)))
83         errx(EXIT_FAILURE, "bad input");
84       pos += nst; len -= nst;
85 
86       if (!(nst = vstr_parse_netstr(s2, pos, len, &vpos, &vlen)))
87         errx(EXIT_FAILURE, "bad input");
88       pos += nst; len -= nst;
89 
90       if (!vstr_cmp_cstr_eq(s2, kpos, klen, "name"))
91         continue; /* only sort by names atm. */
92 
93       if (!(key = vstr_export_cstr_malloc(s2, vpos, vlen)))
94         errno = ENOMEM, err(EXIT_FAILURE, "sort");
95 
96       val->pos = scan_pos;
97       val->len = ns1;
98 
99       if (!(names = bag_add_obj(names, key, val)))
100         errno = ENOMEM, err(EXIT_FAILURE, "sort");
101       break;
102     }
103 
104     scan_len -= ns1;
105     scan_pos += ns1;
106   }
107 
108   return (TRUE);
109 }
110 
ex_dir_sort_read_fd_write_stdout(Vstr_base * s1,Vstr_base * s2,int fd)111 static void ex_dir_sort_read_fd_write_stdout(Vstr_base *s1, Vstr_base *s2,
112                                              int fd)
113 {
114   int parsed_header[1] = {FALSE};
115 
116   while (TRUE)
117   {
118     int io_w_state = IO_OK;
119     int io_r_state = io_get(s2, fd);
120 
121     if (io_r_state == IO_EOF)
122       break;
123 
124     io_w_state = io_put(s1, STDOUT_FILENO);
125 
126     io_limit(io_r_state, fd, io_w_state, STDOUT_FILENO, s1);
127   }
128 
129   ex_dir_sort_process(s1, s2, parsed_header);
130 
131   bag_sort(names, sort_cmp);
132 
133   {
134     Bag_iter iter[1];
135     const Bag_obj *obj = bag_iter_beg(names, iter);
136 
137     while (obj)
138     {
139       const Vstr_sect_node *val = obj->val;
140 
141       vstr_add_vstr(s1, s1->len, s2, val->pos, val->len, 0);
142       obj = bag_iter_nxt(iter);
143     }
144 
145     if (s1->conf->malloc_bad)
146       errno = ENOMEM, err(EXIT_FAILURE, "sort");
147     vstr_del(s2, 1, s2->len);
148   }
149 
150   bag_del_all(names);
151 }
152 
ex_dir_sort_init(Vstr_base * s1)153 static void ex_dir_sort_init(Vstr_base *s1)
154 {
155   if (!vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$') ||
156       !vstr_sc_fmt_add_all(s1->conf))
157     errno = ENOMEM, err(EXIT_FAILURE, "init");
158 
159   if (!(names = bag_make(16, bag_cb_free_malloc, bag_cb_free_malloc)))
160     errno = ENOMEM, err(EXIT_FAILURE, "init");
161   names->can_resize = TRUE;
162 }
163 
usage(const char * program_name,int ret,const char * prefix)164 static void usage(const char *program_name, int ret, const char *prefix)
165 {
166   Vstr_base *out = vstr_make_base(NULL);
167 
168   if (!out)
169     errno = ENOMEM, err(EXIT_FAILURE, "usage");
170 
171   vstr_add_fmt(out, 0, "%s\n"
172          " Usage: %s [-hV] [FILES]\n"
173          "   or: %s OPTION\n"
174          " --help -h         - Print this message.\n"
175          " --sort            - Use cmp, case, version or collating sorting.\n"
176          " --version -V      - Print the version string.\n",
177          prefix, program_name, program_name);
178 
179   if (io_put_all(out, ret ? STDERR_FILENO : STDOUT_FILENO) == IO_FAIL)
180     err(EXIT_FAILURE, "write");
181 
182   exit (ret);
183 }
184 
ex_dir_sort_cmd_line(int * passed_argc,char ** passed_argv[])185 static void ex_dir_sort_cmd_line(int *passed_argc, char **passed_argv[])
186 {
187   int    argc = *passed_argc;
188   char **argv = *passed_argv;
189   char optchar = 0;
190   const char *program_name = NULL;
191   struct option long_options[] =
192   { /* allow sorting by size etc. */
193    {"help", no_argument, NULL, 'h'},
194    {"sort", required_argument, NULL, 1},
195    {"version", no_argument, NULL, 'V'},
196    {NULL, 0, NULL, 0}
197   };
198   Vstr_base *out = vstr_make_base(NULL);
199 
200   if (!out)
201     errno = ENOMEM, err(EXIT_FAILURE, "command line");
202 
203   program_name = opt_program_name(argv[0], "jdir_sort");
204 
205   while ((optchar = getopt_long(argc, argv, "hV",
206                                 long_options, NULL)) != -1)
207   {
208     switch (optchar)
209     {
210       case '?': usage(program_name, EXIT_FAILURE, "");
211       case 'h': usage(program_name, EXIT_SUCCESS, "");
212 
213       case 'V':
214         vstr_add_fmt(out, 0,"\
215 %s version 1.0.0, compiled on %s.\n\
216 Written by James Antill\n\
217 \n\
218 Uses Vstr string library.\n\
219 ",
220                program_name, __DATE__);
221 
222         if (io_put_all(out, STDOUT_FILENO) == IO_FAIL)
223           err(EXIT_FAILURE, "write");
224 
225         exit (EXIT_SUCCESS);
226 
227       case 1:
228         if (0) {}
229         else if (!strcmp(optarg, "cmp"))       sort_cmp = bag_cb_sort_key_cmp;
230         else if (!strcmp(optarg, "case"))      sort_cmp = bag_cb_sort_key_case;
231         else if (!strcmp(optarg, "version"))   sort_cmp = bag_cb_sort_key_vers;
232         else if (!strcmp(optarg, "collating")) sort_cmp = bag_cb_sort_key_coll;
233         else
234           usage(program_name, EXIT_FAILURE, "");
235         break;
236 
237       default:
238         abort();
239     }
240   }
241   vstr_free_base(out); out = NULL;
242 
243   argc -= optind;
244   argv += optind;
245 
246   *passed_argc = argc;
247   *passed_argv = argv;
248 }
249 
main(int argc,char * argv[])250 int main(int argc, char *argv[])
251 {
252   Vstr_base *s1 = NULL;
253   Vstr_base *s2 = ex_init(&s1); /* init the library etc. */
254   int count = 0;
255 
256   setlocale(LC_ALL, "");
257 
258   ex_dir_sort_init(s1);
259 
260   ex_dir_sort_cmd_line(&argc, &argv);
261 
262   /* if no arguments are given just do stdin to stdout */
263   if (count >= argc)
264   {
265     io_fd_set_o_nonblock(STDIN_FILENO);
266     ex_dir_sort_read_fd_write_stdout(s1, s2, STDIN_FILENO);
267   }
268 
269   /* loop through all arguments, open the dir specified
270    * and do the read/write loop */
271   while (count < argc)
272   {
273     int fd = io_open(argv[count]);
274 
275     ex_dir_sort_read_fd_write_stdout(s1, s2, fd);
276 
277     if (close(fd) == -1)
278       warn("close(%s)", argv[count]);
279 
280     ++count;
281   }
282 
283   bag_free(names);
284 
285   /* output all remaining data */
286   io_put_all(s1, STDOUT_FILENO);
287 
288   exit (ex_exit(s1, s2));
289 }
290