Update copyright dates in source files.
[cumulus.git] / scandir.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2006-2008  Michael Vrable
3  *
4  * Main entry point for LBS.  Contains logic for traversing the filesystem and
5  * constructing a backup.
6  */
7
8 #include <dirent.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <getopt.h>
12 #include <grp.h>
13 #include <pwd.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <sys/stat.h>
18 #include <sys/sysmacros.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <unistd.h>
22
23 #include <algorithm>
24 #include <fstream>
25 #include <iostream>
26 #include <list>
27 #include <set>
28 #include <sstream>
29 #include <string>
30 #include <vector>
31
32 #include "localdb.h"
33 #include "metadata.h"
34 #include "remote.h"
35 #include "store.h"
36 #include "sha1.h"
37 #include "util.h"
38
39 using std::list;
40 using std::string;
41 using std::vector;
42 using std::ostream;
43
44 /* Version information.  This will be filled in by the Makefile. */
45 #ifndef LBS_VERSION
46 #define LBS_VERSION Unknown
47 #endif
48 #define LBS_STRINGIFY(s) LBS_STRINGIFY2(s)
49 #define LBS_STRINGIFY2(s) #s
50 static const char lbs_version[] = LBS_STRINGIFY(LBS_VERSION);
51
52 static RemoteStore *remote = NULL;
53 static TarSegmentStore *tss = NULL;
54 static MetadataWriter *metawriter = NULL;
55
56 /* Buffer for holding a single block of data read from a file. */
57 static const size_t LBS_BLOCK_SIZE = 1024 * 1024;
58 static char *block_buf;
59
60 /* Local database, which tracks objects written in this and previous
61  * invocations to help in creating incremental snapshots. */
62 LocalDb *db;
63
64 /* Keep track of all segments which are needed to reconstruct the snapshot. */
65 std::set<string> segment_list;
66
67 /* Snapshot intent: 1=daily, 7=weekly, etc.  This is not used directly, but is
68  * stored in the local database and can help guide segment cleaning and
69  * snapshot expiration policies. */
70 double snapshot_intent = 1.0;
71
72 /* Selection of files to include/exclude in the snapshot. */
73 std::list<string> includes;         // Paths in which files should be saved
74 std::list<string> excludes;         // Paths which will not be saved
75 std::list<string> searches;         // Directories we don't want to save, but
76                                     //   do want to descend searching for data
77                                     //   in included paths
78
79 bool relative_paths = true;
80
81 /* Ensure that the given segment is listed as a dependency of the current
82  * snapshot. */
83 void add_segment(const string& segment)
84 {
85     segment_list.insert(segment);
86 }
87
88 /* Read data from a file descriptor and return the amount of data read.  A
89  * short read (less than the requested size) will only occur if end-of-file is
90  * hit. */
91 ssize_t file_read(int fd, char *buf, size_t maxlen)
92 {
93     size_t bytes_read = 0;
94
95     while (true) {
96         ssize_t res = read(fd, buf, maxlen);
97         if (res < 0) {
98             if (errno == EINTR)
99                 continue;
100             fprintf(stderr, "error reading file: %m\n");
101             return -1;
102         } else if (res == 0) {
103             break;
104         } else {
105             bytes_read += res;
106             buf += res;
107             maxlen -= res;
108         }
109     }
110
111     return bytes_read;
112 }
113
114 /* Read the contents of a file (specified by an open file descriptor) and copy
115  * the data to the store.  Returns the size of the file (number of bytes
116  * dumped), or -1 on error. */
117 int64_t dumpfile(int fd, dictionary &file_info, const string &path,
118                  struct stat& stat_buf)
119 {
120     int64_t size = 0;
121     list<string> object_list;
122     const char *status = NULL;          /* Status indicator printed out */
123
124     /* Look up this file in the old stat cache, if we can.  If the stat
125      * information indicates that the file has not changed, do not bother
126      * re-reading the entire contents. */
127     bool cached = false;
128
129     if (metawriter->find(path) && metawriter->is_unchanged(&stat_buf)) {
130         cached = true;
131         list<ObjectReference> blocks = metawriter->get_blocks();
132
133         /* If any of the blocks in the object have been expired, then we should
134          * fall back to fully reading in the file. */
135         for (list<ObjectReference>::const_iterator i = blocks.begin();
136              i != blocks.end(); ++i) {
137             const ObjectReference &ref = *i;
138             if (!db->IsAvailable(ref)) {
139                 cached = false;
140                 status = "repack";
141                 break;
142             }
143         }
144
145         /* If everything looks okay, use the cached information */
146         if (cached) {
147             file_info["checksum"] = metawriter->get_checksum();
148             for (list<ObjectReference>::const_iterator i = blocks.begin();
149                  i != blocks.end(); ++i) {
150                 const ObjectReference &ref = *i;
151                 object_list.push_back(ref.to_string());
152                 if (ref.is_normal())
153                     add_segment(ref.get_segment());
154                 db->UseObject(ref);
155             }
156             size = stat_buf.st_size;
157         }
158     }
159
160     /* If the file is new or changed, we must read in the contents a block at a
161      * time. */
162     if (!cached) {
163         SHA1Checksum hash;
164         while (true) {
165             ssize_t bytes = file_read(fd, block_buf, LBS_BLOCK_SIZE);
166             if (bytes == 0)
167                 break;
168             if (bytes < 0) {
169                 fprintf(stderr, "Backup contents for %s may be incorrect\n",
170                         path.c_str());
171                 break;
172             }
173
174             hash.process(block_buf, bytes);
175
176             // Sparse file processing: if we read a block of all zeroes, encode
177             // that explicitly.
178             bool all_zero = true;
179             for (int i = 0; i < bytes; i++) {
180                 if (block_buf[i] != 0) {
181                     all_zero = false;
182                     break;
183                 }
184             }
185
186             // Either find a copy of this block in an already-existing segment,
187             // or index it so it can be re-used in the future
188             double block_age = 0.0;
189             ObjectReference ref;
190
191             SHA1Checksum block_hash;
192             block_hash.process(block_buf, bytes);
193             string block_csum = block_hash.checksum_str();
194
195             if (all_zero) {
196                 ref = ObjectReference(ObjectReference::REF_ZERO);
197                 ref.set_range(0, bytes);
198             } else {
199                 ref = db->FindObject(block_csum, bytes);
200             }
201
202             // Store a copy of the object if one does not yet exist
203             if (ref.is_null()) {
204                 LbsObject *o = new LbsObject;
205                 int object_group;
206
207                 /* We might still have seen this checksum before, if the object
208                  * was stored at some time in the past, but we have decided to
209                  * clean the segment the object was originally stored in
210                  * (FindObject will not return such objects).  When rewriting
211                  * the object contents, put it in a separate group, so that old
212                  * objects get grouped together.  The hope is that these old
213                  * objects will continue to be used in the future, and we
214                  * obtain segments which will continue to be well-utilized.
215                  * Additionally, keep track of the age of the data by looking
216                  * up the age of the block which was expired and using that
217                  * instead of the current time. */
218                 if (db->IsOldObject(block_csum, bytes,
219                                     &block_age, &object_group)) {
220                     if (object_group == 0) {
221                         o->set_group("data");
222                     } else {
223                         char group[32];
224                         sprintf(group, "compacted-%d", object_group);
225                         o->set_group(group);
226                     }
227                     if (status == NULL)
228                         status = "partial";
229                 } else {
230                     o->set_group("data");
231                     status = "new";
232                 }
233
234                 o->set_data(block_buf, bytes);
235                 o->write(tss);
236                 ref = o->get_ref();
237                 db->StoreObject(ref, block_csum, bytes, block_age);
238                 ref.set_range(0, bytes);
239                 delete o;
240             }
241
242             object_list.push_back(ref.to_string());
243             if (ref.is_normal())
244                 add_segment(ref.get_segment());
245             db->UseObject(ref);
246             size += bytes;
247
248             if (status == NULL)
249                 status = "old";
250         }
251
252         file_info["checksum"] = hash.checksum_str();
253     }
254
255     if (status != NULL)
256         printf("    [%s]\n", status);
257
258     string blocklist = "";
259     for (list<string>::iterator i = object_list.begin();
260          i != object_list.end(); ++i) {
261         if (i != object_list.begin())
262             blocklist += "\n    ";
263         blocklist += *i;
264     }
265     file_info["data"] = blocklist;
266
267     return size;
268 }
269
270 /* Dump a specified filesystem object (file, directory, etc.) based on its
271  * inode information.  If the object is a regular file, an open filehandle is
272  * provided. */
273 void dump_inode(const string& path,         // Path within snapshot
274                 const string& fullpath,     // Path to object in filesystem
275                 struct stat& stat_buf,      // Results of stat() call
276                 int fd)                     // Open filehandle if regular file
277 {
278     char *buf;
279     dictionary file_info;
280     int64_t file_size;
281     ssize_t len;
282
283     printf("%s\n", path.c_str());
284     metawriter->find(path);
285
286     file_info["name"] = uri_encode(path);
287     file_info["mode"] = encode_int(stat_buf.st_mode & 07777, 8);
288     file_info["ctime"] = encode_int(stat_buf.st_ctime);
289     file_info["mtime"] = encode_int(stat_buf.st_mtime);
290     file_info["user"] = encode_int(stat_buf.st_uid);
291     file_info["group"] = encode_int(stat_buf.st_gid);
292
293     time_t now = time(NULL);
294     if (now - stat_buf.st_ctime < 30 || now - stat_buf.st_mtime < 30)
295         if ((stat_buf.st_mode & S_IFMT) != S_IFDIR)
296             file_info["volatile"] = "1";
297
298     struct passwd *pwd = getpwuid(stat_buf.st_uid);
299     if (pwd != NULL) {
300         file_info["user"] += " (" + uri_encode(pwd->pw_name) + ")";
301     }
302
303     struct group *grp = getgrgid(stat_buf.st_gid);
304     if (pwd != NULL) {
305         file_info["group"] += " (" + uri_encode(grp->gr_name) + ")";
306     }
307
308     if (stat_buf.st_nlink > 1 && (stat_buf.st_mode & S_IFMT) != S_IFDIR) {
309         file_info["links"] = encode_int(stat_buf.st_nlink);
310     }
311
312     file_info["inode"] = encode_int(major(stat_buf.st_dev))
313         + "/" + encode_int(minor(stat_buf.st_dev))
314         + "/" + encode_int(stat_buf.st_ino);
315
316     char inode_type;
317
318     switch (stat_buf.st_mode & S_IFMT) {
319     case S_IFIFO:
320         inode_type = 'p';
321         break;
322     case S_IFSOCK:
323         inode_type = 's';
324         break;
325     case S_IFBLK:
326     case S_IFCHR:
327         inode_type = ((stat_buf.st_mode & S_IFMT) == S_IFBLK) ? 'b' : 'c';
328         file_info["device"] = encode_int(major(stat_buf.st_rdev))
329             + "/" + encode_int(minor(stat_buf.st_rdev));
330         break;
331     case S_IFLNK:
332         inode_type = 'l';
333
334         /* Use the reported file size to allocate a buffer large enough to read
335          * the symlink.  Allocate slightly more space, so that we ask for more
336          * bytes than we expect and so check for truncation. */
337         buf = new char[stat_buf.st_size + 2];
338         len = readlink(fullpath.c_str(), buf, stat_buf.st_size + 1);
339         if (len < 0) {
340             fprintf(stderr, "error reading symlink: %m\n");
341         } else if (len <= stat_buf.st_size) {
342             buf[len] = '\0';
343             file_info["target"] = uri_encode(buf);
344         } else if (len > stat_buf.st_size) {
345             fprintf(stderr, "error reading symlink: name truncated\n");
346         }
347
348         delete[] buf;
349         break;
350     case S_IFREG:
351         inode_type = 'f';
352
353         file_size = dumpfile(fd, file_info, path, stat_buf);
354         file_info["size"] = encode_int(file_size);
355
356         if (file_size < 0)
357             return;             // error occurred; do not dump file
358
359         if (file_size != stat_buf.st_size) {
360             fprintf(stderr, "Warning: Size of %s changed during reading\n",
361                     path.c_str());
362             file_info["volatile"] = "1";
363         }
364
365         break;
366     case S_IFDIR:
367         inode_type = 'd';
368         break;
369
370     default:
371         fprintf(stderr, "Unknown inode type: mode=%x\n", stat_buf.st_mode);
372         return;
373     }
374
375     file_info["type"] = string(1, inode_type);
376
377     metawriter->add(file_info);
378 }
379
380 void scanfile(const string& path, bool include)
381 {
382     int fd = -1;
383     long flags;
384     struct stat stat_buf;
385     list<string> refs;
386
387     string true_path;
388     if (relative_paths)
389         true_path = path;
390     else
391         true_path = "/" + path;
392
393     // Set to true if we should scan through the contents of this directory,
394     // but not actually back files up
395     bool scan_only = false;
396
397     // Check this file against the include/exclude list to see if it should be
398     // considered
399     for (list<string>::iterator i = includes.begin();
400          i != includes.end(); ++i) {
401         if (path == *i) {
402             printf("Including %s\n", path.c_str());
403             include = true;
404         }
405     }
406
407     for (list<string>::iterator i = excludes.begin();
408          i != excludes.end(); ++i) {
409         if (path == *i) {
410             printf("Excluding %s\n", path.c_str());
411             include = false;
412         }
413     }
414
415     for (list<string>::iterator i = searches.begin();
416          i != searches.end(); ++i) {
417         if (path == *i) {
418             printf("Scanning %s\n", path.c_str());
419             scan_only = true;
420         }
421     }
422
423     if (!include && !scan_only)
424         return;
425
426     if (lstat(true_path.c_str(), &stat_buf) < 0) {
427         fprintf(stderr, "lstat(%s): %m\n", path.c_str());
428         return;
429     }
430
431     if ((stat_buf.st_mode & S_IFMT) == S_IFREG) {
432         /* Be paranoid when opening the file.  We have no guarantee that the
433          * file was not replaced between the stat() call above and the open()
434          * call below, so we might not even be opening a regular file.  We
435          * supply flags to open to to guard against various conditions before
436          * we can perform an lstat to check that the file is still a regular
437          * file:
438          *   - O_NOFOLLOW: in the event the file was replaced by a symlink
439          *   - O_NONBLOCK: prevents open() from blocking if the file was
440          *     replaced by a fifo
441          * We also add in O_NOATIME, since this may reduce disk writes (for
442          * inode updates).  However, O_NOATIME may result in EPERM, so if the
443          * initial open fails, try again without O_NOATIME.  */
444         fd = open(true_path.c_str(), O_RDONLY|O_NOATIME|O_NOFOLLOW|O_NONBLOCK);
445         if (fd < 0) {
446             fd = open(true_path.c_str(), O_RDONLY|O_NOFOLLOW|O_NONBLOCK);
447         }
448         if (fd < 0) {
449             fprintf(stderr, "Unable to open file %s: %m\n", path.c_str());
450             return;
451         }
452
453         /* Drop the use of the O_NONBLOCK flag; we only wanted that for file
454          * open. */
455         flags = fcntl(fd, F_GETFL);
456         fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
457
458         /* Perform the stat call again, and check that we still have a regular
459          * file. */
460         if (fstat(fd, &stat_buf) < 0) {
461             fprintf(stderr, "fstat: %m\n");
462             close(fd);
463             return;
464         }
465
466         if ((stat_buf.st_mode & S_IFMT) != S_IFREG) {
467             fprintf(stderr, "file is no longer a regular file!\n");
468             close(fd);
469             return;
470         }
471     }
472
473     dump_inode(path, true_path, stat_buf, fd);
474
475     if (fd >= 0)
476         close(fd);
477
478     // If we hit a directory, now that we've written the directory itself,
479     // recursively scan the directory.
480     if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) {
481         DIR *dir = opendir(true_path.c_str());
482
483         if (dir == NULL) {
484             fprintf(stderr, "Error: %m\n");
485             return;
486         }
487
488         struct dirent *ent;
489         vector<string> contents;
490         while ((ent = readdir(dir)) != NULL) {
491             string filename(ent->d_name);
492             if (filename == "." || filename == "..")
493                 continue;
494             contents.push_back(filename);
495         }
496
497         closedir(dir);
498
499         sort(contents.begin(), contents.end());
500
501         for (vector<string>::iterator i = contents.begin();
502              i != contents.end(); ++i) {
503             const string& filename = *i;
504             if (path == ".")
505                 scanfile(filename, include);
506             else
507                 scanfile(path + "/" + filename, include);
508         }
509     }
510 }
511
512 /* Include the specified file path in the backups.  Append the path to the
513  * includes list, and to ensure that we actually see the path when scanning the
514  * directory tree, add all the parent directories to the search list, which
515  * means we will scan through the directory listing even if the files
516  * themselves are excluded from being backed up. */
517 void add_include(const char *path)
518 {
519     printf("Add: %s\n", path);
520     /* Was an absolute path specified?  If so, we'll need to start scanning
521      * from the root directory.  Make sure that the user was consistent in
522      * providing either all relative paths or all absolute paths. */
523     if (path[0] == '/') {
524         if (includes.size() > 0 && relative_paths == true) {
525             fprintf(stderr,
526                     "Error: Cannot mix relative and absolute paths!\n");
527             exit(1);
528         }
529
530         relative_paths = false;
531
532         // Skip over leading '/'
533         path++;
534     } else if (relative_paths == false && path[0] != '/') {
535         fprintf(stderr, "Error: Cannot mix relative and absolute paths!\n");
536         exit(1);
537     }
538
539     includes.push_back(path);
540
541     /* Split the specified path into directory components, and ensure that we
542      * descend into all the directories along the path. */
543     const char *slash = path;
544
545     if (path[0] == '\0')
546         return;
547
548     while ((slash = strchr(slash + 1, '/')) != NULL) {
549         string component(path, slash - path);
550         searches.push_back(component);
551     }
552 }
553
554 void usage(const char *program)
555 {
556     fprintf(
557         stderr,
558         "LBS %s\n\n"
559         "Usage: %s [OPTION]... --dest=DEST PATHS...\n"
560         "Produce backup snapshot of files in SOURCE and store to DEST.\n"
561         "\n"
562         "Options:\n"
563         "  --dest=PATH          path where backup is to be written\n"
564         "  --upload-script=COMMAND\n"
565         "                       program to invoke for each backup file generated\n"
566         "  --exclude=PATH       exclude files in PATH from snapshot\n"
567         "  --localdb=PATH       local backup metadata is stored in PATH\n"
568         "  --tmpdir=PATH        path for temporarily storing backup files\n"
569         "                           (defaults to TMPDIR environment variable or /tmp)\n"
570         "  --filter=COMMAND     program through which to filter segment data\n"
571         "                           (defaults to \"bzip2 -c\")\n"
572         "  --filter-extension=EXT\n"
573         "                       string to append to segment files\n"
574         "                           (defaults to \".bz2\")\n"
575         "  --signature-filter=COMMAND\n"
576         "                       program though which to filter descriptor\n"
577         "  --scheme=NAME        optional name for this snapshot\n"
578         "  --intent=FLOAT       intended backup type: 1=daily, 7=weekly, ...\n"
579         "                           (defaults to \"1\")\n"
580         "  --full-metadata      do not re-use metadata from previous backups\n"
581         "\n"
582         "Exactly one of --dest or --upload-script must be specified.\n",
583         lbs_version, program
584     );
585 }
586
587 int main(int argc, char *argv[])
588 {
589     string backup_dest = "", backup_script = "";
590     string localdb_dir = "";
591     string backup_scheme = "";
592     string signature_filter = "";
593
594     string tmp_dir = "/tmp";
595     if (getenv("TMPDIR") != NULL)
596         tmp_dir = getenv("TMPDIR");
597
598     while (1) {
599         static struct option long_options[] = {
600             {"localdb", 1, 0, 0},           // 0
601             {"exclude", 1, 0, 0},           // 1
602             {"filter", 1, 0, 0},            // 2
603             {"filter-extension", 1, 0, 0},  // 3
604             {"dest", 1, 0, 0},              // 4
605             {"scheme", 1, 0, 0},            // 5
606             {"signature-filter", 1, 0, 0},  // 6
607             {"intent", 1, 0, 0},            // 7
608             {"full-metadata", 0, 0, 0},     // 8
609             {"tmpdir", 1, 0, 0},            // 9
610             {"upload-script", 1, 0, 0},     // 10
611             {NULL, 0, 0, 0},
612         };
613
614         int long_index;
615         int c = getopt_long(argc, argv, "", long_options, &long_index);
616
617         if (c == -1)
618             break;
619
620         if (c == 0) {
621             switch (long_index) {
622             case 0:     // --localdb
623                 localdb_dir = optarg;
624                 break;
625             case 1:     // --exclude
626                 if (optarg[0] != '/')
627                     excludes.push_back(optarg);
628                 else
629                     excludes.push_back(optarg + 1);
630                 break;
631             case 2:     // --filter
632                 filter_program = optarg;
633                 break;
634             case 3:     // --filter-extension
635                 filter_extension = optarg;
636                 break;
637             case 4:     // --dest
638                 backup_dest = optarg;
639                 break;
640             case 5:     // --scheme
641                 backup_scheme = optarg;
642                 break;
643             case 6:     // --signature-filter
644                 signature_filter = optarg;
645                 break;
646             case 7:     // --intent
647                 snapshot_intent = atof(optarg);
648                 if (snapshot_intent <= 0)
649                     snapshot_intent = 1;
650                 break;
651             case 8:     // --full-metadata
652                 flag_full_metadata = true;
653                 break;
654             case 9:     // --tmpdir
655                 tmp_dir = optarg;
656                 break;
657             case 10:    // --upload-script
658                 backup_script = optarg;
659                 break;
660             default:
661                 fprintf(stderr, "Unhandled long option!\n");
662                 return 1;
663             }
664         } else {
665             usage(argv[0]);
666             return 1;
667         }
668     }
669
670     if (optind == argc) {
671         usage(argv[0]);
672         return 1;
673     }
674
675     searches.push_back(".");
676     for (int i = optind; i < argc; i++)
677         add_include(argv[i]);
678
679     if (backup_dest == "" && backup_script == "") {
680         fprintf(stderr,
681                 "Error: Backup destination must be specified using --dest= or --upload-script=\n");
682         usage(argv[0]);
683         return 1;
684     }
685
686     if (backup_dest != "" && backup_script != "") {
687         fprintf(stderr,
688                 "Error: Cannot specify both --dest= and --upload-script=\n");
689         usage(argv[0]);
690         return 1;
691     }
692
693     // Default for --localdb is the same as --dest
694     if (localdb_dir == "") {
695         localdb_dir = backup_dest;
696     }
697     if (localdb_dir == "") {
698         fprintf(stderr,
699                 "Error: Must specify local database path with --localdb=\n");
700         usage(argv[0]);
701         return 1;
702     }
703
704     // Dump paths for debugging/informational purposes
705     {
706         list<string>::const_iterator i;
707
708         printf("LBS Version: %s\n", lbs_version);
709
710         printf("--dest=%s\n--localdb=%s\n--upload-script=%s\n",
711                backup_dest.c_str(), localdb_dir.c_str(), backup_script.c_str());
712
713         printf("Includes:\n");
714         for (i = includes.begin(); i != includes.end(); ++i)
715             printf("    %s\n", i->c_str());
716
717         printf("Excludes:\n");
718         for (i = excludes.begin(); i != excludes.end(); ++i)
719             printf("    %s\n", i->c_str());
720
721         printf("Searching:\n");
722         for (i = searches.begin(); i != searches.end(); ++i)
723             printf("    %s\n", i->c_str());
724     }
725
726     block_buf = new char[LBS_BLOCK_SIZE];
727
728     /* Initialize the remote storage layer.  If using an upload script, create
729      * a temporary directory for staging files.  Otherwise, write backups
730      * directly to the destination directory. */
731     if (backup_script != "") {
732         tmp_dir = tmp_dir + "/lbs." + generate_uuid();
733         if (mkdir(tmp_dir.c_str(), 0700) < 0) {
734             fprintf(stderr, "Cannot create temporary directory %s: %m\n",
735                     tmp_dir.c_str());
736             return 1;
737         }
738         remote = new RemoteStore(tmp_dir);
739         remote->set_script(backup_script);
740     } else {
741         remote = new RemoteStore(backup_dest);
742     }
743
744     /* Store the time when the backup started, so it can be included in the
745      * snapshot name. */
746     time_t now;
747     struct tm time_buf;
748     char desc_buf[256];
749     time(&now);
750     localtime_r(&now, &time_buf);
751     strftime(desc_buf, sizeof(desc_buf), "%Y%m%dT%H%M%S", &time_buf);
752
753     /* Open the local database which tracks all objects that are stored
754      * remotely, for efficient incrementals.  Provide it with the name of this
755      * snapshot. */
756     string database_path = localdb_dir + "/localdb.sqlite";
757     db = new LocalDb;
758     db->Open(database_path.c_str(), desc_buf,
759              backup_scheme.size() ? backup_scheme.c_str() : NULL,
760              snapshot_intent);
761
762     tss = new TarSegmentStore(remote, db);
763
764     /* Initialize the stat cache, for skipping over unchanged files. */
765     metawriter = new MetadataWriter(tss, localdb_dir.c_str(), desc_buf,
766                                     backup_scheme.size()
767                                         ? backup_scheme.c_str()
768                                         : NULL);
769
770     scanfile(".", false);
771
772     ObjectReference root_ref = metawriter->close();
773     add_segment(root_ref.get_segment());
774     string backup_root = root_ref.to_string();
775
776     delete metawriter;
777
778     tss->sync();
779     tss->dump_stats();
780     delete tss;
781
782     /* Write out a checksums file which lists the checksums for all the
783      * segments included in this snapshot.  The format is designed so that it
784      * may be easily verified using the sha1sums command. */
785     const char csum_type[] = "sha1";
786     string checksum_filename = "snapshot-";
787     if (backup_scheme.size() > 0)
788         checksum_filename += backup_scheme + "-";
789     checksum_filename = checksum_filename + desc_buf + "." + csum_type + "sums";
790     RemoteFile *checksum_file = remote->alloc_file(checksum_filename,
791                                                    "checksums");
792     FILE *checksums = fdopen(checksum_file->get_fd(), "w");
793
794     for (std::set<string>::iterator i = segment_list.begin();
795          i != segment_list.end(); ++i) {
796         string seg_path, seg_csum;
797         if (db->GetSegmentChecksum(*i, &seg_path, &seg_csum)) {
798             const char *raw_checksum = NULL;
799             if (strncmp(seg_csum.c_str(), csum_type,
800                         strlen(csum_type)) == 0) {
801                 raw_checksum = seg_csum.c_str() + strlen(csum_type);
802                 if (*raw_checksum == '=')
803                     raw_checksum++;
804                 else
805                     raw_checksum = NULL;
806             }
807
808             if (raw_checksum != NULL)
809                 fprintf(checksums, "%s *%s\n",
810                         raw_checksum, seg_path.c_str());
811         }
812     }
813     fclose(checksums);
814     checksum_file->send();
815
816     db->Close();
817
818     /* All other files should be flushed to remote storage before writing the
819      * backup descriptor below, so that it is not possible to have a backup
820      * descriptor written out depending on non-existent (not yet written)
821      * files. */
822     remote->sync();
823
824     /* Write a backup descriptor file, which says which segments are needed and
825      * where to start to restore this snapshot.  The filename is based on the
826      * current time.  If a signature filter program was specified, filter the
827      * data through that to give a chance to sign the descriptor contents. */
828     string desc_filename = "snapshot-";
829     if (backup_scheme.size() > 0)
830         desc_filename += backup_scheme + "-";
831     desc_filename = desc_filename + desc_buf + ".lbs";
832
833     RemoteFile *descriptor_file = remote->alloc_file(desc_filename,
834                                                      "snapshots");
835     int descriptor_fd = descriptor_file->get_fd();
836     if (descriptor_fd < 0) {
837         fprintf(stderr, "Unable to open descriptor output file: %m\n");
838         return 1;
839     }
840     pid_t signature_pid = 0;
841     if (signature_filter.size() > 0) {
842         int new_fd = spawn_filter(descriptor_fd, signature_filter.c_str(),
843                                   &signature_pid);
844         close(descriptor_fd);
845         descriptor_fd = new_fd;
846     }
847     FILE *descriptor = fdopen(descriptor_fd, "w");
848
849     fprintf(descriptor, "Format: LBS Snapshot v0.6\n");
850     fprintf(descriptor, "Producer: LBS %s\n", lbs_version);
851     strftime(desc_buf, sizeof(desc_buf), "%Y-%m-%d %H:%M:%S %z", &time_buf);
852     fprintf(descriptor, "Date: %s\n", desc_buf);
853     if (backup_scheme.size() > 0)
854         fprintf(descriptor, "Scheme: %s\n", backup_scheme.c_str());
855     fprintf(descriptor, "Backup-Intent: %g\n", snapshot_intent);
856     fprintf(descriptor, "Root: %s\n", backup_root.c_str());
857
858     SHA1Checksum checksum_csum;
859     if (checksum_csum.process_file(checksum_filename.c_str())) {
860         string csum = checksum_csum.checksum_str();
861         fprintf(descriptor, "Checksums: %s\n", csum.c_str());
862     }
863
864     fprintf(descriptor, "Segments:\n");
865     for (std::set<string>::iterator i = segment_list.begin();
866          i != segment_list.end(); ++i) {
867         fprintf(descriptor, "    %s\n", i->c_str());
868     }
869
870     fclose(descriptor);
871
872     if (signature_pid) {
873         int status;
874         waitpid(signature_pid, &status, 0);
875
876         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
877             throw IOException("Signature filter process error");
878         }
879     }
880
881     descriptor_file->send();
882
883     remote->sync();
884     delete remote;
885
886     if (backup_script != "") {
887         if (rmdir(tmp_dir.c_str()) < 0) {
888             fprintf(stderr,
889                     "Warning: Cannot delete temporary directory %s: %m\n",
890                     tmp_dir.c_str());
891         }
892     }
893
894     return 0;
895 }