]> git.notmuchmail.org Git - notmuch/blob - notmuch.c
Print a better message than "0s" for zero seconds.
[notmuch] / notmuch.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #ifndef _GNU_SOURCE
22 #define _GNU_SOURCE /* for getline */
23 #endif
24 #include <stdio.h>
25
26 #include "notmuch.h"
27
28 /* This is separate from notmuch-private.h because we're trying to
29  * keep notmuch.c from looking into any internals, (which helps us
30  * develop notmuch.h into a plausible library interface).
31  */
32 #include "xutil.h"
33
34 #include <stddef.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <unistd.h>
39 #include <dirent.h>
40 #include <errno.h>
41
42 #include <talloc.h>
43
44 #include <glib.h> /* g_strdup_printf */
45
46 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
47
48 typedef int (*command_function_t) (int argc, char *argv[]);
49
50 typedef struct command {
51     const char *name;
52     command_function_t function;
53     const char *usage;
54 } command_t;
55
56 typedef struct {
57     int total_files;
58     int processed_files;
59     int added_messages;
60     struct timeval tv_start;
61 } add_files_state_t;
62
63 static void
64 chomp_newline (char *str)
65 {
66     if (str && str[strlen(str)-1] == '\n')
67         str[strlen(str)-1] = '\0';
68 }
69
70 /* Compute the number of seconds elapsed from start to end. */
71 double
72 tv_elapsed (struct timeval start, struct timeval end)
73 {
74     return ((end.tv_sec - start.tv_sec) +
75             (end.tv_usec - start.tv_usec) / 1e6);
76 }
77
78 void
79 print_formatted_seconds (double seconds)
80 {
81     int hours;
82     int minutes;
83
84     if (seconds < 1) {
85         printf ("almost no time");
86         return;
87     }
88
89     if (seconds > 3600) {
90         hours = (int) seconds / 3600;
91         printf ("%dh ", hours);
92         seconds -= hours * 3600;
93     }
94
95     if (seconds > 60) {
96         minutes = (int) seconds / 60;
97         printf ("%dm ", minutes);
98         seconds -= minutes * 60;
99     }
100
101     printf ("%ds", (int) seconds);
102 }
103
104 void
105 add_files_print_progress (add_files_state_t *state)
106 {
107     struct timeval tv_now;
108     double elapsed_overall, rate_overall;
109
110     gettimeofday (&tv_now, NULL);
111
112     elapsed_overall = tv_elapsed (state->tv_start, tv_now);
113     rate_overall = (state->processed_files) / elapsed_overall;
114
115     printf ("Processed %d", state->processed_files);
116
117     if (state->total_files) {
118         printf (" of %d files (", state->total_files);
119         print_formatted_seconds ((state->total_files - state->processed_files) /
120                                  rate_overall);
121         printf (" remaining).      \r");
122     } else {
123         printf (" files (%d files/sec.)    \r", (int) rate_overall);
124     }
125
126     fflush (stdout);
127 }
128
129 /* Examine 'path' recursively as follows:
130  *
131  *   o Ask the filesystem for the mtime of 'path' (path_mtime)
132  *
133  *   o Ask the database for its timestamp of 'path' (path_dbtime)
134  *
135  *   o If 'path_mtime' > 'path_dbtime'
136  *
137  *       o For each regular file in 'path' with mtime newer than the
138  *         'path_dbtime' call add_message to add the file to the
139  *         database.
140  *
141  *       o For each sub-directory of path, recursively call into this
142  *         same function.
143  *
144  *   o Tell the database to update its time of 'path' to 'path_mtime'
145  */
146 notmuch_status_t
147 add_files (notmuch_database_t *notmuch, const char *path,
148            add_files_state_t *state)
149 {
150     DIR *dir = NULL;
151     struct dirent *e, *entry = NULL;
152     int entry_length;
153     int err;
154     char *next = NULL;
155     struct stat st;
156     time_t path_mtime, path_dbtime;
157     notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
158
159     if (stat (path, &st)) {
160         fprintf (stderr, "Error reading directory %s: %s\n",
161                  path, strerror (errno));
162         return NOTMUCH_STATUS_FILE_ERROR;
163     }
164
165     if (! S_ISDIR (st.st_mode)) {
166         fprintf (stderr, "Error: %s is not a directory.\n", path);
167         return NOTMUCH_STATUS_FILE_ERROR;
168     }
169
170     path_mtime = st.st_mtime;
171
172     path_dbtime = notmuch_database_get_timestamp (notmuch, path);
173
174     dir = opendir (path);
175     if (dir == NULL) {
176         fprintf (stderr, "Error opening directory %s: %s\n",
177                  path, strerror (errno));
178         ret = NOTMUCH_STATUS_FILE_ERROR;
179         goto DONE;
180     }
181
182     entry_length = offsetof (struct dirent, d_name) +
183         pathconf (path, _PC_NAME_MAX) + 1;
184     entry = malloc (entry_length);
185
186     while (1) {
187         err = readdir_r (dir, entry, &e);
188         if (err) {
189             fprintf (stderr, "Error reading directory: %s\n",
190                      strerror (errno));
191             ret = NOTMUCH_STATUS_FILE_ERROR;
192             goto DONE;
193         }
194
195         if (e == NULL)
196             break;
197
198         /* If this directory hasn't been modified since the last
199          * add_files, then we only need to look further for
200          * sub-directories. */
201         if (path_mtime <= path_dbtime && entry->d_type != DT_DIR)
202             continue;
203
204         /* Ignore special directories to avoid infinite recursion.
205          * Also ignore the .notmuch directory.
206          */
207         /* XXX: Eventually we'll want more sophistication to let the
208          * user specify files to be ignored. */
209         if (strcmp (entry->d_name, ".") == 0 ||
210             strcmp (entry->d_name, "..") == 0 ||
211             strcmp (entry->d_name, ".notmuch") ==0)
212         {
213             continue;
214         }
215
216         next = g_strdup_printf ("%s/%s", path, entry->d_name);
217
218         if (stat (next, &st)) {
219             fprintf (stderr, "Error reading %s: %s\n",
220                      next, strerror (errno));
221             ret = NOTMUCH_STATUS_FILE_ERROR;
222             continue;
223         }
224
225         if (S_ISREG (st.st_mode)) {
226             /* If the file hasn't been modified since the last
227              * add_files, then we need not look at it. */
228             if (st.st_mtime > path_dbtime) {
229                 state->processed_files++;
230
231                 status = notmuch_database_add_message (notmuch, next);
232                 switch (status) {
233                     /* success */
234                     case NOTMUCH_STATUS_SUCCESS:
235                         state->added_messages++;
236                         break;
237                     /* Non-fatal issues (go on to next file) */
238                     case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
239                         /* Stay silent on this one. */
240                         break;
241                     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
242                         fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
243                                  next);
244                         break;
245                     /* Fatal issues. Don't process anymore. */
246                     case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
247                         fprintf (stderr, "A Xapian error was encountered. Halting processing.\n");
248                         ret = status;
249                         goto DONE;
250                     default:
251                         fprintf (stderr, "Internal error: add_message returned unexpected value: %d\n",  status);
252                         ret = status;
253                         goto DONE;
254                 }
255                 if (state->processed_files % 1000 == 0)
256                     add_files_print_progress (state);
257             }
258         } else if (S_ISDIR (st.st_mode)) {
259             status = add_files (notmuch, next, state);
260             if (status && ret == NOTMUCH_STATUS_SUCCESS)
261                 ret = status;
262         }
263
264         free (next);
265         next = NULL;
266     }
267
268     notmuch_database_set_timestamp (notmuch, path, path_mtime);
269
270   DONE:
271     if (next)
272         free (next);
273     if (entry)
274         free (entry);
275     if (dir)
276         closedir (dir);
277
278     return ret;
279 }
280
281 /* Recursively count all regular files in path and all sub-direcotries
282  * of path.  The result is added to *count (which should be
283  * initialized to zero by the top-level caller before calling
284  * count_files). */
285 void
286 count_files (const char *path, int *count)
287 {
288     DIR *dir;
289     struct dirent *entry, *e;
290     int entry_length;
291     int err;
292     char *next;
293     struct stat st;
294
295     dir = opendir (path);
296
297     if (dir == NULL) {
298         fprintf (stderr, "Warning: failed to open directory %s: %s\n",
299                  path, strerror (errno));
300         return;
301     }
302
303     entry_length = offsetof (struct dirent, d_name) +
304         pathconf (path, _PC_NAME_MAX) + 1;
305     entry = malloc (entry_length);
306
307     while (1) {
308         err = readdir_r (dir, entry, &e);
309         if (err) {
310             fprintf (stderr, "Error reading directory: %s\n",
311                      strerror (errno));
312             free (entry);
313             return;
314         }
315
316         if (e == NULL)
317             break;
318
319         /* Ignore special directories to avoid infinite recursion.
320          * Also ignore the .notmuch directory.
321          */
322         /* XXX: Eventually we'll want more sophistication to let the
323          * user specify files to be ignored. */
324         if (strcmp (entry->d_name, ".") == 0 ||
325             strcmp (entry->d_name, "..") == 0 ||
326             strcmp (entry->d_name, ".notmuch") == 0)
327         {
328             continue;
329         }
330
331         next = g_strdup_printf ("%s/%s", path, entry->d_name);
332
333         stat (next, &st);
334
335         if (S_ISREG (st.st_mode)) {
336             *count = *count + 1;
337             if (*count % 1000 == 0) {
338                 printf ("Found %d files so far.\r", *count);
339                 fflush (stdout);
340             }
341         } else if (S_ISDIR (st.st_mode)) {
342             count_files (next, count);
343         }
344
345         free (next);
346     }
347
348     free (entry);
349
350     closedir (dir);
351 }
352
353 int
354 setup_command (int argc, char *argv[])
355 {
356     notmuch_database_t *notmuch = NULL;
357     char *default_path, *mail_directory = NULL;
358     size_t line_size;
359     int count;
360     add_files_state_t add_files_state;
361     double elapsed;
362     struct timeval tv_now;
363     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
364
365     printf ("Welcome to notmuch!\n\n");
366
367     printf ("The goal of notmuch is to help you manage and search your collection of\n"
368             "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
369
370     printf ("Notmuch needs to know the top-level directory of your email archive,\n"
371             "(where you already have mail stored and where messages will be delivered\n"
372             "in the future). This directory can contain any number of sub-directories\n"
373             "and primarily just files with indvidual email messages (eg. maildir or mh\n"
374             "archives are perfect). If there are other, non-email files (such as\n"
375             "indexes maintained by other email programs) then notmuch will do its\n"
376             "best to detect those and ignore them.\n\n");
377
378     printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
379             "messages), will not work with notmuch. If that's how your mail is currently\n"
380             "stored, we recommend you first convert it to maildir format with a utility\n"
381             "such as mb2md. In that case, press Control-C now and run notmuch again\n"
382             "once the conversion is complete.\n\n");
383
384
385     default_path = notmuch_database_default_path ();
386     printf ("Top-level mail directory [%s]: ", default_path);
387     fflush (stdout);
388
389     getline (&mail_directory, &line_size, stdin);
390     chomp_newline (mail_directory);
391
392     printf ("\n");
393
394     if (mail_directory == NULL || strlen (mail_directory) == 0) {
395         if (mail_directory)
396             free (mail_directory);
397         mail_directory = default_path;
398     } else {
399         /* XXX: Instead of telling the user to use an environment
400          * variable here, we should really be writing out a configuration
401          * file and loading that on the next run. */
402         if (strcmp (mail_directory, default_path)) {
403             printf ("Note: Since you are not using the default path, you will want to set\n"
404                     "the NOTMUCH_BASE environment variable to %s so that\n"
405                     "future calls to notmuch commands will know where to find your mail.\n",
406                     mail_directory);
407             printf ("For example, if you are using bash for your shell, add:\n\n");
408             printf ("\texport NOTMUCH_BASE=%s\n\n", mail_directory);
409             printf ("to your ~/.bashrc file.\n\n");
410         }
411         free (default_path);
412     }
413
414     notmuch = notmuch_database_create (mail_directory);
415     if (notmuch == NULL) {
416         fprintf (stderr, "Failed to create new notmuch database at %s\n",
417                  mail_directory);
418         ret = NOTMUCH_STATUS_FILE_ERROR;
419         goto DONE;
420     }
421
422     printf ("OK. Let's take a look at the mail we can find in the directory\n");
423     printf ("%s ...\n", mail_directory);
424
425     count = 0;
426     count_files (mail_directory, &count);
427
428     printf ("Found %d total files. That's not much mail.\n\n", count);
429
430     printf ("Next, we'll inspect the messages and create a database of threads:\n");
431
432     add_files_state.total_files = count;
433     add_files_state.processed_files = 0;
434     add_files_state.added_messages = 0;
435     gettimeofday (&add_files_state.tv_start, NULL);
436
437     ret = add_files (notmuch, mail_directory, &add_files_state);
438
439     gettimeofday (&tv_now, NULL);
440     elapsed = tv_elapsed (add_files_state.tv_start,
441                           tv_now);
442     printf ("Processed %d total files in ", add_files_state.processed_files);
443     print_formatted_seconds (elapsed);
444     if (elapsed > 1) {
445         printf (" (%d files/sec.).                 \n",
446                 (int) (add_files_state.processed_files / elapsed));
447     } else {
448         printf (".                    \n");
449     }
450     printf ("Added %d unique messages to the database.\n\n",
451             add_files_state.added_messages);
452
453     printf ("When new mail is delivered to %s in the future,\n"
454             "run \"notmuch new\" to add it to the database.\n",
455             mail_directory);
456
457     if (ret) {
458         printf ("Note: At least one error was encountered: %s\n",
459                 notmuch_status_to_string (ret));
460     }
461
462   DONE:
463     if (mail_directory)
464         free (mail_directory);
465     if (notmuch)
466         notmuch_database_close (notmuch);
467
468     return ret;
469 }
470
471 int
472 new_command (int argc, char *argv[])
473 {
474     notmuch_database_t *notmuch;
475     const char *mail_directory;
476     add_files_state_t add_files_state;
477     double elapsed;
478     struct timeval tv_now;
479     int ret = 0;
480
481     notmuch = notmuch_database_open (NULL);
482     if (notmuch == NULL) {
483         ret = 1;
484         goto DONE;
485     }
486
487     mail_directory = notmuch_database_get_path (notmuch);
488
489     add_files_state.total_files = 0;
490     add_files_state.processed_files = 0;
491     add_files_state.added_messages = 0;
492     gettimeofday (&add_files_state.tv_start, NULL);
493
494     ret = add_files (notmuch, mail_directory, &add_files_state);
495
496     gettimeofday (&tv_now, NULL);
497     elapsed = tv_elapsed (add_files_state.tv_start,
498                           tv_now);
499     if (add_files_state.processed_files) {
500         printf ("Processed %d total files in ", add_files_state.processed_files);
501         print_formatted_seconds (elapsed);
502         if (elapsed > 1) {
503             printf (" (%d files/sec.).                 \n",
504                     (int) (add_files_state.processed_files / elapsed));
505         } else {
506             printf (".                    \n");
507         }
508     }
509     if (add_files_state.added_messages) {
510         printf ("Added %d new messages to the database (not much, really).\n",
511                 add_files_state.added_messages);
512     } else {
513         printf ("No new mail---and that's not much!\n");
514     }
515
516     if (ret) {
517         printf ("Note: At least one error was encountered: %s\n",
518                 notmuch_status_to_string (ret));
519     }
520
521   DONE:
522     if (notmuch)
523         notmuch_database_close (notmuch);
524
525     return ret;
526 }
527
528 int
529 search_command (int argc, char *argv[])
530 {
531     fprintf (stderr, "Error: search is not implemented yet.\n");
532     return 1;
533 }
534
535 int
536 show_command (int argc, char *argv[])
537 {
538     fprintf (stderr, "Error: show is not implemented yet.\n");
539     return 1;
540 }
541
542 int
543 dump_command (int argc, char *argv[])
544 {
545     FILE *output;
546     notmuch_database_t *notmuch;
547     notmuch_query_t *query;
548     notmuch_results_t *results;
549     notmuch_message_t *message;
550     notmuch_tags_t *tags;
551     int ret = 0;
552
553     if (argc) {
554         output = fopen (argv[0], "w");
555         if (output == NULL) {
556             fprintf (stderr, "Error opening %s for writing: %s\n",
557                      argv[0], strerror (errno));
558             ret = 1;
559             goto DONE;
560         }
561     } else {
562         output = stdout;
563     }
564
565     notmuch = notmuch_database_open (NULL);
566     if (notmuch == NULL) {
567         ret = 1;
568         goto DONE;
569     }
570
571     query = notmuch_query_create (notmuch, "");
572     if (query == NULL) {
573         fprintf (stderr, "Out of memory\n");
574         ret = 1;
575         goto DONE;
576     }
577
578     notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
579
580     for (results = notmuch_query_search (query);
581          notmuch_results_has_more (results);
582          notmuch_results_advance (results))
583     {
584         int first = 1;
585         message = notmuch_results_get (results);
586
587         fprintf (output,
588                  "%s (", notmuch_message_get_message_id (message));
589
590         for (tags = notmuch_message_get_tags (message);
591              notmuch_tags_has_more (tags);
592              notmuch_tags_advance (tags))
593         {
594             if (! first)
595                 fprintf (output, " ");
596
597             fprintf (output, "%s", notmuch_tags_get (tags));
598
599             first = 0;
600         }
601
602         fprintf (output, ")\n");
603
604         notmuch_message_destroy (message);
605     }
606
607     notmuch_query_destroy (query);
608
609   DONE:
610     if (notmuch)
611         notmuch_database_close (notmuch);
612     if (output != stdout)
613         fclose (output);
614
615     return ret;
616 }
617
618 int
619 restore_command (int argc, char *argv[])
620 {
621     FILE *input;
622     notmuch_database_t *notmuch;
623     char *line = NULL;
624     size_t line_size, line_len;
625     regex_t regex;
626     int rerr;
627     int ret = 0;
628
629     if (argc) {
630         input = fopen (argv[0], "r");
631         if (input == NULL) {
632             fprintf (stderr, "Error opening %s for reading: %s\n",
633                      argv[0], strerror (errno));
634             ret = 1;
635             goto DONE;
636         }
637     } else {
638         printf ("No filename given. Reading dump from stdin.\n");
639         input = stdin;
640     }
641
642     notmuch = notmuch_database_open (NULL);
643     if (notmuch == NULL) {
644         ret = 1;
645         goto DONE;
646     }
647
648     /* Dump output is one line per message. We match a sequence of
649      * non-space characters for the message-id, then one or more
650      * spaces, then a list of space-separated tags as a sequence of
651      * characters within literal '(' and ')'. */
652     xregcomp (&regex,
653               "^([^ ]+) \\(([^)]*)\\)$",
654               REG_EXTENDED);
655
656     while ((line_len = getline (&line, &line_size, input)) != -1) {
657         regmatch_t match[3];
658         char *message_id, *tags, *tag, *next;
659         notmuch_message_t *message;
660         notmuch_status_t status;
661
662         chomp_newline (line);
663
664         rerr = xregexec (&regex, line, 3, match, 0);
665         if (rerr == REG_NOMATCH)
666         {
667             fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
668                      line);
669             continue;
670         }
671
672         message_id = xstrndup (line + match[1].rm_so,
673                                match[1].rm_eo - match[1].rm_so);
674         tags = xstrndup (line + match[2].rm_so,
675                          match[2].rm_eo - match[2].rm_so);
676
677         if (strlen (tags)) {
678
679             message = notmuch_database_find_message (notmuch, message_id);
680             if (message == NULL) {
681                 fprintf (stderr, "Warning: Cannot apply tags to missing message: %s (",
682                          message_id);
683             }
684
685             next = tags;
686             while (next) {
687                 tag = strsep (&next, " ");
688                 if (*tag == '\0')
689                     continue;
690                 if (message) {
691                     status = notmuch_message_add_tag (message, tag);
692                     if (status) {
693                         fprintf (stderr,
694                                  "Error applying tag %s to message %s:\n",
695                                  tag, message_id);
696                         fprintf (stderr, "%s\n",
697                                  notmuch_status_to_string (status));
698                     }
699                 } else {
700                     fprintf (stderr, "%s ", tag);
701                 }
702             }
703
704             if (message)
705                 notmuch_message_destroy (message);
706             else
707                 fprintf (stderr, ")\n");
708         }
709         free (message_id);
710         free (tags);
711     }
712
713     regfree (&regex);
714
715   DONE:
716     if (line)
717         free (line);
718     if (notmuch)
719         notmuch_database_close (notmuch);
720
721     return ret;
722 }
723
724 command_t commands[] = {
725     { "setup", setup_command,
726       "Interactively setup notmuch for first use.\n"
727       "\t\tInvoking notmuch with no command argument will run setup if\n"
728       "\t\tthe setup command has not previously been completed." },
729     { "new", new_command,
730       "Find and import any new messages."},
731     { "search", search_command,
732       "<search-term> [...]\n\n"
733       "\t\tSearch for threads matching the given search terms.\n"
734       "\t\tOnce we actually implement search we'll document the\n"
735       "\t\tsyntax here." },
736     { "show", show_command,
737       "<thread-id>\n\n"
738       "\t\tShow the thread with the given thread ID (see 'search')." },
739     { "dump", dump_command,
740       "[<filename>]\n\n"
741       "\t\tCreate a plain-text dump of the tags for each message\n"
742       "\t\twriting to the given filename, if any, or to stdout.\n"
743       "\t\tThese tags are the only data in the notmuch database\n"
744       "\t\tthat can't be recreated from the messages themselves.\n"
745       "\t\tThe output of notmuch dump is therefore the only\n"
746       "\t\tcritical thing to backup (and much more friendly to\n"
747       "\t\tincremental backup than the native database files." },
748     { "restore", restore_command,
749       "<filename>\n\n"
750       "\t\tRestore the tags from the given dump file (see 'dump')." }
751 };
752
753 void
754 usage (void)
755 {
756     command_t *command;
757     int i;
758
759     fprintf (stderr, "Usage: notmuch <command> [args...]\n");
760     fprintf (stderr, "\n");
761     fprintf (stderr, "Where <command> and [args...] are as follows:\n");
762     fprintf (stderr, "\n");
763
764     for (i = 0; i < ARRAY_SIZE (commands); i++) {
765         command = &commands[i];
766
767         fprintf (stderr, "\t%s\t%s\n\n", command->name, command->usage);
768     }
769 }
770     
771 int
772 main (int argc, char *argv[])
773 {
774     command_t *command;
775     int i;
776
777     if (argc == 1)
778         return setup_command (0, NULL);
779
780     for (i = 0; i < ARRAY_SIZE (commands); i++) {
781         command = &commands[i];
782
783         if (strcmp (argv[1], command->name) == 0)
784             return (command->function) (argc - 2, &argv[2]);
785     }
786
787     /* Don't complain about "help" being an unknown command when we're
788        about to provide exactly what's wanted anyway. */
789     if (strcmp (argv[1], "help") == 0 ||
790         strcmp (argv[1], "--help") == 0)
791     {
792         fprintf (stderr, "The notmuch mail system.\n\n");
793     } else {
794         fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
795     }
796     usage ();
797     exit (1);
798
799     return 0;
800 }