1 /*
2 * Blktrace record utility - Convert binary trace data into bunches of IOs
3 *
4 * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 static char build_date[] = __DATE__ " at "__TIME__;
22
23 #include <assert.h>
24 #include <fcntl.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/param.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <dirent.h>
33 #include <stdarg.h>
34
35 #if !defined(_GNU_SOURCE)
36 # define _GNU_SOURCE
37 #endif
38 #include <getopt.h>
39
40 #include "list.h"
41 #include "btrecord.h"
42 #include "blktrace.h"
43
44 /*
45 * Per input file information
46 *
47 * @head: Used to link up on input_files
48 * @devnm: Device name portion of this input file
49 * @file_name: Fully qualified name for this input file
50 * @cpu: CPU that this file was collected on
51 * @ifd: Input file descriptor (when opened)
52 * @tpkts: Total number of packets processed.
53 */
54 struct ifile_info {
55 struct list_head head;
56 char *devnm, *file_name;
57 int cpu, ifd;
58 __u64 tpkts, genesis;
59 };
60
61 /*
62 * Per IO trace information
63 *
64 * @time: Time stamp when trace was emitted
65 * @sector: IO sector identifier
66 * @bytes: Number of bytes transferred
67 * @rw: Read (1) or write (0)
68 */
69 struct io_spec {
70 __u64 time;
71 __u64 sector;
72 __u32 bytes;
73 int rw;
74 };
75
76 /*
77 * Per output file information
78 *
79 * @ofp: Output file
80 * @vfp: Verbose output file
81 * @file_name: Fully qualified name for this file
82 * @vfn: Fully qualified name for this file
83 * @cur: Current IO bunch being collected
84 * @iip: Input file this is associated with
85 * @start_time: Start time of th ecurrent bunch
86 * @last_time: Time of last packet put in
87 * @bunches: Number of bunches processed
88 * @pkts: Number of packets stored in bunches
89 */
90 struct io_stream {
91 FILE *ofp, *vfp;
92 char *file_name, *vfn;
93 struct io_bunch *cur;
94 struct ifile_info *iip;
95 __u64 start_time, last_time, bunches, pkts;
96 };
97
98 int data_is_native; // Indicates whether to swap
99 static LIST_HEAD(input_files); // List of all input files
100 static char *idir = "."; // Input directory base
101 static char *odir = "."; // Output directory base
102 static char *obase = "replay"; // Output file base
103 static __u64 max_bunch_tm = (10 * 1000 * 1000); // 10 milliseconds
104 static __u64 max_pkts_per_bunch = 8; // Default # of pkts per bunch
105 static int verbose = 0; // Boolean: output stats
106 static int find_traces = 0; // Boolean: Find traces in dir
107
108 static char usage_str[] = \
109 "\n" \
110 "\t[ -d <dir> : --input-directory=<dir> ] Default: .\n" \
111 "\t[ -D <dir> : --output-directory=<dir>] Default: .\n" \
112 "\t[ -F : --find-traces ] Default: Off\n" \
113 "\t[ -h : --help ] Default: Off\n" \
114 "\t[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec\n" \
115 "\t[ -M <pkts> : --max-pkts=<pkts> ] Default: 8\n" \
116 "\t[ -o <base> : --output-base=<base> ] Default: replay\n" \
117 "\t[ -v : --verbose ] Default: Off\n" \
118 "\t[ -V : --version ] Default: Off\n" \
119 "\t<dev>... Default: None\n" \
120 "\n";
121
122 #define S_OPTS "d:D:Fhm:M:o:vV"
123 static struct option l_opts[] = {
124 {
125 .name = "input-directory",
126 .has_arg = required_argument,
127 .flag = NULL,
128 .val = 'd'
129 },
130 {
131 .name = "output-directory",
132 .has_arg = required_argument,
133 .flag = NULL,
134 .val = 'D'
135 },
136 {
137 .name = "find-traces",
138 .has_arg = no_argument,
139 .flag = NULL,
140 .val = 'F'
141 },
142 {
143 .name = "help",
144 .has_arg = no_argument,
145 .flag = NULL,
146 .val = 'h'
147 },
148 {
149 .name = "max-bunch-time",
150 .has_arg = required_argument,
151 .flag = NULL,
152 .val = 'm'
153 },
154 {
155 .name = "max-pkts",
156 .has_arg = required_argument,
157 .flag = NULL,
158 .val = 'M'
159 },
160 {
161 .name = "output-base",
162 .has_arg = required_argument,
163 .flag = NULL,
164 .val = 'o'
165 },
166 {
167 .name = "verbose",
168 .has_arg = no_argument,
169 .flag = NULL,
170 .val = 'v'
171 },
172 {
173 .name = "version",
174 .has_arg = no_argument,
175 .flag = NULL,
176 .val = 'V'
177 },
178 {
179 .name = NULL
180 }
181 };
182
183 #define ERR_ARGS 1
184 #define ERR_SYSCALL 2
fatal(const char * errstring,const int exitval,const char * fmt,...)185 static inline void fatal(const char *errstring, const int exitval,
186 const char *fmt, ...)
187 {
188 va_list ap;
189
190 if (errstring)
191 perror(errstring);
192
193 va_start(ap, fmt);
194 vfprintf(stderr, fmt, ap);
195 va_end(ap);
196
197 exit(exitval);
198 /*NOTREACHED*/
199 }
200
201 /**
202 * match - Return true if this trace is a proper QUEUE transaction
203 * @action: Action field from trace
204 */
match(__u32 action)205 static inline int match(__u32 action)
206 {
207 return ((action & 0xffff) == __BLK_TA_QUEUE) &&
208 (action & BLK_TC_ACT(BLK_TC_QUEUE));
209 }
210
211 /**
212 * usage - Display usage string and version
213 */
usage(void)214 static void usage(void)
215 {
216 fprintf(stderr, "Usage: btrecord -- version %s\n%s",
217 my_btversion, usage_str);
218 }
219
220 /**
221 * write_file_hdr - Seek to and write btrecord file header
222 * @stream: Output file information
223 * @hdr: Header to write
224 */
write_file_hdr(struct io_stream * stream,struct io_file_hdr * hdr)225 static void write_file_hdr(struct io_stream *stream, struct io_file_hdr *hdr)
226 {
227 hdr->version = mk_btversion(btver_mjr, btver_mnr, btver_sub);
228
229 if (verbose) {
230 fprintf(stderr, "\t%s: %llx %llx %llx %llx\n",
231 stream->file_name,
232 (long long unsigned)hdr->version,
233 (long long unsigned)hdr->genesis,
234 (long long unsigned)hdr->nbunches,
235 (long long unsigned)hdr->total_pkts);
236 }
237
238 fseek(stream->ofp, 0, SEEK_SET);
239 if (fwrite(hdr, sizeof(*hdr), 1, stream->ofp) != 1) {
240 fatal(stream->file_name, ERR_SYSCALL, "Hdr write failed\n");
241 /*NOTREACHED*/
242 }
243 }
244
245 /**
246 * io_bunch_create - Allocate & initialize an io_bunch
247 * @io_stream: IO stream being added to
248 * @pre_stall: Amount of time that this bunch should be delayed by
249 * @start_time: Records current start
250 */
io_bunch_create(struct io_stream * stream,__u64 start_time)251 static inline void io_bunch_create(struct io_stream *stream, __u64 start_time)
252 {
253 struct io_bunch *cur = malloc(sizeof(*cur));
254
255 memset(cur, 0, sizeof(*cur));
256
257 cur->hdr.npkts = 0;
258 cur->hdr.time_stamp = stream->start_time = start_time;
259
260 stream->cur = cur;
261 }
262
263 /**
264 * io_bunch_add - Add an IO to the current bunch of IOs
265 * @stream: Per-output file stream information
266 * @spec: IO trace specification
267 *
268 * Returns update bunch information
269 */
io_bunch_add(struct io_stream * stream,struct io_spec * spec)270 static void io_bunch_add(struct io_stream *stream, struct io_spec *spec)
271 {
272 struct io_bunch *cur = stream->cur;
273 struct io_pkt iop = {
274 .sector = spec->sector,
275 .nbytes = spec->bytes,
276 .rw = spec->rw
277 };
278
279 assert(cur != NULL);
280 assert(cur->hdr.npkts < BT_MAX_PKTS);
281 assert(stream->last_time == 0 || stream->last_time <= spec->time);
282
283 cur->pkts[cur->hdr.npkts++] = iop; // Struct copy
284 stream->last_time = spec->time;
285 }
286
287 /**
288 * rem_input_file - Release resources associated with an input file
289 * @iip: Per-input file information
290 */
rem_input_file(struct ifile_info * iip)291 static void rem_input_file(struct ifile_info *iip)
292 {
293 list_del(&iip->head);
294
295 close(iip->ifd);
296 free(iip->file_name);
297 free(iip->devnm);
298 free(iip);
299 }
300
301 /**
302 * __add_input_file - Allocate and initialize per-input file structure
303 * @cpu: CPU for this file
304 * @devnm: Device name for this file
305 * @file_name: Fully qualifed input file name
306 */
__add_input_file(int cpu,char * devnm,char * file_name)307 static void __add_input_file(int cpu, char *devnm, char *file_name)
308 {
309 struct ifile_info *iip = malloc(sizeof(*iip));
310
311 iip->cpu = cpu;
312 iip->tpkts = 0;
313 iip->genesis = 0;
314 iip->devnm = strdup(devnm);
315 iip->file_name = strdup(file_name);
316 iip->ifd = open(file_name, O_RDONLY);
317 if (iip->ifd < 0) {
318 fatal(file_name, ERR_ARGS, "Unable to open\n");
319 /*NOTREACHED*/
320 }
321
322 list_add_tail(&iip->head, &input_files);
323 }
324
325 /**
326 * add_input_file - Set up the input file name
327 * @devnm: Device name to use
328 */
add_input_file(char * devnm)329 static void add_input_file(char *devnm)
330 {
331 struct list_head *p;
332 int cpu, found = 0;
333
334 __list_for_each(p, &input_files) {
335 struct ifile_info *iip = list_entry(p, struct ifile_info, head);
336 if (strcmp(iip->devnm, devnm) == 0)
337 return;
338 }
339
340 for (cpu = 0; ; cpu++) {
341 char full_name[MAXPATHLEN];
342
343 sprintf(full_name, "%s/%s.blktrace.%d", idir, devnm, cpu);
344 if (access(full_name, R_OK) != 0)
345 break;
346
347 __add_input_file(cpu, devnm, full_name);
348 found++;
349 }
350
351 if (!found) {
352 fatal(NULL, ERR_ARGS, "No traces found for %s\n", devnm);
353 /*NOTREACHED*/
354 }
355 }
356
find_input_files(char * idir)357 static void find_input_files(char *idir)
358 {
359 struct dirent *ent;
360 DIR *dir = opendir(idir);
361
362 if (dir == NULL) {
363 fatal(idir, ERR_ARGS, "Unable to open %s\n", idir);
364 /*NOTREACHED*/
365 }
366
367 while ((ent = readdir(dir)) != NULL) {
368 char *p, *dsf = malloc(256);
369
370 if (strstr(ent->d_name, ".blktrace.") == NULL)
371 continue;
372
373 dsf = strdup(ent->d_name);
374 p = index(dsf, '.');
375 assert(p != NULL);
376 *p = '\0';
377 add_input_file(dsf);
378 free(dsf);
379 }
380
381 closedir(dir);
382 }
383
384 /**
385 * handle_args - Parse passed in argument list
386 * @argc: Number of arguments in argv
387 * @argv: Arguments passed in
388 *
389 * Does rudimentary parameter verification as well.
390 */
handle_args(int argc,char * argv[])391 void handle_args(int argc, char *argv[])
392 {
393 int c;
394
395 while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
396 switch (c) {
397 case 'd':
398 idir = optarg;
399 if (access(idir, R_OK | X_OK) != 0) {
400 fatal(idir, ERR_ARGS,
401 "Invalid input directory specified\n");
402 /*NOTREACHED*/
403 }
404 break;
405
406 case 'D':
407 odir = optarg;
408 if (access(odir, R_OK | X_OK) != 0) {
409 fatal(odir, ERR_ARGS,
410 "Invalid output directory specified\n");
411 /*NOTREACHED*/
412 }
413 break;
414
415 case 'F':
416 find_traces = 1;
417 break;
418
419 case 'h':
420 usage();
421 exit(0);
422 /*NOTREACHED*/
423
424 case 'm':
425 max_bunch_tm = (__u64)atoll(optarg);
426 if (max_bunch_tm < 1) {
427 fprintf(stderr, "Invalid bunch time %llu\n",
428 (unsigned long long)max_bunch_tm);
429 exit(ERR_ARGS);
430 /*NOTREACHED*/
431 }
432 break;
433
434 case 'M':
435 max_pkts_per_bunch = (__u64)atoll(optarg);
436 if (!((1 <= max_pkts_per_bunch) &&
437 (max_pkts_per_bunch < 513))) {
438 fprintf(stderr, "Invalid max pkts %llu\n",
439 (unsigned long long)max_pkts_per_bunch);
440 exit(ERR_ARGS);
441 /*NOTREACHED*/
442 }
443 break;
444
445 case 'o':
446 obase = optarg;
447 break;
448
449 case 'V':
450 fprintf(stderr, "btrecord -- version %s\n",
451 my_btversion);
452 fprintf(stderr, " Built on %s\n", build_date);
453 exit(0);
454 /*NOTREACHED*/
455
456 case 'v':
457 verbose++;
458 break;
459
460 default:
461 usage();
462 fatal(NULL, ERR_ARGS, "Invalid command line\n");
463 /*NOTREACHED*/
464 }
465 }
466
467 while (optind < argc)
468 add_input_file(argv[optind++]);
469
470 if (find_traces)
471 find_input_files(idir);
472
473 if (list_len(&input_files) == 0) {
474 fatal(NULL, ERR_ARGS, "Missing required input file name(s)\n");
475 /*NOTREACHED*/
476 }
477 }
478
479 /**
480 * next_io - Retrieve next Q trace from input stream
481 * @iip: Per-input file information
482 * @spec: IO specifier for trace
483 *
484 * Returns 0 on end of file, 1 if valid data returned.
485 */
next_io(struct ifile_info * iip,struct io_spec * spec)486 static int next_io(struct ifile_info *iip, struct io_spec *spec)
487 {
488 ssize_t ret;
489 __u32 action;
490 __u16 pdu_len;
491 struct blk_io_trace t;
492
493 again:
494 ret = read(iip->ifd, &t, sizeof(t));
495 if (ret < 0) {
496 fatal(iip->file_name, ERR_SYSCALL, "Read failed\n");
497 /*NOTREACHED*/
498 }
499 else if (ret == 0)
500 return 0;
501 else if (ret < (ssize_t)sizeof(t)) {
502 fprintf(stderr, "WARNING: Short read on %s (%d)\n",
503 iip->file_name, (int)ret);
504 return 0;
505 }
506
507 if (data_is_native == -1)
508 check_data_endianness(t.magic);
509
510 assert(data_is_native >= 0);
511 if (data_is_native) {
512 spec->time = t.time;
513 spec->sector = t.sector;
514 spec->bytes = t.bytes;
515 action = t.action;
516 pdu_len = t.pdu_len;
517 }
518 else {
519 spec->time = be64_to_cpu(t.time);
520 spec->sector = be64_to_cpu(t.sector);
521 spec->bytes = be32_to_cpu(t.bytes);
522 action = be32_to_cpu(t.action);
523 pdu_len = be16_to_cpu(t.pdu_len);
524 }
525
526
527 if (pdu_len) {
528 char buf[pdu_len];
529
530 ret = read(iip->ifd, buf, pdu_len);
531 if (ret < 0) {
532 fatal(iip->file_name, ERR_SYSCALL, "Read PDU failed\n");
533 /*NOTREACHED*/
534 }
535 else if (ret < (ssize_t)pdu_len) {
536 fprintf(stderr, "WARNING: Short PDU read on %s (%d)\n",
537 iip->file_name, (int)ret);
538 return 0;
539 }
540 }
541
542 iip->tpkts++;
543 if (!match(action))
544 goto again;
545
546 spec->rw = (action & BLK_TC_ACT(BLK_TC_READ)) ? 1 : 0;
547 if (verbose > 1)
548 fprintf(stderr, "%2d: %10llu+%10llu (%d) @ %10llx\n",
549 iip->cpu, (long long unsigned)spec->sector,
550 (long long unsigned)spec->bytes / 512LLU,
551 spec->rw, (long long unsigned)spec->time);
552
553 if (iip->genesis == 0) {
554 iip->genesis = spec->time;
555 if (verbose > 1)
556 fprintf(stderr, "\tSetting new genesis: %llx(%d)\n",
557 (long long unsigned)iip->genesis, iip->cpu);
558 }
559 else if (iip->genesis > spec->time)
560 fatal(NULL, ERR_SYSCALL,
561 "Time inversion? %llu ... %llu\n",
562 (long long unsigned )iip->genesis,
563 (long long unsigned )spec->time);
564
565 return 1;
566 }
567
568 /**
569 * bunch_output_hdr - Output bunch header
570 */
bunch_output_hdr(struct io_stream * stream)571 static inline void bunch_output_hdr(struct io_stream *stream)
572 {
573 struct io_bunch_hdr *hdrp = &stream->cur->hdr;
574
575 assert(0 < hdrp->npkts && hdrp->npkts <= BT_MAX_PKTS);
576 if (fwrite(hdrp, sizeof(struct io_bunch_hdr), 1, stream->ofp) != 1) {
577 fatal(stream->file_name, ERR_SYSCALL, "fwrite(hdr) failed\n");
578 /*NOTREACHED*/
579 }
580
581 if (verbose) {
582 __u64 off = hdrp->time_stamp - stream->iip->genesis;
583
584 assert(stream->vfp);
585 fprintf(stream->vfp, "------------------\n");
586 fprintf(stream->vfp, "%4llu.%09llu %3llu\n",
587 (unsigned long long)off / (1000 * 1000 * 1000),
588 (unsigned long long)off % (1000 * 1000 * 1000),
589 (unsigned long long)hdrp->npkts);
590 fprintf(stream->vfp, "------------------\n");
591 }
592 }
593
594 /**
595 * bunch_output_pkt - Output IO packets
596 */
bunch_output_pkts(struct io_stream * stream)597 static inline void bunch_output_pkts(struct io_stream *stream)
598 {
599 struct io_pkt *p = stream->cur->pkts;
600 size_t npkts = stream->cur->hdr.npkts;
601
602 assert(0 < npkts && npkts <= BT_MAX_PKTS);
603 if (fwrite(p, sizeof(struct io_pkt), npkts, stream->ofp) != npkts) {
604 fatal(stream->file_name, ERR_SYSCALL, "fwrite(pkts) failed\n");
605 /*NOTREACHED*/
606 }
607
608 if (verbose) {
609 size_t i;
610
611 assert(stream->vfp);
612 for (i = 0; i < npkts; i++, p++)
613 fprintf(stream->vfp, "\t%1d %10llu\t%10llu\n",
614 p->rw,
615 (unsigned long long)p->sector,
616 (unsigned long long)p->nbytes / 512);
617 }
618 }
619
620 /**
621 * stream_flush - Flush current bunch of IOs out to the output stream
622 * @stream: Per-output file stream information
623 */
stream_flush(struct io_stream * stream)624 static void stream_flush(struct io_stream *stream)
625 {
626 struct io_bunch *cur = stream->cur;
627
628 if (cur) {
629 if (cur->hdr.npkts) {
630 assert(cur->hdr.npkts <= BT_MAX_PKTS);
631 bunch_output_hdr(stream);
632 bunch_output_pkts(stream);
633
634 stream->bunches++;
635 stream->pkts += cur->hdr.npkts;
636 }
637 free(cur);
638 }
639 }
640
641 /**
642 * bunch_done - Returns true if current bunch is either full, or next IO is late
643 * @stream: Output stream information
644 * @spec: IO trace specification
645 */
bunch_done(struct io_stream * stream,struct io_spec * spec)646 static inline int bunch_done(struct io_stream *stream, struct io_spec *spec)
647 {
648 if (stream->cur->hdr.npkts >= max_pkts_per_bunch)
649 return 1;
650
651 if ((spec->time - stream->start_time) > max_bunch_tm)
652 return 1;
653
654 return 0;
655 }
656
657 /**
658 * stream_add_io - Add an IO trace to the current stream
659 * @stream: Output stream information
660 * @spec: IO trace specification
661 */
stream_add_io(struct io_stream * stream,struct io_spec * spec)662 static void stream_add_io(struct io_stream *stream, struct io_spec *spec)
663 {
664
665 if (stream->cur == NULL)
666 io_bunch_create(stream, spec->time);
667 else if (bunch_done(stream, spec)) {
668 stream_flush(stream);
669 io_bunch_create(stream, spec->time);
670 }
671
672 io_bunch_add(stream, spec);
673 }
674
675 /**
676 * stream_open - Open output stream for specified input stream
677 * @iip: Per-input file information
678 */
stream_open(struct ifile_info * iip)679 static struct io_stream *stream_open(struct ifile_info *iip)
680 {
681 char ofile_name[MAXPATHLEN];
682 struct io_stream *stream = malloc(sizeof(*stream));
683 struct io_file_hdr io_file_hdr = {
684 .genesis = 0,
685 .nbunches = 0,
686 .total_pkts = 0
687 };
688
689 memset(stream, 0, sizeof(*stream));
690
691 sprintf(ofile_name, "%s/%s.%s.%d", odir, iip->devnm, obase, iip->cpu);
692 stream->ofp = fopen(ofile_name, "w");
693 if (!stream->ofp) {
694 fatal(ofile_name, ERR_SYSCALL, "Open failed\n");
695 /*NOTREACHED*/
696 }
697
698 stream->iip = iip;
699 stream->cur = NULL;
700 stream->bunches = stream->pkts = 0;
701 stream->last_time = 0;
702 stream->file_name = strdup(ofile_name);
703
704 write_file_hdr(stream, &io_file_hdr);
705
706 if (verbose) {
707 char vfile_name[MAXPATHLEN];
708
709 sprintf(vfile_name, "%s/%s.%s.%d.rec", odir, iip->devnm,
710 obase, iip->cpu);
711 stream->vfp = fopen(vfile_name, "w");
712 if (!stream->vfp) {
713 fatal(vfile_name, ERR_SYSCALL, "Open failed\n");
714 /*NOTREACHED*/
715 }
716
717 stream->vfn = strdup(vfile_name);
718 }
719
720 data_is_native = -1;
721 return stream;
722 }
723
724 /**
725 * stream_close - Release resources associated with an output stream
726 * @stream: Stream to release
727 */
stream_close(struct io_stream * stream)728 static void stream_close(struct io_stream *stream)
729 {
730 struct io_file_hdr io_file_hdr = {
731 .genesis = stream->iip->genesis,
732 .nbunches = stream->bunches,
733 .total_pkts = stream->pkts
734 };
735
736 stream_flush(stream);
737 write_file_hdr(stream, &io_file_hdr);
738 fclose(stream->ofp);
739
740 if (verbose && stream->bunches) {
741 fprintf(stderr,
742 "%s:%d: %llu pkts (tot), %llu pkts (replay), "
743 "%llu bunches, %.1lf pkts/bunch\n",
744 stream->iip->devnm, stream->iip->cpu,
745 (unsigned long long)stream->iip->tpkts,
746 (unsigned long long)stream->pkts,
747 (unsigned long long)stream->bunches,
748 (double)(stream->pkts) / (double)(stream->bunches));
749
750 fclose(stream->vfp);
751 free(stream->vfn);
752 }
753
754 free(stream->file_name);
755 free(stream);
756 }
757
758 /**
759 * process - Process one input file to an output file
760 * @iip: Per-input file information
761 */
process(struct ifile_info * iip)762 static void process(struct ifile_info *iip)
763 {
764 struct io_spec spec;
765 struct io_stream *stream;
766
767 stream = stream_open(iip);
768 while (next_io(iip, &spec))
769 stream_add_io(stream, &spec);
770 stream_close(stream);
771
772 rem_input_file(iip);
773 }
774
775 /**
776 * main -
777 * @argc: Number of arguments
778 * @argv: Array of arguments
779 */
main(int argc,char * argv[])780 int main(int argc, char *argv[])
781 {
782 struct list_head *p, *q;
783
784 handle_args(argc, argv);
785 list_for_each_safe(p, q, &input_files)
786 process(list_entry(p, struct ifile_info, head));
787
788 return 0;
789 }
790