1 /* Cumulus: Efficient Filesystem Backup to the Cloud
2 * Copyright (C) 2008-2009 The Cumulus Developers
3 * See the AUTHORS file for a list of contributors.
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.
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.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 /* Backup data (segments and backup descriptors) may be stored on a remote
21 * fileserver instead of locally. The only local storage needed is for the
22 * local database and some temporary space for staging files before they are
23 * transferred to the remote server.
25 * Like encryption, remote storage is handled through the use of external
26 * scripts that are called when a file is to be transferred. */
35 #include <sys/types.h>
48 static const char *backup_directories[] = {
56 RemoteStore::RemoteStore(const string &stagedir, const string &script)
58 staging_dir = stagedir;
59 backup_script = script;
61 /* Ensure all necessary directories exist for each type of backup file. */
62 for (size_t i = 0; backup_directories[i]; i++) {
63 string path = stagedir + "/" + backup_directories[i];
64 if (mkdir(path.c_str(), 0777) < 0) {
65 /* Ignore errors for already-existing directories. */
66 if (errno != EEXIST) {
68 "Warning: Cannot create backup directory %s: %m!",
74 /* A background thread is created for each RemoteStore to manage the actual
75 * transfers to a remote server. The main program thread can enqueue
76 * RemoteFile objects to be transferred asynchronously. */
77 pthread_mutex_init(&lock, NULL);
78 pthread_cond_init(&cond, NULL);
81 files_outstanding = 0;
83 if (pthread_create(&thread, NULL, RemoteStore::start_transfer_thread,
85 fprintf(stderr, "Cannot create remote storage thread: %m\n");
86 fatal("pthread_create");
90 /* The RemoteStore destructor will terminate the background transfer thread.
91 * It will wait for all work to finish. */
92 RemoteStore::~RemoteStore()
94 pthread_mutex_lock(&lock);
96 pthread_cond_broadcast(&cond);
97 pthread_mutex_unlock(&lock);
99 if (pthread_join(thread, NULL) != 0) {
100 fprintf(stderr, "Warning: Unable to join storage thread: %m\n");
103 assert(files_outstanding == 0);
105 pthread_cond_destroy(&cond);
106 pthread_mutex_destroy(&lock);
109 /* Prepare to write out a new file. Returns a RemoteFile object. The file
110 * will initially be created in a temporary directory. When the file is
111 * written out, the RemoteFile object should be passed to RemoteStore::enqueue,
112 * which will upload it to the remote server. */
113 RemoteFile *RemoteStore::alloc_file(const string &name, const string &type)
115 pthread_mutex_lock(&lock);
117 pthread_mutex_unlock(&lock);
118 return new RemoteFile(this, name, type,
119 staging_dir + "/" + type + "/" + name);
122 /* Request that a file be transferred to the remote server. The actual
123 * transfer will happen asynchronously in another thread. The call to enqueue
124 * may block, however, if there is a backlog of data to be transferred.
125 * Ownership of the RemoteFile object is transferred; the RemoteStore will be
126 * responsible for its destruction. */
127 void RemoteStore::enqueue(RemoteFile *file)
129 pthread_mutex_lock(&lock);
131 while (transfer_queue.size() >= MAX_QUEUE_SIZE)
132 pthread_cond_wait(&cond, &lock);
134 transfer_queue.push_back(file);
138 pthread_cond_broadcast(&cond);
139 pthread_mutex_unlock(&lock);
142 /* Wait for all transfers to finish. */
143 void RemoteStore::sync()
145 pthread_mutex_lock(&lock);
148 pthread_cond_wait(&cond, &lock);
150 pthread_mutex_unlock(&lock);
153 void *RemoteStore::start_transfer_thread(void *arg)
155 RemoteStore *store = static_cast<RemoteStore *>(arg);
156 store->transfer_thread();
160 /* Background thread for transferring backups to a remote server. */
161 void RemoteStore::transfer_thread()
163 /* If a transfer script was specified, launch it and connect to both stdin
164 * and stdout. fd_in is stdin of the child, and fd_out is stdout for the
167 FILE *fd_in = NULL, *fd_out = NULL;
169 if (backup_script != "") {
172 if (pipe(&fds[0]) < 0) {
173 fatal("Unable to create pipe for upload script");
175 if (pipe(&fds[2]) < 0) {
176 fatal("Unable to create pipe for upload script");
181 fprintf(stderr, "Unable to fork for upload script: %m\n");
182 fatal("fork: upload script");
189 cloexec(fds[1]); fd_in = fdopen(fds[1], "w");
190 cloexec(fds[2]); fd_out = fdopen(fds[2], "r");
191 } else if (pid == 0) {
193 if (dup2(fds[0], 0) < 0)
195 if (dup2(fds[3], 1) < 0)
197 for (int i = 0; i < 3; i++)
200 execlp("/bin/sh", "/bin/sh", "-c", backup_script.c_str(), NULL);
201 fatal("exec failed");
206 RemoteFile *file = NULL;
208 // Wait for a file to transfer
209 pthread_mutex_lock(&lock);
210 while (transfer_queue.empty() && !terminate) {
212 pthread_cond_broadcast(&cond);
213 pthread_cond_wait(&cond, &lock);
215 if (terminate && transfer_queue.empty()) {
217 pthread_cond_broadcast(&cond);
218 pthread_mutex_unlock(&lock);
222 file = transfer_queue.front();
223 transfer_queue.pop_front();
224 pthread_cond_broadcast(&cond);
225 pthread_mutex_unlock(&lock);
228 if (backup_script != "") {
230 cmd += uri_encode(file->type) + " ";
231 cmd += uri_encode(file->remote_path) + " ";
232 cmd += uri_encode(file->local_path) + "\n";
234 fputs(cmd.c_str(), fd_in);
239 if (getline(&resp, &n, fd_out) < 0 || resp == NULL)
240 fatal("error reading response from upload script");
241 if (strchr(resp, '\n'))
242 *strchr(resp, '\n') = '\0';
243 if (strcmp(resp, "OK") != 0)
244 fatal("error response from upload script");
246 if (unlink(file->local_path.c_str()) < 0) {
247 fprintf(stderr, "Warning: Deleting temporary file %s: %m\n",
248 file->local_path.c_str());
255 if (fd_in) fclose(fd_in);
259 waitpid(pid, &status, 0);
260 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
261 fprintf(stderr, "Warning: error code from upload script: %d\n",
266 if (fd_out) fclose(fd_out);
269 RemoteFile::RemoteFile(RemoteStore *remote,
270 const string &name, const string &type,
271 const string &local_path)
273 remote_store = remote;
275 this->local_path = local_path;
276 this->remote_path = type + "/" + name;
278 fd = open(local_path.c_str(), O_WRONLY | O_CREAT, 0666);
280 fatal("Error opening output file");