Put updated copyright statements in all source files.
[cumulus.git] / remote.cc
1 /* Cumulus: Smart Filesystem Backup to Dumb Servers
2  *
3  * Copyright (C) 2008  The Regents of the University of California
4  * Written by Michael Vrable <mvrable@cs.ucsd.edu>
5  *
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.
10  *
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.
15  *
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.
19  */
20
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.
25  *
26  * Like encryption, remote storage is handled through the use of external
27  * scripts that are called when a file is to be transferred. */
28
29 #include <assert.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/wait.h>
35
36 #include <list>
37 #include <string>
38
39 #include "remote.h"
40 #include "store.h"
41
42 using std::string;
43
44 RemoteStore::RemoteStore(const string &stagedir)
45 {
46     staging_dir = stagedir;
47
48     /* A background thread is created for each RemoteStore to manage the actual
49      * transfers to a remote server.  The main program thread can enqueue
50      * RemoteFile objects to be transferred asynchronously. */
51     pthread_mutex_init(&lock, NULL);
52     pthread_cond_init(&cond, NULL);
53     terminate = false;
54     busy = true;
55     files_outstanding = 0;
56
57     if (pthread_create(&thread, NULL, RemoteStore::start_transfer_thread,
58                        (void *)this) != 0) {
59         fprintf(stderr, "Cannot create remote storage thread: %m\n");
60         throw IOException("pthread_create");
61     }
62 }
63
64 /* The RemoteStore destructor will terminate the background transfer thread.
65  * It will wait for all work to finish. */
66 RemoteStore::~RemoteStore()
67 {
68     pthread_mutex_lock(&lock);
69     terminate = true;
70     pthread_cond_broadcast(&cond);
71     pthread_mutex_unlock(&lock);
72
73     if (pthread_join(thread, NULL) != 0) {
74         fprintf(stderr, "Warning: Unable to join storage thread: %m\n");
75     }
76
77     assert(files_outstanding == 0);
78
79     pthread_cond_destroy(&cond);
80     pthread_mutex_destroy(&lock);
81 }
82
83 /* Prepare to write out a new file.  Returns a RemoteFile object.  The file
84  * will initially be created in a temporary directory.  When the file is
85  * written out, the RemoteFile object should be passed to RemoteStore::enqueue,
86  * which will upload it to the remote server. */
87 RemoteFile *RemoteStore::alloc_file(const string &name, const string &type)
88 {
89     fprintf(stderr, "Allocate file: %s\n", name.c_str());
90     pthread_mutex_lock(&lock);
91     files_outstanding++;
92     pthread_mutex_unlock(&lock);
93     return new RemoteFile(this, name, type, staging_dir + "/" + name);
94 }
95
96 /* Request that a file be transferred to the remote server.  The actual
97  * transfer will happen asynchronously in another thread.  The call to enqueue
98  * may block, however, if there is a backlog of data to be transferred.
99  * Ownership of the RemoteFile object is transferred; the RemoteStore will be
100  * responsible for its destruction. */
101 void RemoteStore::enqueue(RemoteFile *file)
102 {
103     fprintf(stderr, "Enqueue: %s\n", file->remote_path.c_str());
104
105     pthread_mutex_lock(&lock);
106
107     while (transfer_queue.size() >= MAX_QUEUE_SIZE)
108         pthread_cond_wait(&cond, &lock);
109
110     transfer_queue.push_back(file);
111     files_outstanding--;
112     busy = true;
113
114     pthread_cond_broadcast(&cond);
115     pthread_mutex_unlock(&lock);
116 }
117
118 /* Wait for all transfers to finish. */
119 void RemoteStore::sync()
120 {
121     fprintf(stderr, "RemoteStore::sync() start\n");
122     pthread_mutex_lock(&lock);
123
124     while (busy)
125         pthread_cond_wait(&cond, &lock);
126
127     pthread_mutex_unlock(&lock);
128     fprintf(stderr, "RemoteStore::sync() end\n");
129 }
130
131 void *RemoteStore::start_transfer_thread(void *arg)
132 {
133     RemoteStore *store = static_cast<RemoteStore *>(arg);
134     store->transfer_thread();
135     return NULL;
136 }
137
138 /* Background thread for transferring backups to a remote server. */
139 void RemoteStore::transfer_thread()
140 {
141     while (true) {
142         RemoteFile *file = NULL;
143
144         // Wait for a file to transfer
145         pthread_mutex_lock(&lock);
146         while (transfer_queue.empty() && !terminate) {
147             busy = false;
148             pthread_cond_broadcast(&cond);
149             pthread_cond_wait(&cond, &lock);
150         }
151         if (terminate && transfer_queue.empty()) {
152             busy = false;
153             pthread_cond_broadcast(&cond);
154             pthread_mutex_unlock(&lock);
155             break;
156         }
157         busy = true;
158         file = transfer_queue.front();
159         transfer_queue.pop_front();
160         pthread_cond_broadcast(&cond);
161         pthread_mutex_unlock(&lock);
162
163         // Transfer the file
164         fprintf(stderr, "Start transfer: %s\n", file->remote_path.c_str());
165         if (backup_script != "") {
166             pid_t pid = fork();
167             if (pid < 0) {
168                 fprintf(stderr, "Unable to fork for upload script: %m\n");
169                 throw IOException("fork: upload script");
170             }
171             if (pid == 0) {
172                 string cmd = backup_script;
173                 cmd += " " + file->local_path + " " + file->type + " "
174                         + file->remote_path;
175                 execlp("/bin/sh", "/bin/sh", "-c", cmd.c_str(), NULL);
176                 throw IOException("exec failed");
177             }
178
179             int status = 0;
180             waitpid(pid, &status, 0);
181             if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
182                 fprintf(stderr, "Warning: error code from upload script: %d\n",
183                         status);
184             }
185
186             if (unlink(file->local_path.c_str()) < 0) {
187                 fprintf(stderr, "Warning: Deleting temporary file %s: %m\n",
188                         file->local_path.c_str());
189             }
190         }
191         fprintf(stderr, "Finish transfer: %s\n", file->remote_path.c_str());
192
193         delete file;
194     }
195 }
196
197 RemoteFile::RemoteFile(RemoteStore *remote,
198                        const string &name, const string &type,
199                        const string &local_path)
200 {
201     remote_store = remote;
202     this->type = type;
203     this->local_path = local_path;
204     this->remote_path = name;
205
206     fd = open(local_path.c_str(), O_WRONLY | O_CREAT, 0666);
207     if (fd < 0)
208         throw IOException("Error opening output file");
209 }