1 /* Cumulus: Smart Filesystem Backup to Dumb Servers
3 * Copyright (C) 2008 The Regents of the University of California
4 * Written by Michael Vrable <mvrable@cs.ucsd.edu>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 /* Backup data (segments and backup descriptors) may be stored on a remote
22 * fileserver instead of locally. The only local storage needed is for the
23 * local database and some temporary space for staging files before they are
24 * transferred to the remote server.
26 * Like encryption, remote storage is handled through the use of external
27 * scripts that are called when a file is to be transferred. */
35 #include <sys/types.h>
48 RemoteStore::RemoteStore(const string &stagedir, const string &script)
50 staging_dir = stagedir;
51 backup_script = script;
53 /* A background thread is created for each RemoteStore to manage the actual
54 * transfers to a remote server. The main program thread can enqueue
55 * RemoteFile objects to be transferred asynchronously. */
56 pthread_mutex_init(&lock, NULL);
57 pthread_cond_init(&cond, NULL);
60 files_outstanding = 0;
62 if (pthread_create(&thread, NULL, RemoteStore::start_transfer_thread,
64 fprintf(stderr, "Cannot create remote storage thread: %m\n");
65 fatal("pthread_create");
69 /* The RemoteStore destructor will terminate the background transfer thread.
70 * It will wait for all work to finish. */
71 RemoteStore::~RemoteStore()
73 pthread_mutex_lock(&lock);
75 pthread_cond_broadcast(&cond);
76 pthread_mutex_unlock(&lock);
78 if (pthread_join(thread, NULL) != 0) {
79 fprintf(stderr, "Warning: Unable to join storage thread: %m\n");
82 assert(files_outstanding == 0);
84 pthread_cond_destroy(&cond);
85 pthread_mutex_destroy(&lock);
88 /* Prepare to write out a new file. Returns a RemoteFile object. The file
89 * will initially be created in a temporary directory. When the file is
90 * written out, the RemoteFile object should be passed to RemoteStore::enqueue,
91 * which will upload it to the remote server. */
92 RemoteFile *RemoteStore::alloc_file(const string &name, const string &type)
94 pthread_mutex_lock(&lock);
96 pthread_mutex_unlock(&lock);
97 return new RemoteFile(this, name, type, staging_dir + "/" + name);
100 /* Request that a file be transferred to the remote server. The actual
101 * transfer will happen asynchronously in another thread. The call to enqueue
102 * may block, however, if there is a backlog of data to be transferred.
103 * Ownership of the RemoteFile object is transferred; the RemoteStore will be
104 * responsible for its destruction. */
105 void RemoteStore::enqueue(RemoteFile *file)
107 pthread_mutex_lock(&lock);
109 while (transfer_queue.size() >= MAX_QUEUE_SIZE)
110 pthread_cond_wait(&cond, &lock);
112 transfer_queue.push_back(file);
116 pthread_cond_broadcast(&cond);
117 pthread_mutex_unlock(&lock);
120 /* Wait for all transfers to finish. */
121 void RemoteStore::sync()
123 pthread_mutex_lock(&lock);
126 pthread_cond_wait(&cond, &lock);
128 pthread_mutex_unlock(&lock);
131 void *RemoteStore::start_transfer_thread(void *arg)
133 RemoteStore *store = static_cast<RemoteStore *>(arg);
134 store->transfer_thread();
138 /* Background thread for transferring backups to a remote server. */
139 void RemoteStore::transfer_thread()
141 /* If a transfer script was specified, launch it and connect to both stdin
142 * and stdout. fd_in is stdin of the child, and fd_out is stdout for the
145 FILE *fd_in = NULL, *fd_out = NULL;
147 if (backup_script != "") {
150 if (pipe(&fds[0]) < 0) {
151 fatal("Unable to create pipe for upload script");
153 if (pipe(&fds[2]) < 0) {
154 fatal("Unable to create pipe for upload script");
159 fprintf(stderr, "Unable to fork for upload script: %m\n");
160 fatal("fork: upload script");
167 cloexec(fds[1]); fd_in = fdopen(fds[1], "w");
168 cloexec(fds[2]); fd_out = fdopen(fds[2], "r");
169 } else if (pid == 0) {
171 if (dup2(fds[0], 0) < 0)
173 if (dup2(fds[3], 1) < 0)
175 for (int i = 0; i < 3; i++)
178 execlp("/bin/sh", "/bin/sh", "-c", backup_script.c_str(), NULL);
179 fatal("exec failed");
184 RemoteFile *file = NULL;
186 // Wait for a file to transfer
187 pthread_mutex_lock(&lock);
188 while (transfer_queue.empty() && !terminate) {
190 pthread_cond_broadcast(&cond);
191 pthread_cond_wait(&cond, &lock);
193 if (terminate && transfer_queue.empty()) {
195 pthread_cond_broadcast(&cond);
196 pthread_mutex_unlock(&lock);
200 file = transfer_queue.front();
201 transfer_queue.pop_front();
202 pthread_cond_broadcast(&cond);
203 pthread_mutex_unlock(&lock);
206 if (backup_script != "") {
208 cmd += uri_encode(file->type) + " ";
209 cmd += uri_encode(file->remote_path) + " ";
210 cmd += uri_encode(file->local_path) + "\n";
212 fputs(cmd.c_str(), fd_in);
217 if (getline(&resp, &n, fd_out) < 0 || resp == NULL)
218 fatal("error reading response from upload script");
219 if (strchr(resp, '\n'))
220 *strchr(resp, '\n') = '\0';
221 if (strcmp(resp, "OK") != 0)
222 fatal("error response from upload script");
224 if (unlink(file->local_path.c_str()) < 0) {
225 fprintf(stderr, "Warning: Deleting temporary file %s: %m\n",
226 file->local_path.c_str());
233 if (fd_in) fclose(fd_in);
237 waitpid(pid, &status, 0);
238 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
239 fprintf(stderr, "Warning: error code from upload script: %d\n",
244 if (fd_out) fclose(fd_out);
247 RemoteFile::RemoteFile(RemoteStore *remote,
248 const string &name, const string &type,
249 const string &local_path)
251 remote_store = remote;
253 this->local_path = local_path;
254 this->remote_path = name;
256 fd = open(local_path.c_str(), O_WRONLY | O_CREAT, 0666);
258 fatal("Error opening output file");