Blob


1 #include "stdinc.h"
2 #include "dat.h"
3 #include "fns.h"
4 #include "error.h"
6 static void fsMetaFlush(void *a);
7 static Snap *snapInit(Fs*);
8 static void snapClose(Snap*);
10 Fs *
11 fsOpen(char *file, VtConn *z, long ncache, int mode)
12 {
13 int fd, m;
14 uchar oscore[VtScoreSize];
15 Block *b, *bs;
16 Disk *disk;
17 Fs *fs;
18 Super super;
19 char e[ERRMAX];
21 switch(mode){
22 default:
23 werrstr(EBadMode);
24 return nil;
25 case OReadOnly:
26 m = OREAD;
27 break;
28 case OReadWrite:
29 m = ORDWR;
30 break;
31 }
32 fd = open(file, m);
33 if(fd < 0){
34 werrstr("open %s: %r", file);
35 return nil;
36 }
38 bwatchInit();
39 disk = diskAlloc(fd);
40 if(disk == nil){
41 werrstr("diskAlloc: %r");
42 close(fd);
43 return nil;
44 }
46 fs = vtmallocz(sizeof(Fs));
47 fs->mode = mode;
48 fs->name = vtstrdup(file);
49 fs->blockSize = diskBlockSize(disk);
50 fs->cache = cacheAlloc(disk, z, ncache, mode);
51 if(mode == OReadWrite && z)
52 fs->arch = archInit(fs->cache, disk, fs, z);
53 fs->z = z;
55 b = cacheLocal(fs->cache, PartSuper, 0, mode);
56 if(b == nil)
57 goto Err;
58 if(!superUnpack(&super, b->data)){
59 blockPut(b);
60 werrstr("bad super block");
61 goto Err;
62 }
63 blockPut(b);
65 fs->ehi = super.epochHigh;
66 fs->elo = super.epochLow;
68 //fprint(2, "%s: fs->ehi %d fs->elo %d active=%d\n", argv0, fs->ehi, fs->elo, super.active);
70 fs->source = sourceRoot(fs, super.active, mode);
71 if(fs->source == nil){
72 /*
73 * Perhaps it failed because the block is copy-on-write.
74 * Do the copy and try again.
75 */
76 rerrstr(e, sizeof e);
77 if(mode == OReadOnly || strcmp(e, EBadRoot) != 0)
78 goto Err;
79 b = cacheLocalData(fs->cache, super.active, BtDir, RootTag,
80 OReadWrite, 0);
81 if(b == nil){
82 werrstr("cacheLocalData: %r");
83 goto Err;
84 }
85 if(b->l.epoch == fs->ehi){
86 blockPut(b);
87 werrstr("bad root source block");
88 goto Err;
89 }
90 b = blockCopy(b, RootTag, fs->ehi, fs->elo);
91 if(b == nil)
92 goto Err;
93 localToGlobal(super.active, oscore);
94 super.active = b->addr;
95 bs = cacheLocal(fs->cache, PartSuper, 0, OReadWrite);
96 if(bs == nil){
97 blockPut(b);
98 werrstr("cacheLocal: %r");
99 goto Err;
101 superPack(&super, bs->data);
102 blockDependency(bs, b, 0, oscore, nil);
103 blockPut(b);
104 blockDirty(bs);
105 blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
106 blockPut(bs);
107 fs->source = sourceRoot(fs, super.active, mode);
108 if(fs->source == nil){
109 werrstr("sourceRoot: %r");
110 goto Err;
114 //fprint(2, "%s: got fs source\n", argv0);
116 rlock(&fs->elk);
117 fs->file = fileRoot(fs->source);
118 fs->source->file = fs->file; /* point back */
119 runlock(&fs->elk);
120 if(fs->file == nil){
121 werrstr("fileRoot: %r");
122 goto Err;
125 //fprint(2, "%s: got file root\n", argv0);
127 if(mode == OReadWrite){
128 fs->metaFlush = periodicAlloc(fsMetaFlush, fs, 1000);
129 fs->snap = snapInit(fs);
131 return fs;
133 Err:
134 fprint(2, "%s: fsOpen error\n", argv0);
135 fsClose(fs);
136 return nil;
139 void
140 fsClose(Fs *fs)
142 rlock(&fs->elk);
143 periodicKill(fs->metaFlush);
144 snapClose(fs->snap);
145 if(fs->file){
146 fileMetaFlush(fs->file, 0);
147 if(!fileDecRef(fs->file))
148 sysfatal("fsClose: files still in use: %r");
150 fs->file = nil;
151 sourceClose(fs->source);
152 cacheFree(fs->cache);
153 if(fs->arch)
154 archFree(fs->arch);
155 vtfree(fs->name);
156 runlock(&fs->elk);
157 memset(fs, ~0, sizeof(Fs));
158 vtfree(fs);
161 int
162 fsRedial(Fs *fs, char *host)
164 if(vtredial(fs->z, host) < 0)
165 return 0;
166 if(vtconnect(fs->z) < 0)
167 return 0;
168 return 1;
171 File *
172 fsGetRoot(Fs *fs)
174 return fileIncRef(fs->file);
177 int
178 fsGetBlockSize(Fs *fs)
180 return fs->blockSize;
183 Block*
184 superGet(Cache *c, Super* super)
186 Block *b;
188 if((b = cacheLocal(c, PartSuper, 0, OReadWrite)) == nil){
189 fprint(2, "%s: superGet: cacheLocal failed: %r\n", argv0);
190 return nil;
192 if(!superUnpack(super, b->data)){
193 fprint(2, "%s: superGet: superUnpack failed: %r\n", argv0);
194 blockPut(b);
195 return nil;
198 return b;
201 void
202 superWrite(Block* b, Super* super, int forceWrite)
204 superPack(super, b->data);
205 blockDirty(b);
206 if(forceWrite){
207 while(!blockWrite(b, Waitlock)){
208 /* this should no longer happen */
209 fprint(2, "%s: could not write super block; "
210 "waiting 10 seconds\n", argv0);
211 sleep(10*1000);
213 while(b->iostate != BioClean && b->iostate != BioDirty){
214 assert(b->iostate == BioWriting);
215 rsleep(&b->ioready);
217 /*
218 * it's okay that b might still be dirty.
219 * that means it got written out but with an old root pointer,
220 * but the other fields went out, and those are the ones
221 * we really care about. (specifically, epochHigh; see fsSnapshot).
222 */
226 /*
227 * Prepare the directory to store a snapshot.
228 * Temporary snapshots go into /snapshot/yyyy/mmdd/hhmm[.#]
229 * Archival snapshots go into /archive/yyyy/mmdd[.#].
231 * TODO This should be rewritten to eliminate most of the duplication.
232 */
233 static File*
234 fileOpenSnapshot(Fs *fs, char *dstpath, int doarchive)
236 int n;
237 char buf[30], *s, *p, *elem;
238 File *dir, *f;
239 Tm now;
241 if(dstpath){
242 if((p = strrchr(dstpath, '/')) != nil){
243 *p++ = '\0';
244 elem = p;
245 p = dstpath;
246 if(*p == '\0')
247 p = "/";
248 }else{
249 p = "/";
250 elem = dstpath;
252 if((dir = fileOpen(fs, p)) == nil)
253 return nil;
254 f = fileCreate(dir, elem, ModeDir|ModeSnapshot|0555, "adm");
255 fileDecRef(dir);
256 return f;
257 }else if(doarchive){
258 /*
259 * a snapshot intended to be archived to venti.
260 */
261 dir = fileOpen(fs, "/archive");
262 if(dir == nil)
263 return nil;
264 now = *localtime(time(0));
266 /* yyyy */
267 snprint(buf, sizeof(buf), "%d", now.year+1900);
268 f = fileWalk(dir, buf);
269 if(f == nil)
270 f = fileCreate(dir, buf, ModeDir|0555, "adm");
271 fileDecRef(dir);
272 if(f == nil)
273 return nil;
274 dir = f;
276 /* mmdd[#] */
277 snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
278 s = buf+strlen(buf);
279 for(n=0;; n++){
280 if(n)
281 seprint(s, buf+sizeof(buf), ".%d", n);
282 f = fileWalk(dir, buf);
283 if(f != nil){
284 fileDecRef(f);
285 continue;
287 f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
288 break;
290 fileDecRef(dir);
291 return f;
292 }else{
293 /*
294 * Just a temporary snapshot
295 * We'll use /snapshot/yyyy/mmdd/hhmm.
296 * There may well be a better naming scheme.
297 * (I'd have used hh:mm but ':' is reserved in Microsoft file systems.)
298 */
299 dir = fileOpen(fs, "/snapshot");
300 if(dir == nil)
301 return nil;
303 now = *localtime(time(0));
305 /* yyyy */
306 snprint(buf, sizeof(buf), "%d", now.year+1900);
307 f = fileWalk(dir, buf);
308 if(f == nil)
309 f = fileCreate(dir, buf, ModeDir|0555, "adm");
310 fileDecRef(dir);
311 if(f == nil)
312 return nil;
313 dir = f;
315 /* mmdd */
316 snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
317 f = fileWalk(dir, buf);
318 if(f == nil)
319 f = fileCreate(dir, buf, ModeDir|0555, "adm");
320 fileDecRef(dir);
321 if(f == nil)
322 return nil;
323 dir = f;
325 /* hhmm[.#] */
326 snprint(buf, sizeof buf, "%02d%02d", now.hour, now.min);
327 s = buf+strlen(buf);
328 for(n=0;; n++){
329 if(n)
330 seprint(s, buf+sizeof(buf), ".%d", n);
331 f = fileWalk(dir, buf);
332 if(f != nil){
333 fileDecRef(f);
334 continue;
336 f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
337 break;
339 fileDecRef(dir);
340 return f;
344 static int
345 fsNeedArch(Fs *fs, uint archMinute)
347 int need;
348 File *f;
349 char buf[100];
350 Tm now;
351 ulong then;
353 then = time(0);
354 now = *localtime(then);
356 /* back up to yesterday if necessary */
357 if(now.hour < archMinute/60
358 || now.hour == archMinute/60 && now.min < archMinute%60)
359 now = *localtime(then-86400);
361 snprint(buf, sizeof buf, "/archive/%d/%02d%02d",
362 now.year+1900, now.mon+1, now.mday);
363 need = 1;
364 rlock(&fs->elk);
365 f = fileOpen(fs, buf);
366 if(f){
367 need = 0;
368 fileDecRef(f);
370 runlock(&fs->elk);
371 return need;
374 int
375 fsEpochLow(Fs *fs, u32int low)
377 Block *bs;
378 Super super;
380 wlock(&fs->elk);
381 if(low > fs->ehi){
382 werrstr("bad low epoch (must be <= %ud)", fs->ehi);
383 wunlock(&fs->elk);
384 return 0;
387 if((bs = superGet(fs->cache, &super)) == nil){
388 wunlock(&fs->elk);
389 return 0;
392 super.epochLow = low;
393 fs->elo = low;
394 superWrite(bs, &super, 1);
395 blockPut(bs);
396 wunlock(&fs->elk);
398 return 1;
401 static int
402 bumpEpoch(Fs *fs, int doarchive)
404 uchar oscore[VtScoreSize];
405 u32int oldaddr;
406 Block *b, *bs;
407 Entry e;
408 Source *r;
409 Super super;
411 /*
412 * Duplicate the root block.
414 * As a hint to flchk, the garbage collector,
415 * and any (human) debuggers, store a pointer
416 * to the old root block in entry 1 of the new root block.
417 */
418 r = fs->source;
419 b = cacheGlobal(fs->cache, r->score, BtDir, RootTag, OReadOnly);
420 if(b == nil)
421 return 0;
423 memset(&e, 0, sizeof e);
424 e.flags = VtEntryActive | VtEntryLocal | _VtEntryDir;
425 memmove(e.score, b->score, VtScoreSize);
426 e.tag = RootTag;
427 e.snap = b->l.epoch;
429 b = blockCopy(b, RootTag, fs->ehi+1, fs->elo);
430 if(b == nil){
431 fprint(2, "%s: bumpEpoch: blockCopy: %r\n", argv0);
432 return 0;
435 if(0) fprint(2, "%s: snapshot root from %d to %d\n", argv0, oldaddr, b->addr);
436 entryPack(&e, b->data, 1);
437 blockDirty(b);
439 /*
440 * Update the superblock with the new root and epoch.
441 */
442 if((bs = superGet(fs->cache, &super)) == nil)
443 return 0;
445 fs->ehi++;
446 memmove(r->score, b->score, VtScoreSize);
447 r->epoch = fs->ehi;
449 super.epochHigh = fs->ehi;
450 oldaddr = super.active;
451 super.active = b->addr;
452 if(doarchive)
453 super.next = oldaddr;
455 /*
456 * Record that the new super.active can't get written out until
457 * the new b gets written out. Until then, use the old value.
458 */
459 localToGlobal(oldaddr, oscore);
460 blockDependency(bs, b, 0, oscore, nil);
461 blockPut(b);
463 /*
464 * We force the super block to disk so that super.epochHigh gets updated.
465 * Otherwise, if we crash and come back, we might incorrectly treat as active
466 * some of the blocks that making up the snapshot we just created.
467 * Basically every block in the active file system and all the blocks in
468 * the recently-created snapshot depend on the super block now.
469 * Rather than record all those dependencies, we just force the block to disk.
471 * Note that blockWrite might actually (will probably) send a slightly outdated
472 * super.active to disk. It will be the address of the most recent root that has
473 * gone to disk.
474 */
475 superWrite(bs, &super, 1);
476 blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
477 blockPut(bs);
479 return 1;
482 int
483 saveQid(Fs *fs)
485 Block *b;
486 Super super;
487 u64int qidMax;
489 if((b = superGet(fs->cache, &super)) == nil)
490 return 0;
491 qidMax = super.qid;
492 blockPut(b);
494 if(!fileSetQidSpace(fs->file, 0, qidMax))
495 return 0;
497 return 1;
500 int
501 fsSnapshot(Fs *fs, char *srcpath, char *dstpath, int doarchive)
503 File *src, *dst;
505 assert(fs->mode == OReadWrite);
507 dst = nil;
509 if(fs->halted){
510 werrstr("file system is halted");
511 return 0;
514 /*
515 * Freeze file system activity.
516 */
517 wlock(&fs->elk);
519 /*
520 * Get the root of the directory we're going to save.
521 */
522 if(srcpath == nil)
523 srcpath = "/active";
524 src = fileOpen(fs, srcpath);
525 if(src == nil)
526 goto Err;
528 /*
529 * It is important that we maintain the invariant that:
530 * if both b and bb are marked as Active with start epoch e
531 * and b points at bb, then no other pointers to bb exist.
533 * When bb is unlinked from b, its close epoch is set to b's epoch.
534 * A block with epoch == close epoch is
535 * treated as free by cacheAllocBlock; this aggressively
536 * reclaims blocks after they have been stored to Venti.
538 * Let's say src->source is block sb, and src->msource is block
539 * mb. Let's also say that block b holds the Entry structures for
540 * both src->source and src->msource (their Entry structures might
541 * be in different blocks, but the argument is the same).
542 * That is, right now we have:
544 * b Active w/ epoch e, holds ptrs to sb and mb.
545 * sb Active w/ epoch e.
546 * mb Active w/ epoch e.
548 * With things as they are now, the invariant requires that
549 * b holds the only pointers to sb and mb. We want to record
550 * pointers to sb and mb in new Entries corresponding to dst,
551 * which breaks the invariant. Thus we need to do something
552 * about b. Specifically, we bump the file system's epoch and
553 * then rewalk the path from the root down to and including b.
554 * This will copy-on-write as we walk, so now the state will be:
556 * b Snap w/ epoch e, holds ptrs to sb and mb.
557 * new-b Active w/ epoch e+1, holds ptrs to sb and mb.
558 * sb Active w/ epoch e.
559 * mb Active w/ epoch e.
561 * In this state, it's perfectly okay to make more pointers to sb and mb.
562 */
563 if(!bumpEpoch(fs, 0) || !fileWalkSources(src))
564 goto Err;
566 /*
567 * Sync to disk. I'm not sure this is necessary, but better safe than sorry.
568 */
569 cacheFlush(fs->cache, 1);
571 /*
572 * Create the directory where we will store the copy of src.
573 */
574 dst = fileOpenSnapshot(fs, dstpath, doarchive);
575 if(dst == nil)
576 goto Err;
578 /*
579 * Actually make the copy by setting dst's source and msource
580 * to be src's.
581 */
582 if(!fileSnapshot(dst, src, fs->ehi-1, doarchive))
583 goto Err;
585 fileDecRef(src);
586 fileDecRef(dst);
587 src = nil;
588 dst = nil;
590 /*
591 * Make another copy of the file system. This one is for the
592 * archiver, so that the file system we archive has the recently
593 * added snapshot both in /active and in /archive/yyyy/mmdd[.#].
594 */
595 if(doarchive){
596 if(!saveQid(fs))
597 goto Err;
598 if(!bumpEpoch(fs, 1))
599 goto Err;
602 wunlock(&fs->elk);
604 /* BUG? can fs->arch fall out from under us here? */
605 if(doarchive && fs->arch)
606 archKick(fs->arch);
608 return 1;
610 Err:
611 fprint(2, "%s: fsSnapshot: %r\n", argv0);
612 if(src)
613 fileDecRef(src);
614 if(dst)
615 fileDecRef(dst);
616 wunlock(&fs->elk);
617 return 0;
620 int
621 fsVac(Fs *fs, char *name, uchar score[VtScoreSize])
623 int r;
624 DirEntry de;
625 Entry e, ee;
626 File *f;
628 rlock(&fs->elk);
629 f = fileOpen(fs, name);
630 if(f == nil){
631 runlock(&fs->elk);
632 return 0;
635 if(!fileGetSources(f, &e, &ee) || !fileGetDir(f, &de)){
636 fileDecRef(f);
637 runlock(&fs->elk);
638 return 0;
640 fileDecRef(f);
642 r = mkVac(fs->z, fs->blockSize, &e, &ee, &de, score);
643 runlock(&fs->elk);
644 return r;
647 static int
648 vtWriteBlock(VtConn *z, uchar *buf, uint n, uint type, uchar score[VtScoreSize])
650 if(vtwrite(z, score, type, buf, n) < 0)
651 return 0;
652 if(vtsha1check(score, buf, n) < 0)
653 return 0;
654 return 1;
657 int
658 mkVac(VtConn *z, uint blockSize, Entry *pe, Entry *pee, DirEntry *pde, uchar score[VtScoreSize])
660 uchar buf[8192];
661 int i;
662 uchar *p;
663 uint n;
664 DirEntry de;
665 Entry e, ee, eee;
666 MetaBlock mb;
667 MetaEntry me;
668 VtRoot root;
670 e = *pe;
671 ee = *pee;
672 de = *pde;
674 if(globalToLocal(e.score) != NilBlock
675 || (ee.flags&VtEntryActive && globalToLocal(ee.score) != NilBlock)){
676 werrstr("can only vac paths already stored on venti");
677 return 0;
680 /*
681 * Build metadata source for root.
682 */
683 n = deSize(&de);
684 if(n+MetaHeaderSize+MetaIndexSize > sizeof buf){
685 werrstr("DirEntry too big");
686 return 0;
688 memset(buf, 0, sizeof buf);
689 mbInit(&mb, buf, n+MetaHeaderSize+MetaIndexSize, 1);
690 p = mbAlloc(&mb, n);
691 if(p == nil)
692 abort();
693 mbSearch(&mb, de.elem, &i, &me);
694 assert(me.p == nil);
695 me.p = p;
696 me.size = n;
697 dePack(&de, &me);
698 mbInsert(&mb, i, &me);
699 mbPack(&mb);
701 eee.size = n+MetaHeaderSize+MetaIndexSize;
702 if(!vtWriteBlock(z, buf, eee.size, VtDataType, eee.score))
703 return 0;
704 eee.psize = 8192;
705 eee.dsize = 8192;
706 eee.depth = 0;
707 eee.flags = VtEntryActive;
709 /*
710 * Build root source with three entries in it.
711 */
712 entryPack(&e, buf, 0);
713 entryPack(&ee, buf, 1);
714 entryPack(&eee, buf, 2);
716 n = VtEntrySize*3;
717 memset(&root, 0, sizeof root);
718 if(!vtWriteBlock(z, buf, n, VtDirType, root.score))
719 return 0;
721 /*
722 * Save root.
723 */
724 strecpy(root.type, root.type+sizeof root.type, "vac");
725 strecpy(root.name, root.name+sizeof root.name, de.elem);
726 root.blocksize = blockSize;
727 vtrootpack(&root, buf);
728 if(!vtWriteBlock(z, buf, VtRootSize, VtRootType, score))
729 return 0;
731 return 1;
734 int
735 fsSync(Fs *fs)
737 wlock(&fs->elk);
738 fileMetaFlush(fs->file, 1);
739 cacheFlush(fs->cache, 1);
740 wunlock(&fs->elk);
741 return 1;
744 int
745 fsHalt(Fs *fs)
747 wlock(&fs->elk);
748 fs->halted = 1;
749 fileMetaFlush(fs->file, 1);
750 cacheFlush(fs->cache, 1);
751 return 1;
754 int
755 fsUnhalt(Fs *fs)
757 if(!fs->halted)
758 return 0;
759 fs->halted = 0;
760 wunlock(&fs->elk);
761 return 1;
764 int
765 fsNextQid(Fs *fs, u64int *qid)
767 Block *b;
768 Super super;
770 if((b = superGet(fs->cache, &super)) == nil)
771 return 0;
773 *qid = super.qid++;
775 /*
776 * It's okay if the super block doesn't go to disk immediately,
777 * since fileMetaAlloc will record a dependency between the
778 * block holding this qid and the super block. See file.c:/^fileMetaAlloc.
779 */
780 superWrite(b, &super, 0);
781 blockPut(b);
782 return 1;
785 static void
786 fsMetaFlush(void *a)
788 int rv;
789 Fs *fs = a;
791 rlock(&fs->elk);
792 rv = fileMetaFlush(fs->file, 1);
793 runlock(&fs->elk);
794 if(rv > 0)
795 cacheFlush(fs->cache, 0);
798 static int
799 fsEsearch1(File *f, char *path, u32int savetime, u32int *plo)
801 int n, r;
802 DirEntry de;
803 DirEntryEnum *dee;
804 File *ff;
805 Entry e, ee;
806 char *t;
808 dee = deeOpen(f);
809 if(dee == nil)
810 return 0;
812 n = 0;
813 for(;;){
814 r = deeRead(dee, &de);
815 if(r <= 0)
816 break;
817 if(de.mode & ModeSnapshot){
818 if((ff = fileWalk(f, de.elem)) != nil){
819 if(fileGetSources(ff, &e, &ee))
820 if(de.mtime >= savetime && e.snap != 0)
821 if(e.snap < *plo)
822 *plo = e.snap;
823 fileDecRef(ff);
826 else if(de.mode & ModeDir){
827 if((ff = fileWalk(f, de.elem)) != nil){
828 t = smprint("%s/%s", path, de.elem);
829 n += fsEsearch1(ff, t, savetime, plo);
830 vtfree(t);
831 fileDecRef(ff);
834 deCleanup(&de);
835 if(r < 0)
836 break;
838 deeClose(dee);
840 return n;
843 static int
844 fsEsearch(Fs *fs, char *path, u32int savetime, u32int *plo)
846 int n;
847 File *f;
848 DirEntry de;
850 f = fileOpen(fs, path);
851 if(f == nil)
852 return 0;
853 if(!fileGetDir(f, &de)){
854 fileDecRef(f);
855 return 0;
857 if((de.mode & ModeDir) == 0){
858 fileDecRef(f);
859 deCleanup(&de);
860 return 0;
862 deCleanup(&de);
863 n = fsEsearch1(f, path, savetime, plo);
864 fileDecRef(f);
865 return n;
868 void
869 fsSnapshotCleanup(Fs *fs, u32int age)
871 u32int lo;
873 /*
874 * Find the best low epoch we can use,
875 * given that we need to save all the unventied archives
876 * and all the snapshots younger than age.
877 */
878 rlock(&fs->elk);
879 lo = fs->ehi;
880 fsEsearch(fs, "/archive", 0, &lo);
881 fsEsearch(fs, "/snapshot", time(0)-age*60, &lo);
882 runlock(&fs->elk);
884 fsEpochLow(fs, lo);
885 fsSnapshotRemove(fs);
888 /* remove all snapshots that have expired */
889 /* return number of directory entries remaining */
890 static int
891 fsRsearch1(File *f, char *s)
893 int n, r;
894 DirEntry de;
895 DirEntryEnum *dee;
896 File *ff;
897 char *t, e[ERRMAX];
899 dee = deeOpen(f);
900 if(dee == nil)
901 return 0;
903 n = 0;
904 for(;;){
905 r = deeRead(dee, &de);
906 if(r <= 0)
907 break;
908 n++;
909 if(de.mode & ModeSnapshot){
910 rerrstr(e, sizeof e);
911 if((ff = fileWalk(f, de.elem)) != nil)
912 fileDecRef(ff);
913 else if(strcmp(e, ESnapOld) == 0){
914 if(fileClri(f, de.elem, "adm"))
915 n--;
918 else if(de.mode & ModeDir){
919 if((ff = fileWalk(f, de.elem)) != nil){
920 t = smprint("%s/%s", s, de.elem);
921 if(fsRsearch1(ff, t) == 0)
922 if(fileRemove(ff, "adm"))
923 n--;
924 vtfree(t);
925 fileDecRef(ff);
928 deCleanup(&de);
929 if(r < 0)
930 break;
932 deeClose(dee);
934 return n;
937 static int
938 fsRsearch(Fs *fs, char *path)
940 File *f;
941 DirEntry de;
943 f = fileOpen(fs, path);
944 if(f == nil)
945 return 0;
946 if(!fileGetDir(f, &de)){
947 fileDecRef(f);
948 return 0;
950 if((de.mode & ModeDir) == 0){
951 fileDecRef(f);
952 deCleanup(&de);
953 return 0;
955 deCleanup(&de);
956 fsRsearch1(f, path);
957 fileDecRef(f);
958 return 1;
961 void
962 fsSnapshotRemove(Fs *fs)
964 rlock(&fs->elk);
965 fsRsearch(fs, "/snapshot");
966 runlock(&fs->elk);
969 struct Snap
971 Fs *fs;
972 Periodic*tick;
973 QLock lk;
974 uint snapMinutes;
975 uint archMinute;
976 uint snapLife;
977 u32int lastSnap;
978 u32int lastArch;
979 u32int lastCleanup;
980 uint ignore;
981 };
983 static void
984 snapEvent(void *v)
986 Snap *s;
987 u32int now, min;
988 Tm tm;
989 int need;
990 u32int snaplife;
992 s = v;
994 now = time(0)/60;
995 qlock(&s->lk);
997 /*
998 * Snapshots happen every snapMinutes minutes.
999 * If we miss a snapshot (for example, because we
1000 * were down), we wait for the next one.
1002 if(s->snapMinutes != ~0 && s->snapMinutes != 0
1003 && now%s->snapMinutes==0 && now != s->lastSnap){
1004 if(!fsSnapshot(s->fs, nil, nil, 0))
1005 fprint(2, "%s: fsSnapshot snap: %r\n", argv0);
1006 s->lastSnap = now;
1010 * Archival snapshots happen at archMinute.
1011 * If we miss an archive (for example, because we
1012 * were down), we do it as soon as possible.
1014 tm = *localtime(now*60);
1015 min = tm.hour*60+tm.min;
1016 if(s->archMinute != ~0){
1017 need = 0;
1018 if(min == s->archMinute && now != s->lastArch)
1019 need = 1;
1020 if(s->lastArch == 0){
1021 s->lastArch = 1;
1022 if(fsNeedArch(s->fs, s->archMinute))
1023 need = 1;
1025 if(need){
1026 fsSnapshot(s->fs, nil, nil, 1);
1027 s->lastArch = now;
1032 * Snapshot cleanup happens every snaplife or every day.
1034 snaplife = s->snapLife;
1035 if(snaplife == ~0)
1036 snaplife = 24*60;
1037 if(s->lastCleanup+snaplife < now){
1038 fsSnapshotCleanup(s->fs, s->snapLife);
1039 s->lastCleanup = now;
1041 qunlock(&s->lk);
1044 static Snap*
1045 snapInit(Fs *fs)
1047 Snap *s;
1049 s = vtmallocz(sizeof(Snap));
1050 s->fs = fs;
1051 s->tick = periodicAlloc(snapEvent, s, 10*1000);
1052 s->snapMinutes = -1;
1053 s->archMinute = -1;
1054 s->snapLife = -1;
1055 s->ignore = 5*2; /* wait five minutes for clock to stabilize */
1056 return s;
1059 void
1060 snapGetTimes(Snap *s, u32int *arch, u32int *snap, u32int *snaplen)
1062 if(s == nil){
1063 *snap = -1;
1064 *arch = -1;
1065 *snaplen = -1;
1066 return;
1069 qlock(&s->lk);
1070 *snap = s->snapMinutes;
1071 *arch = s->archMinute;
1072 *snaplen = s->snapLife;
1073 qunlock(&s->lk);
1076 void
1077 snapSetTimes(Snap *s, u32int arch, u32int snap, u32int snaplen)
1079 if(s == nil)
1080 return;
1082 qlock(&s->lk);
1083 s->snapMinutes = snap;
1084 s->archMinute = arch;
1085 s->snapLife = snaplen;
1086 qunlock(&s->lk);
1089 static void
1090 snapClose(Snap *s)
1092 if(s == nil)
1093 return;
1095 periodicKill(s->tick);
1096 vtfree(s);