From b27c22299d79aa0e5e815165db58031123032525 Mon Sep 17 00:00:00 2001 From: Bron Gondwana Date: Wed, 24 Dec 2008 23:30:38 +1100 Subject: [PATCH] Hash folders by userid only Cyrus tries to map IMAP folder names to directory paths on disk fairly directly, and this usually works OK, but has the property that when intermediate paths are missing there are some strange workarounds. E.g. But no user.foo.bar. Also, "user.foo" is a special case in all sorts of ways that mean it's not really a clean folder structure anyway. This patch changes the filesystem layout so that all of a user's folders are in a flat layout in the same directory (still with some hashing to support big systems) and also transparently working for DELETED folders if you have our delayed delete patch. e.g. the folders above look like: rather than: Note in particular that there are no subdirectories in any IMAP folder. Folders outside the user namespace are only hashed a single level, e.g.: --- imap/mailbox.c | 30 ++- lib/imapoptions | 8 +- lib/libconfig.c | 4 +- lib/libconfig.h | 2 +- tools/dohash | 288 -------------- tools/rehash | 1160 +++++++++++++++++++++++++++++++------------------------ tools/undohash | 154 -------- 7 files changed, 691 insertions(+), 955 deletions(-) delete mode 100755 tools/dohash delete mode 100755 tools/undohash diff --git a/imap/mailbox.c b/imap/mailbox.c index 7bce8e7..e651cf5 100644 --- a/imap/mailbox.c +++ b/imap/mailbox.c @@ -3488,6 +3488,7 @@ void mailbox_hash_mbox(char *buf, size_t buf_len, { const char *idx; char c, *p; + char itemname[MAX_MAILBOX_PATH+1]; snprintf(buf, buf_len, "%s", root); buf_len -= strlen(buf); @@ -3495,7 +3496,7 @@ void mailbox_hash_mbox(char *buf, size_t buf_len, if (config_virtdomains && (p = strchr(name, '!'))) { *p = '\0'; /* split domain!user */ - if (config_hashimapspool) { + if (config_hashimapspool != IMAP_ENUM_HASHIMAPSPOOL_OFF) { c = (char) dir_hash_c(name, config_fulldirhash); snprintf(buf, buf_len, "%s%c/%s", FNAME_DOMAINDIR, c, name); } @@ -3508,7 +3509,7 @@ void mailbox_hash_mbox(char *buf, size_t buf_len, buf += strlen(buf); } - if (config_hashimapspool) { + if (config_hashimapspool == IMAP_ENUM_HASHIMAPSPOOL_ON) { idx = strchr(name, '.'); if (idx == NULL) { idx = name; @@ -3518,13 +3519,30 @@ void mailbox_hash_mbox(char *buf, size_t buf_len, c = (char) dir_hash_c(idx, config_fulldirhash); snprintf(buf, buf_len, "/%c/%s", c, name); + + /* change all '.'s to '/' */ + for (p = buf; *p; p++) { + if (*p == '.') *p = '/'; + } + } else if (config_hashimapspool == IMAP_ENUM_HASHIMAPSPOOL_USERID) { + if (idx = mboxname_isusermailbox(name, 0)) { + c = (char) dir_hash_c(idx, config_fulldirhash); + strncpy(itemname, idx, MAX_MAILBOX_PATH); + if (p = strchr(itemname, '.')) *p = '\0'; /* trim to username */ + snprintf(buf, buf_len, "/user/%c/%s/%s", c, itemname, name); + } else { + strncpy(itemname, name, MAX_MAILBOX_PATH); + if (p = strchr(itemname, '.')) *p = '\0'; /* trim to first part */ + snprintf(buf, buf_len, "/%s/%s", itemname, name); + } } else { /* standard mailbox placement */ snprintf(buf, buf_len, "/%s", name); - } - /* change all '.'s to '/' */ - for (p = buf; *p; p++) { - if (*p == '.') *p = '/'; + /* change all '.'s to '/' */ + for (p = buf; *p; p++) { + if (*p == '.') *p = '/'; + } } + } diff --git a/lib/imapoptions b/lib/imapoptions index e7cfec4..70f40b9 100644 --- a/lib/imapoptions +++ b/lib/imapoptions @@ -307,10 +307,14 @@ are listed with ``''. messages (used by the replication engine). The "sha1" method calculates a SHA1 hash of the entire message */ -{ "hashimapspool", 0, SWITCH } +{ "hashimapspool", "off", ENUM("off", "userid", "on") } /* If enabled, the partitions will also be hashed, in addition to the hashing done on configuration directories. This is recommended if - one partition has a very bushy mailbox tree. */ + one partition has a very bushy mailbox tree. +.PP + If set to "userid" then the second item will be stripped out as + the directory path and the other dots won't be converted: + e.g. /b/brong/user.brong.Trash/ */ # Commented out - there's no such thing as "hostname_mechs", but we need # this for the man page diff --git a/lib/libconfig.c b/lib/libconfig.c index fe6a79e..59ec40a 100644 --- a/lib/libconfig.c +++ b/lib/libconfig.c @@ -77,7 +77,7 @@ enum enum_value config_serverinfo; /* on */ const char *config_mupdate_server = NULL;/* NULL */ const char *config_defdomain = NULL; /* NULL */ const char *config_ident = NULL; /* the service name */ -int config_hashimapspool; /* f */ +enum enum_value config_hashimapspool; /* f */ enum enum_value config_virtdomains; /* f */ enum enum_value config_mupdate_config; /* IMAP_ENUM_MUPDATE_CONFIG_STANDARD */ int config_auditlog; @@ -302,7 +302,7 @@ void config_read(const char *alt_config) } /* look up mailbox hashing */ - config_hashimapspool = config_getswitch(IMAPOPT_HASHIMAPSPOOL); + config_hashimapspool = config_getenum(IMAPOPT_HASHIMAPSPOOL); /* are we supporting virtual domains? */ config_virtdomains = config_getenum(IMAPOPT_VIRTDOMAINS); diff --git a/lib/libconfig.h b/lib/libconfig.h index 6a8bb63..7ebae48 100644 --- a/lib/libconfig.h +++ b/lib/libconfig.h @@ -74,7 +74,7 @@ extern enum enum_value config_serverinfo; extern const char *config_mupdate_server; extern const char *config_defdomain; extern const char *config_ident; -extern int config_hashimapspool; +extern enum enum_value config_hashimapspool; extern int config_implicitrights; extern enum enum_value config_virtdomains; extern enum enum_value config_mupdate_config; diff --git a/tools/dohash b/tools/dohash deleted file mode 100755 index b7739c2..0000000 --- a/tools/dohash +++ /dev/null @@ -1,288 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. The name "Carnegie Mellon University" must not be used to -# endorse or promote products derived from this software without -# prior written permission. For permission or any legal -# details, please contact -# Carnegie Mellon University -# Center for Technology Transfer and Enterprise Creation -# 4615 Forbes Avenue -# Suite 302 -# Pittsburgh, PA 15213 -# (412) 268-7393, fax: (412) 268-7395 -# innovation@andrew.cmu.edu -# -# 4. Redistributions of any form whatsoever must retain the following -# acknowledgment: -# "This product includes software developed by Computing Services -# at Carnegie Mellon University (http://www.cmu.edu/computing/)." -# -# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# -# $Id: dohash,v 1.13 2008/03/24 20:25:22 murch Exp $ - -exec perl -x -S $0 ${1+"$@"} # -*-perl-*- -#!perl -w -# script to upgrade from versions of imapd previous to 1.6.2 -# make sure you run it as the cyrus user - -if ($] !~ /^5\..*/) { - # uh-oh. this isn't perl 5. - foreach (split(/:/, $ENV{PATH})) { # try to find "perl5". - exec("$_/perl5", "-x", "-S", $0, @ARGV) if (-x "$_/perl5"); - } - # we failed. bail. - die "Your perl is too old; I need perl 5.\n"; -} - -# load the real script. this is isolated in an 'eval' so perl4 won't -# choke on the perl5-isms. -eval join("\n", ); -if ($@) { die "$@"; } - -__END__ -require 5; - -$| = 1; - -if ("-i" eq $ARGV[0]) { - $interactive = 1; - shift @ARGV; -} -if ("-f" eq $ARGV[0]) { - $force = 1; - shift @ARGV; -} -if ("-h" eq $ARGV[0] || $#ARGV > 0) { - print "usage: dohash [-i] [-f] [imapd.conf]\n"; - print " -i interactive\n"; - print " -f keep going on errors\n"; - exit; -} - -sub ouch { - my $msg = shift; - - if ($force) { - print "fatal error: $msg\n"; - } else { - print "error: $msg\n"; - exit 1; - } -} - -sub read_conf { - my $file = shift; - - open CONF, $file or die "can't open $file"; - while () { - if (/^#/) { - next; - } - if (/\@include:\s+(.*)$/) { - push @configs, $1; - } - if (/^configdirectory:\s+(.*)$/) { - $confdir = $1; - } - if (/^(?:meta)?partition-.*:\s+(.*)$/) { - if (grep /$1/, @parts) { - next; - } - push @parts, $1; - } - if (/^hashimapspool:\s*(1|t|yes|on)/) { - $hashispool = 1; - print "i will also hash partitions.\n"; - } - } - close CONF; -} - -$imapdconf = shift || "/etc/imapd.conf"; - -$yn = "y"; -$hashispool = 0; - -push @configs, $imapdconf; - -while ($conf = shift @configs) { - read_conf($conf); -} - -if (! $confdir) { $confdir = "/var/imap"; } - -if ($interactive) { - print "upgrade $confdir? "; - $yn = ; -} -if ($yn =~ /^y/) { - print "upgrading configuration directory $confdir..."; - chdir $confdir or die "couldn't change to $confdir"; - - # *** user subdirectory; holds subscription files - print "user "; - chdir "user" or die "couldn't change to user subdir"; - foreach $i ("a".."z") { - mkdir ("$i", 0755) or ouch "couldn't create $i"; - } - # any remaining sub's go into 'q' - opendir (USER, "."); - while ($f = readdir USER) { - print; - if ($f =~ /(.).*\.sub/s) { - print; - $h = lc($1); - if (!($h =~ /[a-z]/)) { $h = 'q'; } - rename ($f, "$h/$f") or ouch "couldn't move $f into $h"; - } - } - closedir USER; - chdir ".."; - - # *** quota subdirectory; holds quota files for each quotaroot - print "quota "; - chdir "quota" or die "couldn't change to quota subdir"; - - # first, create directories we know can't conflict with existing files - foreach $i ("a".."z") { - mkdir (".$i", 0755) or ouch "couldn't create .$i"; - } - - # now for each file, move it into the appropriate directory - opendir QUOTA, "."; - while ($mbox = readdir QUOTA) { - if ($mbox =~ /^\./s) { next; } - - if ($mbox =~ /^.*\.(.).*$/s) { - # hash is $1 - $h = lc($1); - if ($h =~ /[a-z]/) { - rename($mbox, ".$h/$mbox") - or ouch "couldn't move $mbox into $h"; - } else { - rename($mbox, ".q/$mbox") - or ouch "couldn't move $mbox into q"; - } - next; - } - - # we should try to hash on the first letter - if ($mbox =~ /(.).*/) { - $h = lc($1); - if ($h =~ /[a-z]/) { - rename($mbox, ".$h/$mbox") - or ouch "couldn't move $mbox into $h"; - } else { - rename($mbox, ".q/$mbox") - or ouch "couldn't move $mbox into q"; - } - next; - } - - print "weird mailbox '$mbox'?\n"; - } - closedir QUOTA; - - # now move each temporary directory to the right place - foreach $i ("a".."z") { - rename (".$i", $i) or ouch "couldn't rename $i into place"; - } - - print "done\n"; -} - -# *** now for each data partition -while ($part = shift @parts) { - if ($interactive) { - print "upgrade $part? "; - $yn = ; - } - if ($yn =~ /^y/) { - print "upgrading data partition $part..."; - chdir $part or die "couldn't chdir to $part"; - - if ($hashispool) { - foreach $i ("a".."z") { - mkdir (".$i", 0755) or ouch "couldn't create .$i"; - } - - opendir PART, "."; - while ($dir = readdir PART) { - if ($dir =~ /^\./s) { next; } - if ($dir eq "lost+found") { next; } - - # process $dir - print "$dir "; - opendir DIR, $dir; - $ismbox = 0; - while ($sub = readdir DIR) { - if ($sub =~ /^\./s) { next; } - # if there's a dot in this, we're a mbox and - # this isn't a child - if ($sub =~ /(.*)\.(.*)/) { $ismbox = 1; next; } - - if ($sub =~ /^(.).*$/s) { - $h = lc($1); - if (!($h =~ /[a-z]/)) { - $h = 'q'; - } - mkdir (".$h/$dir", 0755); # might already be there - rename("$dir/$sub", ".$h/$dir/$sub") or - ouch "couldn't move $dir/$sub into $h"; - } else { - print "weird mailbox '$dir/$sub'?\n"; - } - } - closedir DIR; - # if $ismbox is set, then $dir is a mailbox of it's own right - if ($ismbox && $dir =~ /^(.).*/s) { - $h = lc($1); - if (!($h =~ /[a-z]/)) { - $h = 'q'; - } - mkdir (".$h/$dir", 0755); # might already be there - opendir DIR, $dir; - while ($sub = readdir DIR) { - if ($sub =~ /^\./s) { next; } - rename("$dir/$sub", ".$h/$dir/$sub") or - ouch "couldn't move $dir into $h"; - } - closedir DIR; - } - - rmdir $dir or print "\ncouldn't remove '$dir'??\n"; - } - closedir PART; - - foreach $i ("a".."z") { - rename (".$i", $i) or ouch "couldn't rename .$i to $i"; - } - } - - chdir $part or die "couldn't chdir to $part"; - mkdir "stage.", 0755; - - print "done\n"; - } -} diff --git a/tools/rehash b/tools/rehash index fffbe23..bd14d77 100755 --- a/tools/rehash +++ b/tools/rehash @@ -65,10 +65,14 @@ if ($@) { die "$@"; } __END__ require 5; +use strict; +use warnings; + $| = 1; die "must not run as root" if ($< == 0); +my ($interactive, $force, $verbose, $no_actions); if ("-i" eq $ARGV[0]) { $interactive = 1; shift @ARGV; @@ -77,121 +81,54 @@ if ("-f" eq $ARGV[0]) { $force = 1; shift @ARGV; } +if ("-v" eq $ARGV[0]) { + $verbose = 1; + shift @ARGV; +} +if ("-n" eq $ARGV[0]) { + $no_actions = 1; + shift @ARGV; +} if ("-h" eq $ARGV[0] || @ARGV < 1) { - print "usage: rehash [-i] [-f] none|basic|full [imapd.conf]\n"; + print "usage: rehash [-i] [-f] [-v] [-n] none|basic|full|userid [imapd.conf]\n"; print " -i interactive\n"; print " -f keep going on errors\n"; + print " -v verbose\n"; + print " -n do not actually rename, just show what would be done\n"; exit; } -$MOVE_DOMAIN_CONF = 1; -$MOVE_DOMAIN_SIEVE = 2; -$MOVE_DOMAIN_PART = 3; - -$tonone = 1 if ("none" eq $ARGV[0]); -$tobasic = 1 if ("basic" eq $ARGV[0]); -$tofull = 1 if ("full" eq $ARGV[0]); -unless ($tonone || $tobasic || $tofull) { - print "rehash: one of none/basic/full required\n"; - exit; -} -shift @ARGV; - -my @bdirs = ("a".."z"); -my @fdirs = ("A".."W"); -my $dirs = [@bdirs,@fdirs] if $tonone; - -if ($tobasic) { - $dirs = \@bdirs; - $old = \@fdirs; -} - -if ($tofull) { - $dirs = \@fdirs; - $old = \@bdirs; -} - -sub ouch { - my $msg = shift; - - if ($force) { - print "fatal error: $msg\n"; - } else { - print "error: $msg\n"; - exit 1; - } -} - -sub dir_hash_c { - my $name = shift; - my ($h, $n); - - if ($tofull) { - $n = 0; - foreach my $b (split(/ */, $name)) { - $n = (($n << 3) ^ ($n >> 5)) ^ ord($b); - } - $h = chr(ord('A') + ($n % 23)); - return $h; - } - elsif ($tobasic) { - $h = lc(substr($name, 0, 1)); - if (!($h =~ /[a-z]/)) { $h = 'q'; } - return $h; - } +my $hashtype = shift @ARGV; +my %types = ( + none => "No hashing", + basic => "Basic hashing", + full => "Full hashing", + userid => "Hash by userid", +); + +unless ($hashtype && $types{$hashtype}) { + print "type must be none|basic|full|userid ($hashtype)\n"; + exit 1; } -sub read_conf { - my $file = shift; +my $bdir_re = qr([a-z]); +my $fdir_re = qr([A-W]); - open CONF, $file or die "can't open $file"; - while () { - if (/^#/) { - next; - } - if (/\@include:\s+(.*)$/) { - push @configs, $1; - } - if (/^configdirectory:\s+(.*)$/) { - $confdir = $1; - } - if (/^(?:meta)?partition-.*:\s+(.*)$/) { - if (grep /$1/, @parts) { - next; - } - push @parts, $1; - } - if (/^hashimapspool:\s*(1|t|yes|on)/) { - $hashispool = 1; - print "i will also hash partitions.\n"; - } - if (/^sieveusehomedir:\s+(1|t|yes|on)/) { - $nosievedir = 1; - print "you are storing sieve scripts in user's home directories.\n"; - } - if (/^sievedir:\s+(.*)$/) { - $sievedir = $1; - print "you are using $sievedir as your sieve directory.\n"; - } - if (/^virtdomains:\s+(1|t|yes|on)/) { - $virtdomains = 1; - print "i will deal with virtual domains.\n"; - } - } - close CONF; -} +my $imapdconf = shift || "/etc/imapd.conf"; -$imapdconf = shift || "/etc/imapd.conf"; +my $yn = "y"; +my $sievedir = "/usr/sieve"; +my $nosievedir = 0; +my $hashispool = 0; +my $virtdomains = 0; -$yn = "y"; -$sievedir = "/usr/sieve"; -$nosievedir = 0; -$hashispool = 0; -$virtdomains = 0; +my @configs; +my $confdir; +my @parts; push @configs, $imapdconf; -while ($conf = shift @configs) { +while (my $conf = shift @configs) { read_conf($conf); } @@ -202,36 +139,36 @@ if ($interactive) { $yn = ; } if ($yn =~ /^y/) { - unless (-d $confdir) { print "creating $confdir...\n"; - mkdir $confdir, 0755; + unless (-d $confdir) { print "creating $confdir...\n"; + mkdir $confdir, 0755; } - print "converting configuration directory $confdir..."; + print "converting configuration directory $confdir...\n"; chdir $confdir or die "couldn't change to $confdir"; - foreach $i ("user", "proc", "db", "socket", "log", "msg", "quota") { - unless (-d $i) { - print "creating $i...\n"; - mkdir $i, 0755; - } + foreach my $dir ("user", "proc", "db", "socket", "log", "msg", "quota") { + unless (-d $dir) { + print "creating $dir...\n"; + mkdir $dir, 0755; + } } # *** rehash the domain subdirectory to the new format, # don't worry about internal format yet if($virtdomains) { - print "domain "; - chdir "domain" or die "couldn't change to domain subdir"; - &move_domains($MOVE_DOMAIN_CONF); - chdir ".."; + print "domain:\n"; + chdir "domain" or die "couldn't change to domain subdir"; + &move_domains(); + chdir ".."; } # *** user subdirectory; holds subscription files - print "user "; + print "user:\n"; chdir "user" or die "couldn't change to user subdir"; &move_users; chdir ".."; # *** quota subdirectory; holds quota files for each quotaroot - print "quota "; + print "quota:\n"; chdir "quota" or die "couldn't change to quota subdir"; &move_quotas; print "done\n"; @@ -239,437 +176,656 @@ if ($yn =~ /^y/) { # create the sieve stuff unless ($nosievedir) { - print "converting $sievedir...\n"; + print "converting sieve $sievedir...\n"; - mkdir $sievedir, 0755; + unless (-d $sievedir) { + mkdir $sievedir, 0755; + } if (chdir $sievedir) { &move_sieve; } } # *** now for each data partition -my $i; -my $f; -while ($part = shift @parts) { +while (my $part = shift @parts) { if ($interactive) { - print "upgrade $part? "; - $yn = ; + print "upgrade $part? "; + $yn = ; } if ($yn =~ /^y/) { - unless (-d $part) { - print "creating $part...\n"; - mkdir $part, 0755; - } - print "converting data partition $part..."; - chdir $part or die "couldn't chdir to $part"; - - if ($hashispool) { - &move_part; - - chdir $part or die "couldn't chdir to $part"; - mkdir "stage.", 0755; - } + unless (-d $part) { + print "creating $part...\n"; + mkdir $part, 0755; + } + print "converting data partition $part...\n"; + chdir $part or die "couldn't chdir to $part"; + + &move_part; + + chdir $part or die "couldn't chdir to $part"; + unless (-d "stage.") { + mkdir "stage.", 0755; + } - print "done\n"; + print "done\n"; } } -sub do_subdomain_conf { - if(-d "quota") { - chdir "quota"; - &move_quotas; - chdir ".."; +exit 0; + +sub read_conf { + my $file = shift; + + open CONF, $file or die "can't open $file"; + while () { + if (/^#/) { + next; + } + if (/\@include:\s+(.*)$/) { + push @configs, $1; + } + if (/^configdirectory:\s+(.*)$/) { + $confdir = $1; + } + if (/^(?:meta)?partition-.*:\s+(.*)$/) { + my $part = $1; + if (grep /$part/, @parts) { + next; + } + push @parts, $part; + } + if (/^sieveusehomedir:\s+(1|t|yes|on)/) { + $nosievedir = 1; + print "you are storing sieve scripts in user's home directories.\n"; + } + if (/^sievedir:\s+(.*)$/) { + $sievedir = $1; + print "you are using $sievedir as your sieve directory.\n"; + } + if (/^virtdomains:\s+(1|t|yes|on|userid)/) { + $virtdomains = 1; + print "i will deal with virtual domains.\n"; + } } - if(-d "user") { - chdir "user"; - &move_users; - chdir ".."; + close CONF; +} + +sub ouch { + my $msg = shift; + + if ($force) { + print "fatal error: $msg\n"; + } else { + print "error: $msg\n"; + exit 1; } } -sub move_domains { - my $type_of_move = shift; +sub dir_hash_c { + my $name = shift; + my $is_full = shift; + my ($h, $n); - if(!defined($type_of_move) || !$type_of_move) { - die "move_domains called badly"; + if ($is_full) { + $n = 0; + foreach my $b (split(/ */, $name)) { + $n = (($n << 3) ^ ($n >> 5)) ^ ord($b); + } + $h = chr(ord('A') + ($n % 23)); + return $h; + } + else { + $h = lc(substr($name, 0, 1)); + if (!($h =~ /[a-z]/)) { $h = 'q'; } + return $h; } +} - my $i; - my $s; - my $h; - my $mbox; - - foreach $i (@{$dirs}) { - if ($tonone) { - if (opendir SUB, $i) { - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - chdir "$i/$s"; - &do_subdomain; - chdir "../.."; - rename("$i/$s", "$s") or die "couldn't move $s back!"; - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } - } - else { - unless (-d $i) { - mkdir ("$i", 0755) or ouch "couldn't create $i"; - } - } +# === functions for renaming stuff in conf/ + +sub move_domains { + walk_tree(undef, sub { + my $file = shift; + my ($domain, $type, $value) = find_domain_part($file); + return 0 unless ($domain and $type and $value); + my $to = find_domain_target($domain, $type); + if ($type eq 'user') { + $to .= find_user_target($value); + } + else { + $to .= find_quota_target($value); } - unless ($tonone) { - foreach $i (@{$old}) { - if (opendir SUB, $i) { - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - - chdir "$i/$s"; - - if($type_of_move == $MOVE_DOMAIN_CONF) { - &do_subdomain_conf; - } elsif ($type_of_move == $MOVE_DOMAIN_SIEVE) { - &move_sieve; - } elsif ($type_of_move == $MOVE_DOMAIN_PART) { - &move_part; - } else { - die "bad domain move mode: $type_of_move"; - } - - chdir "../.."; - - $h = dir_hash_c($s); - print "moving $i/$s to $h/$s\n"; - rename("$i/$s", "$h/$s") or ouch "couldn't move $s back!"; - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } - } - opendir (USER, "."); - while ($f = readdir USER) { - if ($f =~ /^\./s) { next; } - - # don't move the hashed directories themselves - my $flag = 0; - foreach $item (@{$dirs}) { - if($item eq $f) { - $flag = 1; - $break; - } - } - next if($flag); - - # hash on name before '.sub' suffix - print "$f\n"; - $h = dir_hash_c($f); - rename ($f, "$h/$f") or ouch "couldn't move $f into $h"; - } - closedir USER; + if ($to ne $file) { + print "$type - $value\@$domain: " if $verbose; + file_rename($file, $to); } + return 1; + }); } sub move_users { - my $i; - my $s; - my $h; - my $f; - my $mbox; - - foreach $i (@{$dirs}) { - if ($tonone) { - if (opendir SUB, $i) { - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - rename("$i/$s", "$s") or die "couldn't move $s back!"; - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } - } else { - unless (-d $i) { - mkdir ("$i", 0755) or ouch "couldn't create $i"; - } - } - } - unless ($tonone) { - foreach $i (@{$old}) { - if (opendir SUB, $i) { - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - # hash on name before '.sub' suffix - if ($s =~ /^(.+)\./) { - $h = dir_hash_c($1); - rename("$i/$s", "$h/$s") or ouch "couldn't move $s back!"; - } - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } - } - opendir (USER, "."); - while ($f = readdir USER) { - if ($f =~ /^\./s) { next; } - # hash on name before '.sub' suffix - if ($f =~ /^(.+)\./) { - print "$f\n"; - $h = dir_hash_c($1); - rename ($f, "$h/$f") or ouch "couldn't move $f into $h"; - } - } - closedir USER; + walk_tree(undef, sub { + my $file = shift; + my $filename = find_user_part($file); + return 0 unless $filename; # not something I can process + my $to = find_user_target($filename); + if ($to ne $file) { + print "user - $filename: " if $verbose; + file_rename($file, $to); } + return 1; + }); } sub move_quotas { - my $i; - my $s; - my $h; - my $mbox; - - # first, create directories we know can't conflict with existing files - foreach $i (@{$dirs}) { - if ($tonone) { - if (-d $i) { - rename ($i, ".$i") or die "couldn't rename $i to .$i"; - opendir SUB, ".$i"; - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - rename(".$i/$s", $s) or die "couldn't move $s back!"; - } - closedir SUB; - rmdir ".$i" or die "couldn't remove .$i"; - } - } - else { - if (-d $i) { - rename ($i, ".$i") or die "couldn't rename $i to .$i"; - } - else { - mkdir (".$i", 0755); - } - } + walk_tree(undef, sub { + my $file = shift; + my $mailbox = find_quota_part($file); + return 0 unless $mailbox; + my $to = find_quota_target($mailbox); + if ($to ne $file) { + print "quota - $mailbox: " if $verbose; + file_rename($file, $to); } + return 1; + }); +} - # now for each file, move it into the appropriate directory - unless ($tonone) { - foreach $i (@{$old}) { - if (opendir SUB, $i) { - while ($s = readdir SUB) { - # hash on name after 'user.' - if ($s =~ /^.+\.(.+)$/) { - $h = dir_hash_c($1); - rename("$i/$s", ".$h/$s") - or ouch "couldn't move $s back!"; - } - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } - } - opendir QUOTA, "."; - while ($mbox = readdir QUOTA) { - if ($mbox =~ /^\./s) { next; } - - # hash on name after 'user.' - if ($mbox =~ /^.*\.(.*)$/) { - $h = dir_hash_c($1); - rename($mbox, ".$h/$mbox") - or ouch "couldn't move $mbox into $h"; - next; - } - - # we should try to hash the entire file - $h = dir_hash_c($mbox); - rename($mbox, ".$h/$mbox") - or ouch "couldn't move $mbox into $h"; - next; - - } - closedir QUOTA; - - # now move each temporary directory to the right place - foreach $i (@{$dirs}) { - rename (".$i", $i) or ouch "couldn't rename $i into place"; - } +sub move_sieve { + walk_tree(undef, sub { + my $dir = shift; + my $username = find_sieve_part($dir); + return 0 unless $username; + my $to = find_sieve_target($username); + if ($to ne $dir) { + print "sieve - $username: " if $verbose; + dir_rename($dir, $to); } + return 1; + }); } -sub move_sieve { - my $i; - my $s; - my $h; - my $mbox; - - foreach $i (@{$dirs}) { - unless ($tonone) { - if (-d $i) { - rename ($i, ".$i") or die "couldn't rename $i to .$i"; - } - else { - mkdir (".$i", 0755); - } - } - else { - rmdir "$i"; - } - } - unless ($tonone) { - foreach $i (@{$old}) { - if (opendir SUB, $i) { - while ($s = readdir SUB) { - unless ($s =~ /^\./) { - $h = dir_hash_c($s); - rename("$i/$s", ".$h/$s") - or ouch "couldn't move $s back!"; - } - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } - } - # now move each temporary directory to the right place - foreach $i (@{$dirs}) { - rename (".$i", $i) or ouch "couldn't rename $i into place"; - } - } - - if($virtdomains && chdir "domain") { - &move_domains($MOVE_DOMAIN_SIEVE); - chdir ".."; - } +# === functions for parsing paths in conf/ + +sub find_domain_part { + my $file = shift; + my ($domain, $type); + if ($file =~ s{^(?:[a-zA-W]/)?([^/]{2,})/(user|quota)/}{}) { + $domain = $1; + $type = $2; + } + else { + return undef; # not a domain path + } + my $value; + if ($type eq 'user') { + $value = find_user_part($file); + } + else { + $value = find_quota_part($file); + } + + return ($domain, $type, $value); +} + +sub find_user_part { + my $file = shift; + + if ($file =~ m{^[a-zA-W]/([^/.]+\.(seen|sub))$}) { + return $1; + } + elsif ($file =~ m{^([^/.]+\.(seen|sub))$}) { + return $1; + } + + return undef; +} + +sub find_quota_part { + my $file = shift; + + if ($file =~ m{^[a-zA-W]/([^/]{2,})$}) { + return $1; + } + + elsif ($file =~ m{^([^/]{2,})$}) { + return $1; + } + + return undef; } +sub find_sieve_part { + my $dir = shift; + + my $append = ''; + if ($dir =~ s{^domain/(?:[a-zA-W]/)?([^/]{2,})/}{}) { + $append = '@' . $1; + } + elsif ($dir =~ m{^domain$} or $dir =~ m{^domain/}) { + return undef; + } + + if ($dir =~ m{^[a-zA-W]/([^/]{2,})$}) { + return $1 . $append; + } + + elsif ($dir =~ m{^([^/]{2,})$}) { + return $1 . $append; + } + + return undef; +} + +# === functions for generating paths in conf/ + +# prependable domain, so it needs a trailing slash, also we're +# chdir into $confdir/domain already +sub find_domain_target { + my $domain = shift; + my $type = shift; + + my $target = shift; + if ($hashtype eq 'none') { + $target = "$domain/"; + } + else { + my $hl = dir_hash_c($domain, $hashtype eq 'full'); + $target = "$hl/$domain/"; + } + + $target .= "$type/"; + + return $target; +} + +sub find_user_target { + my $name = shift; + + my $target = ''; + if ($hashtype eq 'none') { + $target = $name; + } + else { + my $hl = dir_hash_c($name, $hashtype eq 'full'); + $target = "$hl/$name"; + } + + return $target; +} + +sub find_quota_target { + my $name = shift; + + my $target = ''; + if ($hashtype eq 'none') { + $target = $name; + } + else { + my $second = $name; + $second =~ s{^[^.]+\.}{}; # strip to first dot + my $hl = dir_hash_c($second, $hashtype eq 'full'); + $target = "$hl/$name"; + } + + return $target; +} + +sub find_sieve_target { + my $name = shift; + + my $domain; + if ($name =~ s{\@(.*)}{}) { + $domain = $1; + } + + my $target = ''; + if ($hashtype eq 'none') { + if ($domain) { + $target = "domain/$domain/"; + } + $target .= $name; + } + else { + my $is_full = $hashtype eq 'full'; + if ($domain) { + my $dl = dir_hash_c($domain, $is_full); + $target = "domain/$dl/$domain/"; + } + my $nl = dir_hash_c($name, $is_full); + $target .= "$nl/$name"; + } + + return $target; +} + +# === functions for meta/ and data/ + sub move_part { - my $i; - my $s; - my $t; - my $h; - my $dir; - my $sub; - my $ismbox; - - foreach $i (@{$dirs}) { - if ($tonone) { - if (-d $i) { - rename ($i, ".$i") or die "couldn't rename $i to .$i"; - print "$i "; - - opendir SUB, ".$i"; - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - mkdir $s, 0755; # ignore errors as it might already exist - - opendir MV, ".$i/$s"; - while ($t = readdir MV) { - if ($t =~ /^\./s) { next; } - rename (".$i/$s/$t", "$s/$t") - or die "couldn't rename .$i/$s/$t to $s/$t"; - } - closedir MV; - } - closedir SUB; - rmdir ".$i" or die "could not remove .$i"; - } - print "done\n"; + # complete any pending moves + my @unfinished; + if (opendir(DH, ".")) { + while (my $item = readdir(DH)) { + next unless $item =~ m{^\.tmprehash\.(.*)}; + push @unfinished, $1; } - else { - mkdir (".$i", 0755) or ouch "couldn't create .$i"; + closedir(DH); + } + foreach my $foldername (sort { length($a) <=> length($b) } @unfinished) { + my $tmpname = ".tmprehash.$foldername"; + my $to = find_target_part($foldername); + die "can't find destination for $foldername" + unless $to; + dir_rename($tmpname, $to); + } + + walk_tree('', sub { + my $dir = shift; + return 0 unless is_part_base($dir); + my @dirs = ($dir, find_dirs($dir)); + my %rename; + foreach my $dir (sort { length($b) <=> length($a) } @dirs) { + my $foldername = find_foldername_part($dir); + next unless $foldername; + my $to = find_target_part($foldername); + die "can't find destination for $foldername" + unless $to; + next if ($to eq $dir); + my $tmpname = ".tmprehash.$foldername"; + dir_rename($dir, $tmpname); + $rename{$tmpname} = $to; + } + foreach my $tmpname (sort { length($a) <=> length($b) } keys %rename) { + dir_rename($tmpname, $rename{$tmpname}); } + return 1; + }); +} + +# is this the base level of a user or similar grouping? +sub is_part_base { + my $dir = shift; + + # handle domains + if ($dir =~ s{^domain/(?:[a-zA-W]/)?([^/]{2,})/}{}) { + # ok } - - unless ($tonone) { - foreach $i (@{$old}) { - if (opendir SUB, $i) { - while ($dir = readdir SUB) { - if ($dir =~ /^\./s) { next; } - # process $dir - print "$i/$dir "; - opendir DIR, "$i/$dir"; - $ismbox = 0; - while ($sub = readdir DIR) { - if ($sub =~ /^\./s) { next; } - # if there's a dot in this, we're a mbox and - # this isn't a child - if ($sub =~ /(.*)\.(.*)/) { $ismbox = 1; next; } - - print "/$sub "; - $h = dir_hash_c($sub); - mkdir (".$h/$dir", 0755); # might already be there - rename("$i/$dir/$sub", ".$h/$dir/$sub") or - ouch "couldn't move $dir/$sub into $h"; - } - closedir DIR; - # if $ismbox is set, then $dir is a mailbox of it's own right - if ($ismbox) { - $h = dir_hash_c($dir); - mkdir (".$h/$dir", 0755); # might already be there - opendir DIR, "$i/$dir"; - while ($sub = readdir DIR) { - if ($sub =~ /^\./s) { next; } - print "/$sub "; - rename("$i/$dir/$sub", ".$h/$dir/$sub") or - ouch "couldn't move $dir into $h"; - } - closedir DIR; - } - - rmdir "$i/$dir" or print "\ncouldn't remove '$dir'??\n"; - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; - } + elsif ($dir =~ m{^domain$} or $dir =~ m{^domain/}) { + return undef; # domain subdir + } + + # basic or full hash + if ($dir =~ m{^[a-zA-W]/(.+)}) { + my $name = $1; + return undef if $name eq 'user'; # bogus special case + return 1; + } + + # userid user folder + elsif ($dir =~ m{^user/[a-z]/([^/.]+)}) { + return 1; + } + + # userid non-mailbox + elsif ($dir =~ m{^user$} or $dir =~ m{^user/}) { + return undef; + } + + # userid non-user folder, none folder, etc + elsif ($dir =~ m{^([^/.]{2,})}) { + return 1; + } + + return undef; +} + +# Given a current path, figure out what the IMAP folder name is. +sub find_foldername_part { + my $dir = shift; + + # handle domains + my $append = ''; + if ($dir =~ s{^domain/(?:[a-zA-W]/)?([^/]{2,})/}{}) { + $append = '@' . $1; + } + elsif ($dir =~ m{^domain$} or $dir =~ m{^domain/}) { + return undef; # domain subdir + } + + # basic or full hash + if ($dir =~ m{^[a-zA-W]/(.+)}) { + my $name = $1; + return undef if $name eq 'user'; # bogus special case + $name =~ s{/}{.}g; + return $name . $append; + } + + # userid user folder + elsif ($dir =~ m{^user/[a-z]/([^/.]+)/((DELETED\.)?user\.\1.*)}) { + my $name = $2; + return $name . $append; + } + + # userid non-mailbox + elsif ($dir =~ m{^user$} or $dir =~ m{^user/}) { + return undef; + } + + # userid non-user folder + elsif ($dir =~ m{^([^/.]+)/(\1.*)}) { + my $name = $2; + return $name . $append; + } + + # no hash folder + elsif ($dir =~ m{^[^/.]{2,}$} || $dir =~ m{^[^/.]{2,}/}) { + my $name = $dir; + $name =~ s{/}{.}g; + return $name . $append; + } + + return undef; # not an IMAP folder +} + +sub find_target_part { + my $foldername = shift; + + my $name = $foldername; + my $domain; + if ($name =~ s{\@(.*)}{}) { + $domain = $1; + } + my $target = ''; + + if ($hashtype eq 'none') { + if ($domain) { + $target = "domain/$domain/"; } - opendir PART, "."; - while ($dir = readdir PART) { - if ($dir =~ /^\./s) { next; } - if ($dir eq "lost+found") { next; } - if ($dir eq "stage.") { next; } - if ($dir eq "domain") { - if(chdir "domain") { - &move_domains($MOVE_DOMAIN_PART); - chdir ".."; - } - next; - } - - # process $dir - print "$dir "; - opendir DIR, $dir; - $ismbox = 0; - while ($sub = readdir DIR) { - if ($sub =~ /^\./s) { next; } - # if there's a dot in this, we're a mbox and - # this isn't a child - if ($sub =~ /(.*)\.(.*)/) { $ismbox = 1; next; } - - $h = dir_hash_c($sub); - mkdir (".$h/$dir", 0755); # might already be there - rename("$dir/$sub", ".$h/$dir/$sub") or - ouch "couldn't move $dir/$sub into $h"; - } - closedir DIR; - # if $ismbox is set, then $dir is a mailbox of it's own right - if ($ismbox) { - $h = dir_hash_c($dir); - mkdir (".$h/$dir", 0755); # might already be there - opendir DIR, $dir; - while ($sub = readdir DIR) { - if ($sub =~ /^\./s) { next; } - rename("$dir/$sub", ".$h/$dir/$sub") or - ouch "couldn't move $dir into $h"; - } - closedir DIR; - } - - rmdir $dir or print "\ncouldn't remove '$dir'??\n"; + $name =~ s{\.}{/}g; + $target .= "$name"; + } + elsif ($hashtype eq 'basic' or $hashtype eq 'full') { + my $is_full = $hashtype eq 'full'; + if ($domain) { + my $dl = dir_hash_c($domain, $is_full); + $target = "domain/$dl/$domain/"; } - closedir PART; - - foreach $i (@{$dirs}) { - rename (".$i", $i) or ouch "couldn't rename .$i to $i"; + my $second = $name; + $second =~ s{^[^.]+\.}{}; # strip to after first dot + my $ul = dir_hash_c($second, $is_full); + $name =~ s{\.}{/}g; + $target .= "$ul/$name"; + } + elsif ($hashtype eq 'userid') { + if ($domain) { + my $dl = dir_hash_c($domain, 0); + $target = "domain/$dl/$domain/"; + } + if ($name =~ m{^(?:DELETED\.)?user\.}) { + my $second = $name; + $second =~ s{^DELETED\.}{}; # strip leading deleted + $second =~ s{^[^.]+\.}{}; # strip up to first dot + my $ul = dir_hash_c($second, 0); + my $username = $second; + $username =~ s{\..*}{}; # strip from first dot + $target .= "user/$ul/$username/$name"; + } else { + my $base = $name; + $base =~ s{\..*}{}; # strip from first dot + $target .= "$base/$name"; + } + } + return $target; +} + +# === functions for FS manipulation + +sub walk_tree { + my $path = shift; + my $sub = shift; + my $filter = shift; + + my @items; + my $dir = $path || '.'; + if (opendir(DH, $dir)) { + while (my $item = readdir(DH)) { + next if $item =~ m{^\.}; # any hidden file + next if $item =~ m{\.$}; # cyrus spool file + next if ($filter and $item =~ $filter); # anything else to skip + push @items, $item; + } + closedir(DH); + } + foreach my $item (@items) { + my $full = $path ? "$path/$item" : $item; + next if $sub->($full); # handled + walk_tree($full, $sub) if -d $full; + } +} + +sub find_dirs { + my $path = shift; + + my @items; + my $dir = $path || '.'; + if (opendir(DH, $dir)) { + while (my $item = readdir(DH)) { + next if $item =~ m{^\.}; # hidden + next if $item =~ m{\.$}; # cyrus spool file + my $full = $path ? "$path/$item" : $item; + next unless -d $full; + push @items, $full; } + closedir(DH); + } + return (@items, map { find_dirs($_) } @items); +} + +sub file_rename { + my $src = shift; + my $target = shift; + + if ($verbose or $no_actions) { + print "Rename: $src => $target\n"; + } + return if $no_actions; + + unless (-f $src) { + my $dir = `pwd`; + chomp($dir); + die "source doesn't exist $dir/$src $target"; + } + + if (-e $target) { + die "Target already exists $target"; + } + + cyrus_mkdir($target); + + rename($src, $target); +} + +sub dir_rename { + my $src = shift; + my $target = shift; + + if ($verbose or $no_actions) { + print "Rename: $src => $target\n"; + } + return if $no_actions; + + unless (-d $src) { + my $dir = `pwd`; + chomp($dir); + die "source doesn't exist $dir/$src $target"; + } + + cyrus_mkdir($target); + + cyrus_rename($src, $target) || die "Failed to rename $src to $target: $!"; + + # NOTE: potentially bogus with split meta and empty folders +# cyrus_rmdir($src); +} + +sub cyrus_rename { + my $src = shift; + my $target = shift; + my $can_rename = 1; + if (-e $target) { + $can_rename = 0; + } + opendir(DH, $src) || die "Can't read directory $src"; + while (my $item = readdir(DH)) { + next if $item eq '.'; + next if $item eq '..'; + next if -f "$src/$item"; + $can_rename = 0; + } + closedir(DH); + + if ($can_rename) { + return rename($src, $target); + } + else { + mkdir($target) unless -d $target; + unless (-d $target) { + die "Not a directory $target"; + } + opendir(DH, $src) || die "Can't read directory $src"; + while (my $item = readdir(DH)) { + next if $item eq '.'; + next if $item eq '..'; + next unless -f "$src/$item"; + rename("$src/$item", "$target/$item"); + } + closedir(DH); + return rmdir($src); + } +} + +sub cyrus_mkdir { + my $dir = shift; + return if (not $dir or $dir eq '/'); + $dir =~ s{/$}{}; # trailing slash + $dir =~ s{[^/]+$}{}; # back to previous slash + return if (not $dir or $dir eq '/'); + + unless (-d $dir) { + cyrus_mkdir($dir); + mkdir($dir) || die "Failed to mkdir $dir: $!"; + } +} + +sub cyrus_rmdir { + my $dir = shift; + die "Tried to cyrus_rmdir to high" if (not $dir || $dir eq '/'); + $dir =~ s{/$}{}; # trailing slash + $dir =~ s{[^/]+$}{}; # back to previous slash + + return unless -d $dir; + if (rmdir($dir)) { + cyrus_rmdir($dir); } } diff --git a/tools/undohash b/tools/undohash deleted file mode 100755 index 2a4fa95..0000000 --- a/tools/undohash +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/perl -# script to downgrade from cyrus imapd 1.6.2+ to earlier. -# do NOT run this script while imapd's are running -# -# Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. The name "Carnegie Mellon University" must not be used to -# endorse or promote products derived from this software without -# prior written permission. For permission or any legal -# details, please contact -# Carnegie Mellon University -# Center for Technology Transfer and Enterprise Creation -# 4615 Forbes Avenue -# Suite 302 -# Pittsburgh, PA 15213 -# (412) 268-7393, fax: (412) 268-7395 -# innovation@andrew.cmu.edu -# -# 4. Redistributions of any form whatsoever must retain the following -# acknowledgment: -# "This product includes software developed by Computing Services -# at Carnegie Mellon University (http://www.cmu.edu/computing/)." -# -# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# -# $Id: undohash,v 1.10 2008/03/24 20:25:23 murch Exp $ - -require 5; - -$| = 1; - -sub read_conf { - my $file = shift; - - open CONF, $file or die "can't open $file"; - while () { - if (/^#/) { - next; - } - if (/\@include:\s+(.*)$/) { - push @configs, $1; - } - if (/^configdirectory:\s+(.*)$/) { - $confdir = $1; - } - if (/^(?:meta)?partition-.*:\s+(.*)$/) { - if (grep /$1/, @parts) { - next; - } - push @parts, $1; - } - if (/^hashimapspool:\s*(1|t|yes|on)/) { - $hashispool = 1; - print "i will also hash partitions.\n"; - } - } - close CONF; -} - -$imapdconf = shift || "/etc/imapd.conf"; -$hashispool = 0; - -push @configs, $imapdconf; - -while ($conf = shift @configs) { - read_conf($conf); -} - -print "downgrading configuration directory $confdir..."; -chdir $confdir or die "couldn't change to $confdir"; - -# *** user subdirectory; holds subscription files -print "user "; -chdir "user" or die "couldn't change to user subdir"; -foreach $i ("a".."z") { - opendir SUB, $i; - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - rename("$i/$s", "$s") or die "couldn't move $s back!"; - } - closedir SUB; - rmdir "$i" or die "couldn't remove $i"; -} -chdir ".."; - -# *** quota subdirectory; holds quota files for each quotaroot -print "quota "; -chdir "quota" or die "couldn't change to quota subdir"; - -# first, create directories we know can't conflict with existing files -foreach $i ("a".."z") { - rename ($i, ".$i") or die "couldn't rename $i to .$i"; - opendir SUB, ".$i"; - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - rename(".$i/$s", $s) or die "couldn't move $s back!"; - } - closedir SUB; - rmdir ".$i" or die "couldn't remove .$i"; -} -chdir ".."; - -print "done\n"; - -# *** now for each data partition -while ($hashispool && ($part = shift @parts)) { - print "downgrading data partition $part..."; - chdir $part or die "couldn't chdir to $part"; - - foreach $i ("a".."z") { - rename ("$i", ".$i") or die "couldn't rename $i to .$i"; - } - - # process each subdir - foreach $i ("a".."z") { - print "$i "; - - opendir SUB, ".$i"; - while ($s = readdir SUB) { - if ($s =~ /^\./s) { next; } - mkdir $s, 0755; # ignore errors as it might already exist - - opendir MV, ".$i/$s"; - while ($t = readdir MV) { - if ($t =~ /^\./s) { next; } - rename (".$i/$s/$t", "$s/$t") or - die "couldn't rename .$i/$s/$t to $s/$t"; - } - closedir MV; - } - closedir SUB; - rmdir ".$i"; - } - - print "done\n"; -} -- 1.5.6.5