Support for spreading objects across segments.
[cumulus.git] / scandir.cc
1 /* Recursively descend the filesystem and visit each file. */
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <stdint.h>
6 #include <dirent.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12
13 #include <algorithm>
14 #include <string>
15 #include <vector>
16
17 #include "store.h"
18 #include "sha1.h"
19
20 using std::string;
21 using std::vector;
22
23 static SegmentStore *segment_store;
24 static OutputStream *info_dump = NULL;
25
26 static SegmentPartitioner *index_segment, *data_segment;
27
28 void scandir(const string& path);
29
30 /* Converts time to microseconds since the epoch. */
31 int64_t encode_time(time_t time)
32 {
33     return (int64_t)time * 1000000;
34 }
35
36 void dumpfile(int fd, dictionary &file_info)
37 {
38     struct stat stat_buf;
39     fstat(fd, &stat_buf);
40     int64_t size = 0;
41
42     char buf[4096];
43
44     if ((stat_buf.st_mode & S_IFMT) != S_IFREG) {
45         printf("file is no longer a regular file!\n");
46         return;
47     }
48
49     SHA1Checksum hash;
50     while (true) {
51         ssize_t res = read(fd, buf, sizeof(buf));
52         if (res < 0) {
53             if (errno == EINTR)
54                 continue;
55             printf("Error while reading: %m\n");
56             return;
57         } else if (res == 0) {
58             break;
59         } else {
60             hash.process(buf, res);
61             OutputStream *block = data_segment->new_object();
62             block->write(buf, res);
63             size += res;
64         }
65     }
66
67     file_info["sha1"] = string((const char *)hash.checksum(),
68                                hash.checksum_size());
69 }
70
71 void scanfile(const string& path)
72 {
73     int fd;
74     long flags;
75     struct stat stat_buf;
76     char *buf;
77     ssize_t len;
78
79     // Set to true if the item is a directory and we should recursively scan
80     bool recurse = false;
81
82     dictionary file_info;
83
84     lstat(path.c_str(), &stat_buf);
85
86     printf("%s\n", path.c_str());
87
88     file_info["mode"] = encode_u16(stat_buf.st_mode & 07777);
89     file_info["atime"] = encode_u64(encode_time(stat_buf.st_atime));
90     file_info["ctime"] = encode_u64(encode_time(stat_buf.st_ctime));
91     file_info["mtime"] = encode_u64(encode_time(stat_buf.st_mtime));
92     file_info["user"] = encode_u32(stat_buf.st_uid);
93     file_info["group"] = encode_u32(stat_buf.st_gid);
94
95     char inode_type;
96
97     switch (stat_buf.st_mode & S_IFMT) {
98     case S_IFIFO:
99         inode_type = 'p';
100         break;
101     case S_IFSOCK:
102         inode_type = 's';
103         break;
104     case S_IFCHR:
105         inode_type = 'c';
106         break;
107     case S_IFBLK:
108         inode_type = 'b';
109         break;
110     case S_IFLNK:
111         inode_type = 'l';
112
113         /* Use the reported file size to allocate a buffer large enough to read
114          * the symlink.  Allocate slightly more space, so that we ask for more
115          * bytes than we expect and so check for truncation. */
116         buf = new char[stat_buf.st_size + 2];
117         len = readlink(path.c_str(), buf, stat_buf.st_size + 1);
118         if (len < 0) {
119             printf("error reading symlink: %m\n");
120         } else if (len <= stat_buf.st_size) {
121             buf[len] = '\0';
122             printf("    contents=%s\n", buf);
123         } else if (len > stat_buf.st_size) {
124             printf("error reading symlink: name truncated\n");
125         }
126
127         file_info["contents"] = buf;
128
129         delete[] buf;
130         break;
131     case S_IFREG:
132         inode_type = '-';
133
134         /* Be paranoid when opening the file.  We have no guarantee that the
135          * file was not replaced between the stat() call above and the open()
136          * call below, so we might not even be opening a regular file.  That
137          * the file descriptor refers to a regular file is checked in
138          * dumpfile().  But we also supply flags to open to to guard against
139          * various conditions before we can perform that verification:
140          *   - O_NOFOLLOW: in the event the file was replaced by a symlink
141          *   - O_NONBLOCK: prevents open() from blocking if the file was
142          *     replaced by a fifo
143          * We also add in O_NOATIME, since this may reduce disk writes (for
144          * inode updates). */
145         fd = open(path.c_str(), O_RDONLY|O_NOATIME|O_NOFOLLOW|O_NONBLOCK);
146
147         /* Drop the use of the O_NONBLOCK flag; we only wanted that for file
148          * open. */
149         flags = fcntl(fd, F_GETFL);
150         fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
151
152         file_info["size"] = encode_u64(stat_buf.st_size);
153         dumpfile(fd, file_info);
154         close(fd);
155
156         break;
157     case S_IFDIR:
158         inode_type = 'd';
159         recurse = true;
160         break;
161
162     default:
163         fprintf(stderr, "Unknown inode type: mode=%x\n", stat_buf.st_mode);
164         return;
165     }
166
167     file_info["type"] = string(1, inode_type);
168
169     info_dump->write_string(path);
170     info_dump->write_dictionary(file_info);
171
172     // If we hit a directory, now that we've written the directory itself,
173     // recursively scan the directory.
174     if (recurse)
175         scandir(path);
176 }
177
178 void scandir(const string& path)
179 {
180     DIR *dir = opendir(path.c_str());
181
182     if (dir == NULL) {
183         printf("Error: %m\n");
184         return;
185     }
186
187     struct dirent *ent;
188     vector<string> contents;
189     while ((ent = readdir(dir)) != NULL) {
190         string filename(ent->d_name);
191         if (filename == "." || filename == "..")
192             continue;
193         contents.push_back(filename);
194     }
195
196     sort(contents.begin(), contents.end());
197
198     for (vector<string>::iterator i = contents.begin();
199          i != contents.end(); ++i) {
200         const string& filename = *i;
201         scanfile(path + "/" + filename);
202     }
203
204     closedir(dir);
205 }
206
207 int main(int argc, char *argv[])
208 {
209     segment_store = new SegmentStore(".");
210     SegmentWriter *sw = segment_store->new_segment();
211     info_dump = sw->new_object();
212
213     index_segment = new SegmentPartitioner(segment_store);
214     data_segment = new SegmentPartitioner(segment_store);
215
216     string uuid = SegmentWriter::format_uuid(sw->get_uuid());
217     printf("Backup UUID: %s\n", uuid.c_str());
218
219     try {
220         scanfile(".");
221     } catch (IOException e) {
222         fprintf(stderr, "IOException: %s\n", e.getError().c_str());
223     }
224
225     delete index_segment;
226     delete data_segment;
227     delete sw;
228
229     return 0;
230 }