xref: /plan9-contrib/sys/src/cmd/ip/imap4d/folder.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "imap4d.h"
6 
7 static	int	copyData(int ffd, int tfd, MbLock *ml);
8 static	MbLock	mLock =
9 {
10 	.fd = -1
11 };
12 
13 static char curDir[MboxNameLen];
14 
15 void
16 resetCurDir(void)
17 {
18 	curDir[0] = '\0';
19 }
20 
21 int
22 myChdir(char *dir)
23 {
24 	if(strcmp(dir, curDir) == 0)
25 		return 0;
26 	if(dir[0] != '/' || strlen(dir) > MboxNameLen)
27 		return -1;
28 	strcpy(curDir, dir);
29 	if(chdir(dir) < 0){
30 		werrstr("mychdir failed: %r");
31 		return -1;
32 	}
33 	return 0;
34 }
35 
36 int
37 cdCreate(char *dir, char *file, int mode, ulong perm)
38 {
39 	if(myChdir(dir) < 0)
40 		return -1;
41 	return create(file, mode, perm);
42 }
43 
44 Dir*
45 cdDirstat(char *dir, char *file)
46 {
47 	if(myChdir(dir) < 0)
48 		return nil;
49 	return dirstat(file);
50 }
51 
52 int
53 cdExists(char *dir, char *file)
54 {
55 	Dir *d;
56 
57 	d = cdDirstat(dir, file);
58 	if(d == nil)
59 		return 0;
60 	free(d);
61 	return 1;
62 }
63 
64 int
65 cdDirwstat(char *dir, char *file, Dir *d)
66 {
67 	if(myChdir(dir) < 0)
68 		return -1;
69 	return dirwstat(file, d);
70 }
71 
72 int
73 cdOpen(char *dir, char *file, int mode)
74 {
75 	if(myChdir(dir) < 0)
76 		return -1;
77 	return open(file, mode);
78 }
79 
80 int
81 cdRemove(char *dir, char *file)
82 {
83 	if(myChdir(dir) < 0)
84 		return -1;
85 	return remove(file);
86 }
87 
88 /*
89  * open the one true mail lock file
90  */
91 MbLock*
92 mbLock(void)
93 {
94 	if(mLock.fd >= 0)
95 		bye("mail lock deadlock");
96 	mLock.fd = openLocked(mboxDir, "L.mbox", OREAD);
97 	if(mLock.fd >= 0)
98 		return &mLock;
99 	return nil;
100 }
101 
102 void
103 mbUnlock(MbLock *ml)
104 {
105 	if(ml != &mLock)
106 		bye("bad mail unlock");
107 	if(ml->fd < 0)
108 		bye("mail unlock when not locked");
109 	close(ml->fd);
110 	ml->fd = -1;
111 }
112 
113 void
114 mbLockRefresh(MbLock *ml)
115 {
116 	char buf[1];
117 
118 	seek(ml->fd, 0, 0);
119 	read(ml->fd, buf, 1);
120 }
121 
122 int
123 mbLocked(void)
124 {
125 	return mLock.fd >= 0;
126 }
127 
128 char*
129 impName(char *name)
130 {
131 	char *s;
132 	int n;
133 
134 	if(cistrcmp(name, "inbox") == 0)
135 		name = "mbox";
136 	n = strlen(name) + STRLEN(".imp") + 1;
137 	s = binalloc(&parseBin, n, 0);
138 	if(s == nil)
139 		return nil;
140 	snprint(s, n, "%s.imp", name);
141 	return s;
142 }
143 
144 /*
145  * massage the mailbox name into something valid
146  * eliminates all .', and ..',s, redundatant and trailing /'s.
147  */
148 char *
149 mboxName(char *s)
150 {
151 	char *ss;
152 
153 	ss = mutf7str(s);
154 	if(ss == nil)
155 		return nil;
156 	cleanname(ss);
157 	return ss;
158 }
159 
160 char *
161 strmutf7(char *s)
162 {
163 	char *m;
164 	int n;
165 
166 	n = strlen(s) * MUtf7Max + 1;
167 	m = binalloc(&parseBin, n, 0);
168 	if(m == nil)
169 		return nil;
170 	if(encmutf7(m, n, s) < 0)
171 		return nil;
172 	return m;
173 }
174 
175 char *
176 mutf7str(char *s)
177 {
178 	char *m;
179 	int n;
180 
181 	/*
182 	 * n = strlen(s) * UTFmax / (2.67) + 1
183 	 * UTFMax / 2.67 == 3 / (8/3) == 9 / 8
184 	 */
185 	n = strlen(s);
186 	n = (n * 9 + 7) / 8 + 1;
187 	m = binalloc(&parseBin, n, 0);
188 	if(m == nil)
189 		return nil;
190 	if(decmutf7(m, n, s) < 0)
191 		return nil;
192 	return m;
193 }
194 
195 void
196 splitr(char *s, int c, char **left, char **right)
197 {
198 	char *d;
199 	int n;
200 
201 	n = strlen(s);
202 	d = binalloc(&parseBin, n + 1, 0);
203 	if(d == nil)
204 		parseErr("out of memory");
205 	strcpy(d, s);
206 	s = strrchr(d, c);
207 	if(s != nil){
208 		*left = d;
209 		*s++ = '\0';
210 		*right = s;
211 	}else{
212 		*right = d;
213 		*left = d + n;
214 	}
215 }
216 
217 /*
218  * create the mailbox and all intermediate components
219  * a trailing / implies the new mailbox is a directory;
220  * otherwise, it's a file.
221  *
222  * return with the file open for write, or directory open for read.
223  */
224 int
225 createBox(char *mbox, int dir)
226 {
227 	char *m;
228 	int fd;
229 
230 	fd = -1;
231 	for(m = mbox; *m; m++){
232 		if(*m == '/'){
233 			*m = '\0';
234 			if(access(mbox, AEXIST) < 0){
235 				if(fd >= 0)
236 					close(fd);
237 				fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
238 				if(fd < 0)
239 					return -1;
240 			}
241 			*m = '/';
242 		}
243 	}
244 	if(dir)
245 		fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
246 	else
247 		fd = cdCreate(mboxDir, mbox, OWRITE, 0664);
248 	return fd;
249 }
250 
251 /*
252  * move one mail folder to another
253  * destination mailbox doesn't exist.
254  * the source folder may be a directory or a mailbox,
255  * and may be in the same directory as the destination,
256  * or a completely different directory.
257  */
258 int
259 moveBox(char *from, char *to)
260 {
261 	Dir *d;
262 	char *fd, *fe, *td, *te, *fimp;
263 
264 	splitr(from, '/', &fd, &fe);
265 	splitr(to, '/', &td, &te);
266 
267 	/*
268 	 * in the same directory: try rename
269 	 */
270 	d = cdDirstat(mboxDir, from);
271 	if(d == nil)
272 		return 0;
273 	if(strcmp(fd, td) == 0){
274 		nulldir(d);
275 		d->name = te;
276 		if(cdDirwstat(mboxDir, from, d) >= 0){
277 			fimp = impName(from);
278 			d->name = impName(te);
279 			cdDirwstat(mboxDir, fimp, d);
280 			free(d);
281 			return 1;
282 		}
283 	}
284 
285 	/*
286 	 * directory copy is too hard for now
287 	 */
288 	if(d->mode & DMDIR)
289 		return 0;
290 	free(d);
291 
292 	return copyBox(from, to, 1);
293 }
294 
295 /*
296  * copy the contents of one mailbox to another
297  * either truncates or removes the source box if it succeeds.
298  */
299 int
300 copyBox(char *from, char *to, int doremove)
301 {
302 	MbLock *ml;
303 	char *fimp, *timp;
304 	int ffd, tfd, ok;
305 
306 	if(cistrcmp(from, "inbox") == 0)
307 		from = "mbox";
308 
309 	ml = mbLock();
310 	if(ml == nil)
311 		return 0;
312 	ffd = openLocked(mboxDir, from, OREAD);
313 	if(ffd < 0){
314 		mbUnlock(ml);
315 		return 0;
316 	}
317 	tfd = createBox(to, 0);
318 	if(tfd < 0){
319 		mbUnlock(ml);
320 		close(ffd);
321 		return 0;
322 	}
323 
324 	ok = copyData(ffd, tfd, ml);
325 	close(ffd);
326 	close(tfd);
327 	if(!ok){
328 		mbUnlock(ml);
329 		return 0;
330 	}
331 
332 	fimp = impName(from);
333 	timp = impName(to);
334 	if(fimp != nil && timp != nil){
335 		ffd = cdOpen(mboxDir, fimp, OREAD);
336 		if(ffd >= 0){
337 			tfd = cdCreate(mboxDir, timp, OWRITE, 0664);
338 			if(tfd >= 0){
339 				copyData(ffd, tfd, ml);
340 				close(tfd);
341 			}
342 			close(ffd);
343 		}
344 	}
345 	cdRemove(mboxDir, fimp);
346 	if(doremove)
347 		cdRemove(mboxDir, from);
348 	else
349 		close(cdOpen(mboxDir, from, OWRITE|OTRUNC));
350 	mbUnlock(ml);
351 	return 1;
352 }
353 
354 /*
355  * copies while holding the mail lock,
356  * then tries to copy permissions and group ownership
357  */
358 static int
359 copyData(int ffd, int tfd, MbLock *ml)
360 {
361 	Dir *fd, td;
362 	char buf[BufSize];
363 	int n;
364 
365 	for(;;){
366 		n = read(ffd, buf, BufSize);
367 		if(n <= 0){
368 			if(n < 0)
369 				return 0;
370 			break;
371 		}
372 		if(write(tfd, buf, n) != n)
373 			return 0;
374 		mbLockRefresh(ml);
375 	}
376 	fd = dirfstat(ffd);
377 	if(fd != nil){
378 		nulldir(&td);
379 		td.mode = fd->mode;
380 		if(dirfwstat(tfd, &td) >= 0){
381 			nulldir(&td);
382 			td.gid = fd->gid;
383 			dirfwstat(tfd, &td);
384 		}
385 	}
386 	return 1;
387 }
388