Initial framework for direct transfer of backups to remote storage.
[cumulus.git] / remote.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2006  Michael Vrable
3  *
4  * Backup data (segments and backup descriptors) may be stored on a remote
5  * fileserver instead of locally.  The only local storage needed is for the
6  * local database and some temporary space for staging files before they are
7  * transferred to the remote server.
8  *
9  * Like encryption, remote storage is handled through the use of external
10  * scripts that are called when a file is to be transferred. */
11
12 #include <assert.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17
18 #include <list>
19 #include <string>
20
21 #include "remote.h"
22 #include "store.h"
23
24 using std::string;
25
26 RemoteStore::RemoteStore(const string &stagedir)
27 {
28     staging_dir = stagedir;
29
30     /* A background thread is created for each RemoteStore to manage the actual
31      * transfers to a remote server.  The main program thread can enqueue
32      * RemoteFile objects to be transferred asynchronously. */
33     pthread_mutex_init(&lock, NULL);
34     pthread_cond_init(&cond, NULL);
35     terminate = false;
36     busy = true;
37     files_outstanding = 0;
38
39     if (pthread_create(&thread, NULL, RemoteStore::start_transfer_thread,
40                        (void *)this) != 0) {
41         fprintf(stderr, "Cannot create remote storage thread: %m\n");
42         throw IOException("pthread_create");
43     }
44 }
45
46 /* The RemoteStore destructor will terminate the background transfer thread.
47  * It will wait for all work to finish. */
48 RemoteStore::~RemoteStore()
49 {
50     pthread_mutex_lock(&lock);
51     terminate = true;
52     pthread_cond_broadcast(&cond);
53     pthread_mutex_unlock(&lock);
54
55     if (pthread_join(thread, NULL) != 0) {
56         fprintf(stderr, "Warning: Unable to join storage thread: %m\n");
57     }
58
59     assert(files_outstanding == 0);
60
61     pthread_cond_destroy(&cond);
62     pthread_mutex_destroy(&lock);
63 }
64
65 /* Prepare to write out a new file.  Returns a RemoteFile object.  The file
66  * will initially be created in a temporary directory.  When the file is
67  * written out, the RemoteFile object should be passed to RemoteStore::enqueue,
68  * which will upload it to the remote server. */
69 RemoteFile *RemoteStore::alloc_file(const string &name)
70 {
71     fprintf(stderr, "Allocate file: %s\n", name.c_str());
72     pthread_mutex_lock(&lock);
73     files_outstanding++;
74     pthread_mutex_unlock(&lock);
75     return new RemoteFile(this, name, staging_dir + "/" + name);
76 }
77
78 /* Request that a file be transferred to the remote server.  The actual
79  * transfer will happen asynchronously in another thread.  The call to enqueue
80  * may block, however, if there is a backlog of data to be transferred.
81  * Ownership of the RemoteFile object is transferred; the RemoteStore will be
82  * responsible for its destruction. */
83 void RemoteStore::enqueue(RemoteFile *file)
84 {
85     fprintf(stderr, "Enqueue: %s\n", file->remote_path.c_str());
86
87     pthread_mutex_lock(&lock);
88
89     while (transfer_queue.size() >= MAX_QUEUE_SIZE)
90         pthread_cond_wait(&cond, &lock);
91
92     transfer_queue.push_back(file);
93     files_outstanding--;
94     busy = true;
95
96     pthread_cond_broadcast(&cond);
97     pthread_mutex_unlock(&lock);
98 }
99
100 /* Wait for all transfers to finish. */
101 void RemoteStore::sync()
102 {
103     fprintf(stderr, "RemoteStore::sync() start\n");
104     pthread_mutex_lock(&lock);
105
106     while (busy)
107         pthread_cond_wait(&cond, &lock);
108
109     pthread_mutex_unlock(&lock);
110     fprintf(stderr, "RemoteStore::sync() end\n");
111 }
112
113 void *RemoteStore::start_transfer_thread(void *arg)
114 {
115     RemoteStore *store = static_cast<RemoteStore *>(arg);
116     store->transfer_thread();
117     return NULL;
118 }
119
120 /* Background thread for transferring backups to a remote server. */
121 void RemoteStore::transfer_thread()
122 {
123     while (true) {
124         RemoteFile *file = NULL;
125
126         // Wait for a file to transfer
127         pthread_mutex_lock(&lock);
128         while (transfer_queue.empty() && !terminate) {
129             busy = false;
130             pthread_cond_broadcast(&cond);
131             pthread_cond_wait(&cond, &lock);
132         }
133         if (terminate && transfer_queue.empty()) {
134             busy = false;
135             pthread_cond_broadcast(&cond);
136             pthread_mutex_unlock(&lock);
137             break;
138         }
139         busy = true;
140         file = transfer_queue.front();
141         transfer_queue.pop_front();
142         pthread_cond_broadcast(&cond);
143         pthread_mutex_unlock(&lock);
144
145         // Transfer the file
146         fprintf(stderr, "Start transfer: %s\n", file->remote_path.c_str());
147         // TODO
148         fprintf(stderr, "Finish transfer: %s\n", file->remote_path.c_str());
149
150         delete file;
151     }
152 }
153
154 RemoteFile::RemoteFile(RemoteStore *remote,
155                        const string &name, const string &local_path)
156 {
157     remote_store = remote;
158     this->local_path = local_path;
159     this->remote_path = name;
160
161     fd = open(local_path.c_str(), O_WRONLY | O_CREAT, 0666);
162     if (fd < 0)
163         throw IOException("Error opening output file");
164 }