1 /*
2 * mdconvert - Maildir UID scheme converter
3 * Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
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 2 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
19 #include <autodefs.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26 #include <stdarg.h>
27 #include <dirent.h>
28 #include <limits.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <ctype.h>
32
33 #include <db.h>
34
35 #define EXE "mdconvert"
36
37 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
38 # define ATTR_NORETURN __attribute__((noreturn))
39 # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
40 #else
41 # define ATTR_NORETURN
42 # define ATTR_PRINTFLIKE(fmt,var)
43 #endif
44
45 static void ATTR_NORETURN
oob(void)46 oob( void )
47 {
48 fputs( "Fatal: buffer too small. Please report a bug.\n", stderr );
49 abort();
50 }
51
52 static void ATTR_PRINTFLIKE(1, 2)
sys_error(const char * msg,...)53 sys_error( const char *msg, ... )
54 {
55 va_list va;
56 char buf[1024];
57
58 va_start( va, msg );
59 if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf))
60 oob();
61 va_end( va );
62 perror( buf );
63 }
64
65 static int ATTR_PRINTFLIKE(3, 4)
nfsnprintf(char * buf,int blen,const char * fmt,...)66 nfsnprintf( char *buf, int blen, const char *fmt, ... )
67 {
68 int ret;
69 va_list va;
70
71 va_start( va, fmt );
72 if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
73 oob();
74 va_end( va );
75 return ret;
76 }
77
78 static const char *subdirs[] = { "cur", "new" };
79 static struct flock lck;
80 static DBT key, value;
81
82 static int
convert(const char * box,int altmap)83 convert( const char *box, int altmap )
84 {
85 DB *db;
86 DIR *d;
87 struct dirent *e;
88 const char *u, *ru;
89 char *p, *s, *dpath, *spath, *dbpath;
90 int i, n, ret, sfd, dfd, bl, ml, uv[2], uid;
91 struct stat st;
92 char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX];
93 char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX];
94
95 if (stat( box, &st ) || !S_ISDIR(st.st_mode)) {
96 fprintf( stderr, "'%s' is no Maildir mailbox.\n", box );
97 return 1;
98 }
99
100 nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box );
101 nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box );
102 if (altmap)
103 dpath = umpath, spath = uvpath, dbpath = tdpath;
104 else
105 spath = umpath, dpath = uvpath, dbpath = umpath;
106 nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath );
107 if ((sfd = open( spath, O_RDWR )) < 0) {
108 if (errno != ENOENT)
109 sys_error( "Cannot open %s", spath );
110 return 1;
111 }
112 if (fcntl( sfd, F_SETLKW, &lck )) {
113 sys_error( "Cannot lock %s", spath );
114 goto sbork;
115 }
116 if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) {
117 sys_error( "Cannot create %s", tdpath );
118 goto sbork;
119 }
120 if (db_create( &db, NULL, 0 )) {
121 fputs( "Error: db_create() failed\n", stderr );
122 goto tbork;
123 }
124 if ((ret = (db->open)( db, NULL, dbpath, NULL, DB_HASH, altmap ? DB_CREATE|DB_TRUNCATE : 0, 0 ))) {
125 db->err( db, ret, "Error: db->open(%s)", dbpath );
126 dbork:
127 db->close( db, 0 );
128 tbork:
129 unlink( tdpath );
130 close( dfd );
131 sbork:
132 close( sfd );
133 return 1;
134 }
135 key.data = (void *)"UIDVALIDITY";
136 key.size = 11;
137 if (altmap) {
138 if ((n = read( sfd, buf, sizeof(buf) - 1 )) <= 0 ||
139 (buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2))
140 {
141 fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box );
142 goto dbork;
143 }
144 value.data = uv;
145 value.size = sizeof(uv);
146 if ((ret = db->put( db, NULL, &key, &value, 0 ))) {
147 db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box );
148 goto dbork;
149 }
150 } else {
151 if ((ret = db->get( db, NULL, &key, &value, 0 ))) {
152 db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box );
153 goto dbork;
154 }
155 n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] );
156 if (write( dfd, buf, n ) != n) {
157 fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box );
158 goto dbork;
159 }
160 }
161
162 again:
163 for (i = 0; i < 2; i++) {
164 bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] );
165 if (!(d = opendir( buf ))) {
166 sys_error( "Cannot list %s", buf );
167 goto dbork;
168 }
169 while ((e = readdir( d ))) {
170 if (*e->d_name == '.')
171 continue;
172 nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name );
173 memcpy( buf2, buf, bl );
174 p = strstr( e->d_name, ",U=" );
175 if (p)
176 for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++);
177 else
178 u = ru = strchr( e->d_name, ':' );
179 if (u)
180 ml = u - e->d_name;
181 else
182 ru = "", ml = sizeof(buf);
183 if (altmap) {
184 if (!p)
185 continue;
186 key.data = e->d_name;
187 key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name);
188 uid = atoi( p + 3 );
189 value.data = &uid;
190 value.size = sizeof(uid);
191 if ((ret = db->put( db, NULL, &key, &value, 0 ))) {
192 db->err( db, ret, "Error: cannot write UID for '%s'", box );
193 goto ebork;
194 }
195 nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru );
196 } else {
197 s = strpbrk( e->d_name, ",:" );
198 key.data = e->d_name;
199 key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name );
200 if ((ret = db->get( db, NULL, &key, &value, 0 ))) {
201 if (ret != DB_NOTFOUND) {
202 db->err( db, ret, "Error: cannot read UID for '%s'", box );
203 goto ebork;
204 }
205 continue;
206 }
207 uid = *(int *)value.data;
208 nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru );
209 }
210 if (rename( buf, buf2 )) {
211 if (errno == ENOENT) {
212 closedir( d );
213 goto again;
214 }
215 sys_error( "Cannot rename %s to %s", buf, buf2 );
216 ebork:
217 closedir( d );
218 goto dbork;
219 }
220
221 }
222 closedir( d );
223 }
224
225 db->close( db, 0 );
226 close( dfd );
227 if (rename( tdpath, dpath )) {
228 sys_error( "Cannot rename %s to %s", tdpath, dpath );
229 close( sfd );
230 return 1;
231 }
232 if (unlink( spath ))
233 sys_error( "Cannot remove %s", spath );
234 close( sfd );
235 return 0;
236 }
237
238 int
main(int argc,char ** argv)239 main( int argc, char **argv )
240 {
241 int oint, ret, altmap = 0;
242
243 for (oint = 1; oint < argc; oint++) {
244 if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) {
245 puts(
246 "Usage: " EXE " [-a] mailbox...\n"
247 " -a, --alt convert to alternative (DB based) UID scheme\n"
248 " -n, --native convert to native (file name based) UID scheme (default)\n"
249 " -h, --help show this help message\n"
250 " -v, --version display version"
251 );
252 return 0;
253 } else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) {
254 puts( EXE " " VERSION " - Maildir UID scheme converter" );
255 return 0;
256 } else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) {
257 altmap = 1;
258 } else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) {
259 altmap = 0;
260 } else if (!strcmp( argv[oint], "--" )) {
261 oint++;
262 break;
263 } else if (argv[oint][0] == '-') {
264 fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] );
265 return 1;
266 } else
267 break;
268 }
269 if (oint == argc) {
270 fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" );
271 return 1;
272 }
273 #if SEEK_SET != 0
274 lck.l_whence = SEEK_SET;
275 #endif
276 #if F_WRLCK != 0
277 lck.l_type = F_WRLCK;
278 #endif
279 ret = 0;
280 for (; oint < argc; oint++)
281 ret |= convert( argv[oint], altmap );
282 return ret;
283 }
284
285