/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999-2003, 2005-2012, 2014-2017 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see . */ /* MH folder command */ #include #include #include #include #include #include #include #include #include static char prog_doc[] = N_("set or list current folder or message"); static char args_doc[] = N_("[ACTION] [MSG]"); typedef int (*folder_action) (void); static int action_print (void); static int action_list (void); static int action_push (void); static int action_pop (void); static int action_pack (void); static folder_action action = action_print; int show_all = 0; /* List all folders. Raised by --all switch */ int create_flag = -1; /* Create non-existent folders (--create). -1: Prompt before creating 0: Do not create 1: Always create without prompting */ int fast_mode = 0; /* Fast operation mode. (--fast) */ /* The following two vars are three-state, -1 meaning the default for current mode */ int print_header = -1; /* Display the header line (--header) */ int print_total = -1; /* Display total stats */ int verbose = 0; /* Verbosely list actions taken */ size_t pack_start; /* Number to be assigned to the first message in packed folder. 0 means do not change first message number. */ int dry_run; /* Dry run mode */ const char *push_folder; /* Folder name to push on stack */ const char *mh_seq_name; /* Name of the mh sequence file (defaults to .mh_sequences) */ int has_folder; /* Folder has been explicitely given */ int recurse_option = 0; size_t max_depth = 1; /* Maximum recursion depth (0 means infinity) */ #define OPTION_IS_SET(opt) ((opt) == -1 ? show_all : opt) void set_action (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { if (strcmp (opt->opt_long, "print") == 0) action = action_print; else if (strcmp (opt->opt_long, "pack") == 0) { action = action_pack; if (arg) { if (mu_str_to_c (arg, mu_c_size, &pack_start, NULL)) { mu_parseopt_error (po, _("%s: invalid number"), arg); exit (po->po_exit_error); } } } else if (strcmp (opt->opt_long, "list") == 0) action = action_list; else if (strcmp (opt->opt_long, "push") == 0) { action = action_push; if (arg) { push_folder = mh_current_folder (); mh_set_current_folder (arg); } } else if (strcmp (opt->opt_long, "pop") == 0) action = action_pop; else abort (); } void set_folder (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { has_folder = 1; push_folder = mh_current_folder (); mh_set_current_folder (arg); } static struct mu_option options[] = { MU_OPTION_GROUP (N_("Actions are:")), { "print", 0, NULL, MU_OPTION_DEFAULT, N_("list the folders (default)"), mu_c_string, NULL, set_action }, { "list", 0, NULL, MU_OPTION_DEFAULT, N_("list the contents of the folder stack"), mu_c_string, NULL, set_action }, { "pack", 0, N_("NUMBER"), MU_OPTION_ARG_OPTIONAL, N_("remove holes in message numbering, begin numbering from NUMBER (default: first message number)"), mu_c_string, NULL, set_action }, { "push", 0, N_("FOLDER"), MU_OPTION_ARG_OPTIONAL, N_("push the folder on the folder stack. If FOLDER is specified, it is pushed. " "Otherwise, if a folder is given in the command line (via + or --folder), " "it is pushed on stack. Otherwise, the current folder and the top of the folder " "stack are exchanged"), mu_c_string, NULL, set_action }, { "pop", 0, NULL, MU_OPTION_DEFAULT, N_("pop the folder off the folder stack"), mu_c_string, NULL, set_action }, MU_OPTION_GROUP (N_("Options are:")), { "folder", 0, N_("FOLDER"), MU_OPTION_DEFAULT, N_("specify folder to operate upon"), mu_c_string, NULL, set_folder }, { "all", 0, NULL, MU_OPTION_DEFAULT, N_("list all folders"), mu_c_bool, &show_all }, { "create", 0, NULL, MU_OPTION_DEFAULT, N_("create non-existing folders"), mu_c_bool, &create_flag }, { "fast", 0, NULL, MU_OPTION_DEFAULT, N_("list only the folder names"), mu_c_bool, &fast_mode }, { "header", 0, NULL, MU_OPTION_DEFAULT, N_("print the header line"), mu_c_bool, &print_header }, { "recurse",0, NULL, MU_OPTION_DEFAULT, N_("scan folders recursively"), mu_c_bool, &recurse_option }, { "total", 0, NULL, MU_OPTION_DEFAULT, N_("output the total statistics"), mu_c_bool, &print_total }, { "verbose", 0, NULL, MU_OPTION_DEFAULT, N_("verbosely list actions taken"), mu_c_bool, &verbose }, { "dry-run", 0, NULL, MU_OPTION_DEFAULT, N_("do nothing, print what would be done (with --pack)"), mu_c_bool, &dry_run }, MU_OPTION_END }; /* ************************************************************* */ /* Printing */ struct folder_info { char *name; /* Folder name */ size_t message_count; /* Number of messages in this folder */ size_t min; /* First used sequence number (=uid) */ size_t max; /* Last used sequence number */ size_t cur; /* UID of the current message */ size_t others; /* Number of non-message files */ }; mu_list_t folder_info_list; /* Memory storage for folder info */ size_t message_count; /* Total number of messages */ int name_prefix_len; /* Length of the mu_path_folder_dir */ void install_folder_info (const char *name, struct folder_info const *info) { struct folder_info *new_info = mu_alloc (sizeof (*new_info)); *new_info = *info; new_info->name = mu_strdup (new_info->name + name_prefix_len); mu_list_append (folder_info_list, new_info); message_count += info->message_count; } static int folder_info_comp (const void *a, const void *b) { return strcmp (((struct folder_info *)a)->name, ((struct folder_info *)b)->name); } static void read_seq_file (struct folder_info *info, const char *prefix, const char *name) { char *pname = NULL; mu_property_t prop; const char *p; pname = mh_safe_make_file_name (prefix, name); prop = mh_read_property_file (pname, 1); if (mu_property_sget_value (prop, "cur", &p) == 0) info->cur = strtoul (p, NULL, 0); mu_property_destroy (&prop); } static void _scan (const char *name, size_t depth) { DIR *dir; struct dirent *entry; struct folder_info info; char *p; struct stat st; size_t uid; dir = opendir (name); if (!dir && errno == ENOENT) { if (create_flag) { if (mh_check_folder (name, create_flag == -1)) { push_folder = NULL; return; } dir = opendir (name); } else exit (1); } if (!dir) { mu_error (_("cannot scan folder %s: %s"), name, strerror (errno)); return; } if (max_depth == 1) { if (fast_mode && depth > 0) { memset (&info, 0, sizeof (info)); info.name = (char*) name; install_folder_info (name, &info); closedir (dir); return; } } if (max_depth && depth > max_depth) { closedir (dir); return; } memset (&info, 0, sizeof (info)); info.name = mu_strdup (name); while ((entry = readdir (dir))) { if (entry->d_name[0] == '.') { if (strcmp (entry->d_name, mh_seq_name) == 0) read_seq_file (&info, name, entry->d_name); } else if (entry->d_name[0] != ',') { p = mh_safe_make_file_name (name, entry->d_name); if (stat (p, &st) < 0) mu_diag_funcall (MU_DIAG_ERROR, "stat", p, errno); else if (S_ISDIR (st.st_mode)) { info.others++; _scan (p, depth+1); } else { char *endp; uid = strtoul (entry->d_name, &endp, 10); if (*endp) info.others++; else { info.message_count++; if (info.min == 0 || uid < info.min) info.min = uid; if (uid > info.max) info.max = uid; } } } } if (info.cur) { p = mh_safe_make_file_name (name, mu_umaxtostr (0, info.cur)); if (stat (p, &st) < 0 || !S_ISREG (st.st_mode)) info.cur = 0; free (p); } closedir (dir); if (depth > 0) install_folder_info (name, &info); } static int _folder_info_printer (void *item, void *data) { struct folder_info *info = item; int len = strlen (info->name); if (len < 22) printf ("%22.22s", info->name); else printf ("%s", info->name); if (strcmp (info->name, mh_current_folder ()) == 0) printf ("+"); else printf (" "); if (info->message_count) { printf (ngettext(" has %4lu message (%4lu-%4lu)", " has %4lu messages (%4lu-%4lu)", info->message_count), (unsigned long) info->message_count, (unsigned long) info->min, (unsigned long) info->max); if (info->cur) printf ("; cur=%4lu", (unsigned long) info->cur); } else { printf (_(" has no messages")); } if (info->others) { if (!info->cur) printf ("; "); else printf ("; "); printf (_("(others)")); } printf (".\n"); return 0; } static int _folder_name_printer (void *item, void *data) { struct folder_info *info = item; printf ("%s\n", info->name); return 0; } static void print_all (void) { mu_list_foreach (folder_info_list, _folder_info_printer, NULL); } static void print_fast (void) { mu_list_foreach (folder_info_list, _folder_name_printer, NULL); } static int action_print (void) { const char *folder_dir = mu_folder_directory (); mh_seq_name = mh_global_profile_get ("mh-sequences", MH_SEQUENCES_FILE); name_prefix_len = strlen (folder_dir); if (folder_dir[name_prefix_len - 1] == '/') name_prefix_len++; name_prefix_len++; /* skip past the slash */ mu_list_create (&folder_info_list); if (show_all) { _scan (folder_dir, 0); } else { char *p = mh_expand_name (NULL, mh_current_folder (), NAME_ANY); _scan (p, 1); free (p); } mu_list_sort (folder_info_list, folder_info_comp); if (fast_mode) print_fast (); else { if (OPTION_IS_SET (print_header)) printf (_("Folder # of messages ( range ) cur msg (other files)\n")); print_all (); if (OPTION_IS_SET (print_total)) { size_t folder_count; mu_list_count (folder_info_list, &folder_count); printf ("\n%24.24s=", _("TOTAL")); printf (ngettext ("%4lu message ", "%4lu messages ", message_count), (unsigned long) message_count); printf (ngettext ("in %4lu folder", "in %4lu folders", folder_count), (unsigned long) folder_count); printf ("\n"); } } if (push_folder) mh_global_save_state (); return 0; } /* ************************************************************* */ /* Listing */ static int action_list (void) { const char *stack = mh_global_context_get ("Folder-Stack", NULL); printf ("%s", mh_current_folder ()); if (stack && stack[0]) printf (" %s", stack); printf ("\n"); return 0; } /* ************************************************************* */ /* Push & pop */ static void get_stack (size_t *pc, char ***pv) { struct mu_wordsplit ws; const char *stack = mh_global_context_get ("Folder-Stack", NULL); if (!stack) { *pc = 0; *pv = NULL; } else if (mu_wordsplit (stack, &ws, MU_WRDSF_DEFFLAGS)) { mu_error (_("cannot split line `%s': %s"), stack, mu_wordsplit_strerror (&ws)); exit (1); } else { mu_wordsplit_get_words (&ws, pc, pv); mu_wordsplit_free (&ws); } } static void set_stack (int c, char **v) { char *str; int status = mu_argcv_string (c, v, &str); if (status) { mu_error ("%s", mu_strerror (status)); exit (1); } mu_argcv_free (c, v); mh_global_context_set ("Folder-Stack", str); free (str); } static void push_val (size_t *pc, char ***pv, const char *val) { size_t c = *pc; char **v = *pv; c++; if (c == 1) { v = mu_calloc (c + 1, sizeof (*v)); } else { v = mu_realloc (v, (c + 1) * sizeof (*v)); memmove (&v[1], &v[0], c * sizeof (*v)); } v[0] = mu_strdup (val); *pv = v; *pc = c; } static char * pop_val (size_t *pc, char ***pv) { char *val; size_t c; char **v; if (*pc == 0) return NULL; c = *pc; v = *pv; val = v[0]; memmove (&v[0], &v[1], c * sizeof (*v)); c--; *pc = c; *pv = v; return val; } static int action_push (void) { size_t c; char **v; get_stack (&c, &v); if (push_folder) push_val (&c, &v, push_folder); else { char *t = v[0]; v[0] = mu_strdup (mh_current_folder ()); mh_set_current_folder (t); free (t); } set_stack (c, v); action_list (); mh_global_save_state (); return 0; } static int action_pop (void) { size_t c; char **v; get_stack (&c, &v); if (c) { char *p = pop_val (&c, &v); set_stack (c, v); mh_set_current_folder (p); free (p); } action_list (); mh_global_save_state (); return 0; } /* ************************************************************* */ /* Packing */ struct pack_tab { size_t orig; size_t new; }; static int pack_rename (struct pack_tab *tab, int reverse) { int rc; const char *s1; const char *s2; const char *from, *to; s1 = mu_umaxtostr (0, tab->orig); s2 = mu_umaxtostr (1, tab->new); if (!reverse) { from = s1; to = s2; } else { from = s2; to = s1; } if (verbose) fprintf (stderr, _("Renaming %s to %s\n"), from, to); if (!dry_run) { if ((rc = rename (from, to))) mu_error (_("cannot rename `%s' to `%s': %s"), from, to, mu_strerror (errno)); } else rc = 0; return rc; } /* Reverse ordering of COUNT entries in array TAB */ static void reverse (struct pack_tab *tab, size_t count) { size_t i, j; for (i = 0, j = count-1; i < j; i++, j--) { size_t tmp; tmp = tab[i].orig; tab[i].orig = tab[j].orig; tab[j].orig = tmp; tmp = tab[i].new; tab[i].new = tab[j].new; tab[j].new = tmp; } } static void roll_back (const char *folder_name, struct pack_tab *pack_tab, size_t i) { size_t start; if (i == 0) return; start = --i; mu_error (_("rolling back changes...")); do if (pack_rename (pack_tab + i, 1)) { mu_error (_("CRITICAL ERROR: Folder `%s' left in an inconsistent state, because an error\n" "occurred while trying to roll back the changes.\n" "Message range %s-%s has been renamed to %s-%s."), folder_name, mu_umaxtostr (0, pack_tab[0].orig), mu_umaxtostr (1, pack_tab[start].orig), mu_umaxtostr (2, pack_tab[0].new), mu_umaxtostr (3, pack_tab[start].new)); mu_error (_("You will have to fix it manually.")); exit (1); } while (i-- > 0); mu_error (_("folder `%s' restored successfully"), folder_name); } struct fixup_data { mu_mailbox_t mbox; const char *folder_dir; struct pack_tab *pack_tab; size_t count; }; static int pack_cmp (const void *a, const void *b) { const struct pack_tab *pa = a; const struct pack_tab *pb = b; if (pa->orig < pb->orig) return -1; else if (pa->orig > pb->orig) return 1; return 0; } static size_t pack_xlate (struct pack_tab *pack_tab, size_t count, size_t n) { struct pack_tab key, *p; key.orig = n; p = bsearch (&key, pack_tab, count, sizeof pack_tab[0], pack_cmp); return p ? p->new : 0; } static void _fixup (const char *name, const char *value, struct fixup_data *fd, int flags) { size_t i; int rc; struct mu_wordsplit ws; mu_msgset_t msgset; if (verbose) fprintf (stderr, "Sequence `%s'...\n", name); if (mu_wordsplit (value, &ws, MU_WRDSF_DEFFLAGS)) { mu_error (_("cannot split line `%s': %s"), value, mu_wordsplit_strerror (&ws)); return; } rc = mu_msgset_create (&msgset, fd->mbox, MU_MSGSET_UID); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_create", NULL, rc); exit (1); } for (i = 0; i < ws.ws_wordc; i++) { size_t n = pack_xlate (fd->pack_tab, fd->count, strtoul (ws.ws_wordv[i], NULL, 0)); if (n) { rc = mu_msgset_add_range (msgset, n, n, MU_MSGSET_UID); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_add_range", NULL, rc); exit (1); } } } mu_wordsplit_free (&ws); mh_seq_add (fd->mbox, name, msgset, flags | SEQ_ZERO); mu_msgset_free (msgset); if (verbose) { const char *p = mh_seq_read (fd->mbox, name, flags); fprintf (stderr, "Sequence %s: %s\n", name, p); } } static int fixup_global (const char *name, const char *value, void *data) { _fixup (name, value, data, 0); return 0; } static int fixup_private (const char *name, const char *value, void *data) { struct fixup_data *fd = data; int nlen = strlen (name); if (nlen < 4 || memcmp (name, "atr-", 4)) return 0; name += 4; nlen = strlen (name) - strlen (fd->folder_dir); if (nlen > 0 && strcmp (name + nlen, fd->folder_dir) == 0) { char *s = mu_alloc (nlen); memcpy (s, name, nlen - 1); s[nlen-1] = 0; _fixup (s, value, fd, SEQ_PRIVATE); free (s); } return 0; } int action_pack (void) { const char *folder_dir = mh_expand_name (NULL, mh_current_folder (), NAME_ANY); mu_mailbox_t mbox = mh_open_folder (mh_current_folder (), MU_STREAM_RDWR); struct pack_tab *pack_tab; size_t i, count, start; int status; struct fixup_data fd; /* Allocate pack table */ if (mu_mailbox_messages_count (mbox, &count)) { mu_error (_("cannot read input mailbox: %s"), mu_strerror (errno)); return 1; } pack_tab = mu_calloc (count, sizeof pack_tab[0]); /* Never freed. No use to try to. */ /* Populate it with message numbers */ if (verbose) fprintf (stderr, _("Getting message numbers.\n")); for (i = 0; i < count; i++) { mu_message_t msg; status = mu_mailbox_get_message (mbox, i + 1, &msg); if (status) { mu_error (_("%lu: cannot get message: %s"), (unsigned long) i, mu_strerror (status)); return 1; } mh_message_number (msg, &pack_tab[i].orig); } if (verbose) fprintf (stderr, ngettext ("%s message number collected.\n", "%s message numbers collected.\n", (unsigned long) count), mu_umaxtostr (0, count)); mu_mailbox_close (mbox); mu_mailbox_destroy (&mbox); /* Compute new message numbers */ if (pack_start == 0) pack_start = pack_tab[0].orig; for (i = 0, start = pack_start; i < count; i++) pack_tab[i].new = start++; if (pack_start > pack_tab[0].orig) { if (verbose) fprintf (stderr, _("Reverting pack table.\n")); reverse (pack_tab, i); } /* Change to the folder directory and rename messages */ status = chdir (folder_dir); if (status) { mu_error (_("cannot change to directory `%s': %s"), folder_dir, mu_strerror (status)); return 1; } for (i = 0; i < count; i++) { if (pack_rename (pack_tab + i, 0)) { roll_back (folder_dir, pack_tab, i); return 1; } } if (verbose) fprintf (stderr, _("Finished packing messages.\n")); /* Fix-up sequences */ if (!dry_run) { mbox = mh_open_folder (mh_current_folder (), MU_STREAM_RDWR); fd.mbox = mbox; fd.folder_dir = folder_dir; fd.pack_tab = pack_tab; fd.count = count; if (verbose) fprintf (stderr, _("Fixing global sequences\n")); mh_global_sequences_iterate (mbox, fixup_global, &fd); if (verbose) fprintf (stderr, _("Fixing private sequences\n")); mh_global_context_iterate (fixup_private, &fd); mu_mailbox_close (mbox); mu_mailbox_destroy (&mbox); mh_global_save_state (); } return 0; } int main (int argc, char **argv) { int index = 0; mu_msgset_t msgset; mh_getopt (&argc, &argv, options, 0, args_doc, prog_doc, NULL); if (recurse_option) max_depth = 0; /* If folder is invoked by a name ending with "s" (e.g., folders), `-all' is assumed */ if (mu_program_name[strlen (mu_program_name) - 1] == 's') show_all = 1; if (has_folder) { /* If a +folder is given along with the -all switch, folder will, in addition to setting the current folder, list the top-level subfolders for the current folder (with -norecurse) or list all sub-folders under the current folder recursively (with -recurse). */ if (show_all && max_depth) max_depth = 2; show_all = 0; } if (argc - index == 1) { mu_mailbox_t mbox = mh_open_folder (mh_current_folder (), MU_STREAM_RDWR); mh_msgset_parse (&msgset, mbox, argc - index, argv + index, "cur"); mh_mailbox_set_cur (mbox, mh_msgset_first (msgset, RET_UID)); mu_msgset_free (msgset); mh_global_save_state (); mu_mailbox_close (mbox); mu_mailbox_destroy (&mbox); } else if (argc - index > 1) { mu_error (_("too many arguments")); exit (1); } return (*action) (); }