]> granicus.if.org Git - sysstat/blob - tapestat.c
Merge pull request from GHSA-q8r6-g56f-9w7x
[sysstat] / tapestat.c
1 /*
2  * tapestat: report tape statistics
3  * (C) 2015 Hewlett-Packard Development Company, L.P.
4  *
5  * Initial revision by Shane M. SEYMOUR (shane.seymour <at> hpe.com)
6  * Modified for sysstat by Sebastien GODARD (sysstat <at> orange.fr)
7  *
8  ***************************************************************************
9  * This program is free software; you can redistribute it and/or modify it *
10  * under the terms of the GNU General Public License as published  by  the *
11  * Free Software Foundation; either version 2 of the License, or (at  your *
12  * option) any later version.                                              *
13  *                                                                         *
14  * This program is distributed in the hope that it  will  be  useful,  but *
15  * WITHOUT ANY WARRANTY; without the implied warranty  of  MERCHANTABILITY *
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17  * for more details.                                                       *
18  *                                                                         *
19  * You should have received a copy of the GNU General Public License along *
20  * with this program; if not, write to the Free Software Foundation, Inc., *
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA              *
22  ***************************************************************************
23  */
24
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <dirent.h>
31 #define __DO_NOT_DEFINE_COMPILE
32 #include <regex.h>
33 #include <inttypes.h>
34 #include <stdint.h>
35 #include <signal.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <sys/utsname.h>
39
40 #ifdef HAVE_SYS_PARAM_H
41 #include <sys/param.h>
42 #undef HZ /* sys/param.h defines HZ but needed for MAXPATHLEN */
43 #endif
44 #ifndef MAXPATHLEN
45 #define MAXPATHLEN 256
46 #endif
47
48 #include "version.h"
49 #include "tapestat.h"
50 #include "rd_stats.h"
51 #include "count.h"
52
53 #ifdef USE_NLS
54 #include <locale.h>
55 #include <libintl.h>
56 #define _(string) gettext(string)
57 #else
58 #define _(string) (string)
59 #endif
60
61 #ifdef USE_SCCSID
62 #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
63 char *sccsid(void) { return (SCCSID); }
64 #endif
65
66 #ifdef TEST
67 void int_handler(int n) { return; }
68 #endif
69
70 int cpu_nr = 0;         /* Nb of processors on the machine */
71 int flags = 0;          /* Flag for common options and system state */
72
73 long interval = 0;
74 char timestamp[TIMESTAMP_LEN];
75
76 struct sigaction alrm_act;
77
78 /* Number of decimal places */
79 int dplaces_nr = -1;
80
81 /*
82  * For tape stats - it would be extremely rare for there to be a very large
83  * number of tape drives attached to a system. I wouldn't expect to see more
84  * than 20-30 in a very large configuration and discontinguous ones should
85  * be even more rare. Because of this we keep the old and new data in a
86  * simple data structure with the tape index being the number after the tape
87  * drive, st0 at index 0, etc.
88  */
89 int max_tape_drives = 0;
90 struct tape_stats *tape_new_stats = { NULL };
91 struct tape_stats *tape_old_stats = { NULL };
92 regex_t tape_reg;
93
94 /*
95  ***************************************************************************
96  * Print usage and exit.
97  *
98  * IN:
99  * @progname    Name of sysstat command.
100  ***************************************************************************
101  */
102 void usage(char *progname)
103 {
104         fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
105                 progname);
106         fprintf(stderr, _("Options are:\n"
107                           "[ --human ] [ -k | -m ] [ -t ] [ -V ] [ -y ] [ -z ]\n"));
108         exit(1);
109 }
110
111 /*
112  ***************************************************************************
113  * SIGALRM signal handler. No need to reset the handler here.
114  *
115  * IN:
116  * @sig Signal number.
117  ***************************************************************************
118  */
119 void alarm_handler(int sig)
120 {
121         alarm(interval);
122 }
123
124 /*
125  ***************************************************************************
126  * Initialization.
127  ***************************************************************************
128  */
129 void tape_initialise(void)
130 {
131         /* How many processors on this machine? */
132         cpu_nr = get_cpu_nr(~0, FALSE);
133
134         /* Compile regular expression for tape names */
135         if (regcomp(&tape_reg, "^st[0-9]+$", REG_EXTENDED) != 0) {
136                 exit(1);
137         }
138 }
139
140 /*
141  ***************************************************************************
142  * Free structures.
143  ***************************************************************************
144  */
145 void tape_uninitialise(void)
146 {
147         regfree(&tape_reg);
148         if (tape_old_stats != NULL) {
149                 free(tape_old_stats);
150         }
151         if (tape_new_stats != NULL) {
152                 free(tape_new_stats);
153         }
154 }
155
156 /*
157  ***************************************************************************
158  * Get maximum number of tapes in the system.
159  *
160  * RETURNS:
161  * Number of tapes found.
162  ***************************************************************************
163  */
164 int get_max_tape_drives(void)
165 {
166         DIR *dir;
167         struct dirent *entry;
168         int new_max_tape_drives, tmp, num_stats_dir = 0;
169         regmatch_t match;
170         char stats_dir[MAXPATHLEN + 1];
171         struct stat stat_buf;
172
173         new_max_tape_drives = max_tape_drives;
174
175         /* Open sysfs tree */
176         dir = opendir(SYSFS_CLASS_TAPE_DIR);
177         if (dir == NULL)
178                 return 0;
179
180         while ((entry = readdir(dir)) != NULL) {
181
182                 if (regexec(&tape_reg, &entry->d_name[0], 1, &match, 0) == 0) {
183                         /* d_name[2] to skip the st at the front */
184                         tmp = atoi(&entry->d_name[2]) + 1;
185                         if (tmp > new_max_tape_drives) {
186                                 new_max_tape_drives = tmp;
187                         }
188                 }
189                 snprintf(stats_dir, MAXPATHLEN, "%s/%s/%s",
190                         SYSFS_CLASS_TAPE_DIR, &entry->d_name[0], "stats");
191                 if (stat(stats_dir, &stat_buf) == 0) {
192                         if (S_ISDIR(stat_buf.st_mode)) {
193                                 num_stats_dir++;
194                         }
195                 }
196         }
197         closedir(dir);
198
199         /* If there are no stats directories make the new number of tape drives 0 */
200         if (num_stats_dir == 0) {
201                 new_max_tape_drives = 0;
202         }
203
204         return new_max_tape_drives;
205 }
206
207 /*
208  ***************************************************************************
209  * Check if new tapes have been added and reallocate structures accordingly.
210  ***************************************************************************
211  */
212 void tape_check_tapes_and_realloc(void)
213 {
214         int new_max_tape_drives, i;
215
216         /* Count again number of tapes */
217         new_max_tape_drives = get_max_tape_drives();
218
219         if (new_max_tape_drives > max_tape_drives && new_max_tape_drives > 0) {
220                 /* New tapes found: Realloc structures */
221                 struct tape_stats *tape_old_stats_t = (struct tape_stats *)
222                         realloc(tape_old_stats, sizeof(struct tape_stats) * new_max_tape_drives);
223                 struct tape_stats *tape_new_stats_t = (struct tape_stats *)
224                         realloc(tape_new_stats, sizeof(struct tape_stats) * new_max_tape_drives);
225                 if ((tape_old_stats_t == NULL) || (tape_new_stats_t == NULL)) {
226                         if (tape_old_stats_t != NULL) {
227                                 free(tape_old_stats_t);
228                                 tape_old_stats_t = NULL;
229                         } else {
230                                 free(tape_old_stats);
231                                 tape_old_stats = NULL;
232                         }
233                         if (tape_new_stats_t != NULL) {
234                                 free(tape_new_stats_t);
235                                 tape_new_stats_t = NULL;
236                         } else {
237                                 free(tape_new_stats);
238                                 tape_new_stats = NULL;
239                         }
240
241                         perror("realloc");
242                         exit(4);
243                 }
244
245                 tape_old_stats = tape_old_stats_t;
246                 tape_new_stats = tape_new_stats_t;
247
248                 for (i = max_tape_drives; i < new_max_tape_drives; i++) {
249                         tape_old_stats[i].valid = TAPE_STATS_INVALID;
250                         tape_new_stats[i].valid = TAPE_STATS_INVALID;
251                 }
252                 max_tape_drives = new_max_tape_drives;
253         }
254 }
255
256 /*
257  ***************************************************************************
258  * Collect initial statistics for all existing tapes in the system.
259  * This function should be called only once.
260  ***************************************************************************
261  */
262 void tape_gather_initial_stats(void)
263 {
264         int new_max_tape_drives, i;
265         FILE *fp;
266         char filename[MAXPATHLEN + 1];
267
268         /* Get number of tapes in the system */
269         new_max_tape_drives = get_max_tape_drives();
270
271         if (new_max_tape_drives == 0) {
272                 /* No tapes found */
273                 fprintf(stderr, _("No tape drives with statistics found\n"));
274                 exit(1);
275         }
276         else {
277                 /* Allocate structures */
278                 if (tape_old_stats == NULL) {
279                         tape_old_stats = (struct tape_stats *)
280                                 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
281                         tape_new_stats = (struct tape_stats *)
282                                 malloc(sizeof(struct tape_stats) * new_max_tape_drives);
283                         for (i = 0; i < new_max_tape_drives; i++) {
284                                 tape_old_stats[i].valid = TAPE_STATS_INVALID;
285                                 tape_new_stats[i].valid = TAPE_STATS_INVALID;
286                         }
287                         max_tape_drives = new_max_tape_drives;
288                 } else
289                         /* This should only be called once */
290                         return;
291         }
292
293         /* Read stats for each tape */
294         for (i = 0; i < max_tape_drives; i++) {
295                 /*
296                  * Everything starts out valid but failing to open
297                  * a file gets the tape drive marked invalid.
298                  */
299                 tape_new_stats[i].valid = TAPE_STATS_VALID;
300                 tape_old_stats[i].valid = TAPE_STATS_VALID;
301
302                 __gettimeofday(&tape_old_stats[i].tv, NULL);
303
304                 tape_new_stats[i].tv.tv_sec = tape_old_stats[i].tv.tv_sec;
305                 tape_new_stats[i].tv.tv_usec = tape_old_stats[i].tv.tv_usec;
306
307                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
308                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
309                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
310                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
311                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
312                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
313                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
314                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
315                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
316
317                 tape_old_stats[i].read_time = 0;
318                 tape_old_stats[i].write_time = 0;
319                 tape_old_stats[i].other_time = 0;
320                 tape_old_stats[i].read_bytes = 0;
321                 tape_old_stats[i].write_bytes = 0;
322                 tape_old_stats[i].read_count = 0;
323                 tape_old_stats[i].write_count = 0;
324                 tape_old_stats[i].other_count = 0;
325                 tape_old_stats[i].resid_count = 0;
326         }
327 }
328
329 /*
330  ***************************************************************************
331  * Collect a new sample of statistics for all existing tapes in the system.
332  ***************************************************************************
333  */
334 void tape_get_updated_stats(void)
335 {
336         int i;
337         FILE *fp;
338         char filename[MAXPATHLEN + 1] = { 0 };
339
340         /* Check tapes and realloc structures if  needed */
341         tape_check_tapes_and_realloc();
342
343         for (i = 0; i < max_tape_drives; i++) {
344                 /*
345                  * Everything starts out valid but failing
346                  * to open a file gets the tape drive marked invalid.
347                  */
348                 tape_new_stats[i].valid = TAPE_STATS_VALID;
349                 __gettimeofday(&tape_new_stats[i].tv, NULL);
350
351                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_ns", read_time)
352                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_ns", write_time)
353                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "io_ns", other_time)
354                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_byte_cnt", read_bytes)
355                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_byte_cnt", write_bytes)
356                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "read_cnt", read_count)
357                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "write_cnt", write_count)
358                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "other_cnt", other_count)
359                 TAPE_STAT_FILE_VAL(TAPE_STAT_PATH "resid_cnt", resid_count)
360
361                 if ((tape_new_stats[i].read_time < tape_old_stats[i].read_time) ||
362                     (tape_new_stats[i].write_time < tape_old_stats[i].write_time) ||
363                     (tape_new_stats[i].other_time < tape_old_stats[i].other_time)) {
364                         tape_new_stats[i].valid = TAPE_STATS_INVALID;
365                 }
366         }
367 }
368
369 /*
370  ***************************************************************************
371  * Display tapes statistics headings.
372  ***************************************************************************
373  */
374 void tape_write_headings(void)
375 {
376         printf("Tape:     r/s     w/s   ");
377         if (DISPLAY_MEGABYTES(flags)) {
378                 printf("MB_read/s   MB_wrtn/s");
379         } else {
380                 printf("kB_read/s   kB_wrtn/s");
381         }
382         printf("  %%Rd  %%Wr  %%Oa    Rs/s    Ot/s\n");
383 }
384
385 /*
386  ***************************************************************************
387  * Calculate statistics for current tape.
388  *
389  * IN:
390  * @i           Index in array for current tape.
391  *
392  * OUT:
393  * @stats       Statistics for current tape.
394  ***************************************************************************
395  */
396 void tape_calc_one_stats(struct calc_stats *stats, int i)
397 {
398         uint64_t duration;
399         double temp;
400         FILE *fp;
401
402         /* Duration in ms done in ms to prevent rounding issues with using seconds */
403         duration = (tape_new_stats[i].tv.tv_sec -
404                 tape_old_stats[i].tv.tv_sec) * 1000;
405         duration -= tape_old_stats[i].tv.tv_usec / 1000;
406         duration += tape_new_stats[i].tv.tv_usec / 1000;
407
408         /* If duration is zero we need to calculate the ms since boot time */
409         if (duration == 0) {
410                 fp = fopen(UPTIME, "r");
411
412                 /*
413                  * Get uptime from /proc/uptime and if we can't then just set duration to
414                  * be 0 - it will mean that we don't calculate stats.
415                  */
416                 if (fp == NULL) {
417                         duration = 0;
418                 } else {
419                         if (fscanf(fp, "%lf", &temp) != 1) {
420                                 temp = 0;
421                         }
422                         duration = (uint64_t) (temp * 1000);
423                         fclose(fp);
424                 }
425         }
426
427         /* The second value passed into the macro is the thing being calculated */
428         CALC_STAT_CNT(read_count, reads_per_second)
429         CALC_STAT_CNT(write_count, writes_per_second)
430         CALC_STAT_CNT(other_count, other_per_second)
431         CALC_STAT_KB(read_bytes, kbytes_read_per_second)
432         CALC_STAT_KB(write_bytes, kbytes_written_per_second)
433         CALC_STAT_PCT(read_time, read_pct_wait)
434         CALC_STAT_PCT(write_time, write_pct_wait)
435         CALC_STAT_PCT(other_time, all_pct_wait)
436         CALC_STAT_CNT(resid_count, resids_per_second)
437 }
438
439 /*
440  ***************************************************************************
441  * Display statistics for current tape.
442  *
443  * IN:
444  * @tape        Statistics for current tape.
445  * @i           Index in array for current tape.
446  ***************************************************************************
447  */
448 void tape_write_stats(struct calc_stats *tape, int i)
449 {
450         char buffer[32];
451         uint64_t divisor = 1;
452
453         if (DISPLAY_MEGABYTES(flags))
454                 divisor = 1024;
455
456         sprintf(buffer, "st%i        ", i);
457         buffer[5] = 0;
458         cprintf_in(IS_STR, "%s", buffer, 0);
459         cprintf_u64(NO_UNIT, 2, 7,
460                     tape->reads_per_second,
461                     tape->writes_per_second);
462         cprintf_u64(DISPLAY_UNIT(flags) ? UNIT_KILOBYTE : NO_UNIT, 2, 11,
463                     DISPLAY_UNIT(flags) ? tape->kbytes_read_per_second
464                                         : tape->kbytes_read_per_second / divisor,
465                     DISPLAY_UNIT(flags) ? tape->kbytes_written_per_second
466                                         : tape->kbytes_written_per_second / divisor);
467         cprintf_pc(DISPLAY_UNIT(flags), 3, 4, 0,
468                    (double) tape->read_pct_wait,
469                    (double) tape->write_pct_wait,
470                    (double) tape->all_pct_wait);
471         cprintf_u64(NO_UNIT, 2, 7,
472                     tape->resids_per_second,
473                     tape->other_per_second);
474         printf("\n");
475 }
476
477 /*
478  ***************************************************************************
479  * Print everything now (stats and uptime).
480  *
481  * IN:
482  * @rectime     Current date and time.
483  ***************************************************************************
484  */
485 void write_stats(struct tm *rectime)
486 {
487         int i;
488         struct calc_stats tape;
489         struct tape_stats *tmp;
490
491         /* Test stdout */
492         TEST_STDOUT(STDOUT_FILENO);
493
494         /* Print time stamp */
495         if (DISPLAY_TIMESTAMP(flags)) {
496                 if (DISPLAY_ISO(flags)) {
497                         strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
498                 }
499                 else {
500                         strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
501                 }
502                 printf("%s\n", timestamp);
503         }
504
505         /* Print the headings */
506         tape_write_headings();
507
508         /*
509          * If either new or old is invalid or the I/Os per second is 0 and
510          * zero omit is true then we print nothing.
511          */
512         if (max_tape_drives > 0) {
513
514                 for (i = 0; i < max_tape_drives; i++) {
515                         if ((tape_new_stats[i].valid == TAPE_STATS_VALID) &&
516                                 (tape_old_stats[i].valid == TAPE_STATS_VALID)) {
517                                 tape_calc_one_stats(&tape, i);
518                                 if (!(DISPLAY_ZERO_OMIT(flags)
519                                         && (tape.other_per_second == 0)
520                                         && (tape.reads_per_second == 0)
521                                         && (tape.writes_per_second == 0)
522                                         && (tape.kbytes_read_per_second == 0)
523                                         && (tape.kbytes_written_per_second == 0)
524                                         && (tape.read_pct_wait == 0)
525                                         && (tape.write_pct_wait == 0)
526                                         && (tape.all_pct_wait == 0)
527                                         && (tape.resids_per_second == 0))) {
528                                         tape_write_stats(&tape, i);
529                                 }
530                         }
531                 }
532                 /*
533                  * Swap new and old so next time we compare against the new old stats.
534                  * If a new tape drive appears it won't appear in the output until after
535                  * the second time we gather information about it.
536                  */
537                 tmp = tape_old_stats;
538                 tape_old_stats = tape_new_stats;
539                 tape_new_stats = tmp;
540         }
541         printf("\n");
542 }
543
544 /*
545  ***************************************************************************
546  * Main loop: Read tape stats from the relevant sources and display them.
547  *
548  * IN:
549  * @count       Number of lines of stats to print.
550  * @rectime     Current date and time.
551  ***************************************************************************
552  */
553 void rw_tape_stat_loop(long int count, struct tm *rectime)
554 {
555         struct tape_stats *tmp;
556         int skip = 0;
557
558         /* Should we skip first report? */
559         if (DISPLAY_OMIT_SINCE_BOOT(flags) && interval > 0) {
560                 skip = 1;
561         }
562
563         do {
564
565                 if (tape_new_stats == NULL) {
566                         tape_gather_initial_stats();
567                 } else {
568                         tape_get_updated_stats();
569                 }
570
571                 /* Get time */
572                 get_xtime(rectime, 0, LOCAL_TIME);
573
574                 /* Check whether we should skip first report */
575                 if (!skip) {
576                         /* Print results */
577                         write_stats(rectime);
578
579                         if (count > 0) {
580                                 count--;
581                         }
582                 }
583                 else {
584                         skip = 0;
585                         tmp = tape_old_stats;
586                         tape_old_stats = tape_new_stats;
587                         tape_new_stats = tmp;
588                 }
589
590                 if (count) {
591                         __pause();
592                 }
593         }
594         while (count);
595 }
596
597 /*
598  ***************************************************************************
599  * Main entry to the tapestat program.
600  ***************************************************************************
601  */
602 int main(int argc, char **argv)
603 {
604         int it = 0;
605         int opt = 1;
606         int i;
607         long count = 1;
608         struct utsname header;
609         struct tm rectime;
610
611 #ifdef USE_NLS
612         /* Init National Language Support */
613         init_nls();
614 #endif
615
616         /* Init color strings */
617         init_colors();
618
619         /* Process args... */
620         while (opt < argc) {
621
622                 if (!strcmp(argv[opt], "--human")) {
623                         flags |= T_D_UNIT;
624                         opt++;
625                 }
626
627                 else if (!strncmp(argv[opt], "-", 1)) {
628                         for (i = 1; *(argv[opt] + i); i++) {
629
630                                 switch (*(argv[opt] + i)) {
631
632                                 case 'k':
633                                         if (DISPLAY_MEGABYTES(flags)) {
634                                                 usage(argv[0]);
635                                         }
636                                         /* Display stats in kB/s */
637                                         flags |= T_D_KILOBYTES;
638                                         break;
639
640                                 case 'm':
641                                         if (DISPLAY_KILOBYTES(flags)) {
642                                                 usage(argv[0]);
643                                         }
644                                         /* Display stats in MB/s */
645                                         flags |= T_D_MEGABYTES;
646                                         break;
647
648                                 case 't':
649                                         /* Display timestamp */
650                                         flags |= T_D_TIMESTAMP;
651                                         break;
652
653                                 case 'y':
654                                         /* Don't display stats since system restart */
655                                         flags |= T_D_OMIT_SINCE_BOOT;
656                                         break;
657
658                                 case 'z':
659                                         /* Omit output for devices with no activity */
660                                         flags |= T_D_ZERO_OMIT;
661                                         break;
662
663                                 case 'V':
664                                         /* Print version number and exit */
665                                         print_version();
666                                         break;
667
668                                 default:
669                                         usage(argv[0]);
670                                 }
671                         }
672                         opt++;
673                 }
674
675                 else if (!it) {
676                         interval = atol(argv[opt++]);
677                         if (interval < 0) {
678                                 usage(argv[0]);
679                         }
680                         count = -1;
681                         it = 1;
682                 }
683
684                 else if (it > 0) {
685                         count = atol(argv[opt++]);
686                         if ((count < 1) || !interval) {
687                                 usage(argv[0]);
688                         }
689                         it = -1;
690                 }
691                 else {
692                         usage(argv[0]);
693                 }
694         }
695
696         if (!interval) {
697                 count = 1;
698         }
699
700         tape_initialise();
701
702         get_xtime(&rectime, 0, LOCAL_TIME);
703
704         /*
705          * Don't buffer data if redirected to a pipe.
706          * Note: With musl-c, the behavior of this function is undefined except
707          * when it is the first operation on the stream.
708          */
709         setbuf(stdout, NULL);
710
711         /* Get system name, release number and hostname */
712         __uname(&header);
713         if (print_gal_header(&rectime, header.sysname, header.release,
714                              header.nodename, header.machine, cpu_nr,
715                              PLAIN_OUTPUT)) {
716                 flags |= T_D_ISO;
717         }
718         printf("\n");
719
720         /* Set a handler for SIGALRM */
721         memset(&alrm_act, 0, sizeof(alrm_act));
722         alrm_act.sa_handler = alarm_handler;
723         sigaction(SIGALRM, &alrm_act, NULL);
724         alarm(interval);
725
726         /* Main loop */
727         rw_tape_stat_loop(count, &rectime);
728
729         /* Free structures */
730         tape_uninitialise();
731
732         return 0;
733 }