Imported Upstream version 2.6.0
[debian/amanda] / server-src / taper-file-source.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 2006 Zmanda Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #define selfp (self->_priv)
21
22 #include "taper-file-source.h"
23
24 #include "fileheader.h"
25 #include "holding.h"
26
27 #define HOLDING_DISK_OPEN_FLAGS (O_NOCTTY | O_RDONLY)
28
29 struct _TaperFileSourcePrivate {
30     /* How many bytes have we written from the current part? */
31     guint64 current_part_pos;
32     /* Information about the files at the start of this part. */
33     dumpfile_t part_start_chunk_header;
34     int part_start_chunk_fd;
35     /* Where is the start of this part with respect to the first chunk
36        of the part? */
37     guint64 part_start_chunk_offset;
38     /* These may be the same as their part_start_chunk_ counterparts. */
39     dumpfile_t current_chunk_header;
40     int current_chunk_fd;
41     /* Current position of current_chunk_fd. */
42     guint64 current_chunk_position;
43     /* Expected number of split parts. */
44     int predicted_splits;
45 };
46 /* here are local prototypes */
47 static void taper_file_source_init (TaperFileSource * o);
48 static void taper_file_source_class_init (TaperFileSourceClass * c);
49 static ssize_t taper_file_source_read (TaperSource * pself, void * buf,
50                                             size_t count);
51 static gboolean taper_file_source_seek_to_part_start (TaperSource * pself);
52 static void taper_file_source_start_new_part (TaperSource * pself);
53 static int taper_file_source_predict_parts(TaperSource * pself);
54 static dumpfile_t * taper_file_source_get_first_header(TaperSource * pself);
55 static gboolean first_time_setup(TaperFileSource * self);
56
57 /* pointer to the class of our parent */
58 static TaperSourceClass *parent_class = NULL;
59
60 GType taper_file_source_get_type (void) {
61     static GType type = 0;
62     
63     if G_UNLIKELY(type == 0) {
64         static const GTypeInfo info = {
65             sizeof (TaperFileSourceClass),
66             (GBaseInitFunc) NULL,
67             (GBaseFinalizeFunc) NULL,
68             (GClassInitFunc) taper_file_source_class_init,
69             (GClassFinalizeFunc) NULL,
70             NULL /* class_data */,
71             sizeof (TaperFileSource),
72             0 /* n_preallocs */,
73             (GInstanceInitFunc) taper_file_source_init,
74             NULL
75         };
76         
77         type = g_type_register_static (TAPER_TYPE_SOURCE, "TaperFileSource",
78                                        &info, (GTypeFlags)0);
79     }
80     
81     return type;
82 }
83
84 static void
85 taper_file_source_finalize(GObject *obj_self)
86 {
87     TaperFileSource *self = TAPER_FILE_SOURCE (obj_self);
88     gpointer priv G_GNUC_UNUSED = self->_priv;
89     if(G_OBJECT_CLASS(parent_class)->finalize)
90         (* G_OBJECT_CLASS(parent_class)->finalize)(obj_self);
91     if(self->_priv->part_start_chunk_fd >= 0) {
92         close (self->_priv->part_start_chunk_fd);
93     }
94     if(self->_priv->current_chunk_fd >= 0) {
95         close (self->_priv->current_chunk_fd);
96     }
97 }
98
99 static void 
100 taper_file_source_init (TaperFileSource * o G_GNUC_UNUSED)
101 {
102     o->_priv = malloc(sizeof(TaperFileSourcePrivate));
103     o->_priv->part_start_chunk_fd = -1;
104     o->_priv->current_chunk_fd = -1;
105     o->_priv->predicted_splits = -1;
106     o->holding_disk_file = NULL;
107 }
108
109 static void  taper_file_source_class_init (TaperFileSourceClass * c) {
110     GObjectClass *g_object_class = (GObjectClass*) c;
111     TaperSourceClass *taper_source_class = (TaperSourceClass *)c;
112
113     parent_class = g_type_class_ref (TAPER_TYPE_SOURCE);
114
115     taper_source_class->read = taper_file_source_read;
116     taper_source_class->seek_to_part_start =
117         taper_file_source_seek_to_part_start;
118     taper_source_class->start_new_part = taper_file_source_start_new_part;
119     taper_source_class->get_first_header = taper_file_source_get_first_header;
120     taper_source_class->predict_parts = taper_file_source_predict_parts;
121
122     g_object_class->finalize = taper_file_source_finalize;
123 }
124
125 static void compute_splits(TaperFileSource * self) {
126     guint64 total_kb;
127     int predicted_splits;
128     TaperSource * pself = (TaperSource*)self;
129
130     if (selfp->predicted_splits > 0) {
131         return;
132     }
133
134     if (pself->max_part_size <= 0) {
135         selfp->predicted_splits = 1;
136         return;
137     }
138
139     total_kb = holding_file_size(self->holding_disk_file, TRUE);
140     if (total_kb <= 0) {
141         g_fprintf(stderr, "taper: %lld KB holding file makes no sense, not precalculating splits\n",
142                 (long long)total_kb);
143         fflush(stderr);
144         selfp->predicted_splits = -1;
145         return;
146     }
147     
148     g_fprintf(stderr, "taper: Total dump size should be %jukb, part size is %jukb\n",
149             (uintmax_t)total_kb, (uintmax_t)pself->max_part_size);
150
151     predicted_splits = (total_kb * 1024) / pself->max_part_size;
152     if (predicted_splits == 0 ||
153         (0 != ((total_kb * 1024) % pself->max_part_size))) {
154         predicted_splits ++;
155     }
156     selfp->predicted_splits = predicted_splits;
157 }
158
159 static int taper_file_source_predict_parts(TaperSource * pself) {
160     TaperFileSource * self = TAPER_FILE_SOURCE(pself);
161     g_return_val_if_fail(self != NULL, -1);
162
163     compute_splits(self);
164
165     return selfp->predicted_splits;
166 }
167
168 static dumpfile_t * taper_file_source_get_first_header(TaperSource * pself) {
169     TaperFileSource * self = TAPER_FILE_SOURCE(pself);
170     g_return_val_if_fail(self != NULL, NULL);
171
172     first_time_setup(self);
173
174     if (parent_class->get_first_header) {
175         return (parent_class->get_first_header)(pself);
176     } else {
177         return NULL;
178     }
179 }
180
181 /* Open a holding disk and parse the header. Returns TRUE if
182    everything went OK. Writes the fd into fd_pointer and the header
183    into header_pointer. Both must be non-NULL. */
184 static gboolean open_holding_file(char * filename, int * fd_pointer,
185                                   dumpfile_t * header_pointer) {
186     int fd;
187     int read_result;
188     char * header_buffer;
189
190     g_return_val_if_fail(filename != NULL, FALSE);
191     g_return_val_if_fail(fd_pointer != NULL, FALSE);
192     g_return_val_if_fail(header_pointer != NULL, FALSE);
193
194     fd = robust_open(filename, O_NOCTTY | O_RDONLY, 0);
195     if (fd < 0) {
196         g_fprintf(stderr, "Could not open holding disk file %s: %s\n",
197                 filename, strerror(errno));
198         return FALSE;
199     }
200
201     header_buffer = malloc(DISK_BLOCK_BYTES);
202     read_result = fullread(fd, header_buffer, DISK_BLOCK_BYTES);
203     if (read_result < DISK_BLOCK_BYTES) {
204         g_fprintf(stderr,
205                 "Could not read header from holding disk file %s: %s\n",
206                 filename, strerror(errno));
207         aclose(fd);
208         return FALSE;
209     }
210     
211     parse_file_header(header_buffer, header_pointer, DISK_BLOCK_BYTES);
212     amfree(header_buffer);
213     
214     if (!(header_pointer->type == F_DUMPFILE ||
215           header_pointer->type == F_CONT_DUMPFILE)) {
216         g_fprintf(stderr, "Got strange header from file %s.\n",
217                 filename);
218         aclose(fd);
219         return FALSE;
220     }
221     
222     *fd_pointer = fd;
223     return TRUE;
224 }
225
226 /* Copy fd and header information from first chunk fields to current
227    chunk. Returns FALSE if an error occurs (unlikely). */
228 static gboolean copy_chunk_data(int * from_fd, int* to_fd,
229                                 dumpfile_t * from_header,
230                                 dumpfile_t * to_header) {
231     g_return_val_if_fail(from_fd != NULL, FALSE);
232     g_return_val_if_fail(to_fd != NULL, FALSE);
233     g_return_val_if_fail(from_header != NULL, FALSE);
234     g_return_val_if_fail(to_header != NULL, FALSE);
235     g_return_val_if_fail(*to_fd < 0, FALSE);
236     
237     *to_fd = dup(*from_fd);
238     if (*to_fd < 0) {
239         g_fprintf(stderr, "dup(%d) failed!\n", *from_fd);
240         return FALSE;
241     }
242
243     memcpy(to_header, from_header, sizeof(*to_header));
244
245     return TRUE;
246 }
247
248
249 static gboolean first_time_setup(TaperFileSource * self) {
250     TaperSource * pself = (TaperSource*)self;
251
252     if (selfp->part_start_chunk_fd >= 0) {
253         return TRUE;
254     }
255
256     g_return_val_if_fail(self->holding_disk_file != NULL, FALSE);
257
258     if (!open_holding_file(self->holding_disk_file, 
259                            &(selfp->part_start_chunk_fd),
260                            &(selfp->part_start_chunk_header))) {
261         return FALSE;
262     }
263
264     /* We are all set; just copy the "start chunk" datums into the
265        "current chunk" fields. */
266     if (!copy_chunk_data(&(selfp->part_start_chunk_fd),
267                          &(selfp->current_chunk_fd),
268                          &(selfp->part_start_chunk_header),
269                          &(selfp->current_chunk_header))) {
270         aclose(selfp->part_start_chunk_fd);
271         return FALSE;
272     }
273
274     pself->first_header = g_memdup(&(selfp->part_start_chunk_header),
275                                    sizeof(dumpfile_t));
276
277     /* Should not be necessary. You never know! */
278     selfp->current_part_pos = selfp->part_start_chunk_offset =
279         selfp->current_chunk_position = 0;
280
281     return TRUE;
282 }
283
284 static int retry_read(int fd, void * buf, size_t count) {
285     for (;;) {
286         int read_result = read(fd, buf, count);
287         if (read_result < 0 && (0
288 #ifdef EAGAIN
289                                 || errno == EAGAIN
290 #endif
291 #ifdef EWOULDBLOCK
292                                 || errno == EWOULDBLOCK
293 #endif
294 #ifdef EINTR
295                                 || errno == EINTR
296 #endif
297                   )) {
298             /* Try again. */
299             continue;
300         } else {
301             if (read_result < 0) {
302                 g_fprintf(stderr, "Error reading holding disk: %s\n",
303                         strerror(errno));
304             }
305             return read_result;
306         }
307     }
308 }
309
310 /* If another chunk is available, load it. Returns TRUE if there are
311    no more chunks or the next chunk is loaded, or FALSE if an error
312    occurs. */
313 static gboolean get_next_chunk(TaperFileSource * self) {
314     char * cont_filename = NULL;
315
316     if (selfp->current_chunk_header.cont_filename[0] != '\0') {
317         cont_filename =
318             g_strdup(selfp->current_chunk_header.cont_filename);
319     } else {
320         /* No more data. */
321         aclose(selfp->current_chunk_fd);
322         bzero(&(selfp->current_chunk_header),
323               sizeof(selfp->current_chunk_header));
324         return TRUE;
325     }
326
327     /* More data. */
328
329     aclose(selfp->current_chunk_fd);
330
331     if (!open_holding_file(cont_filename,
332                            &(selfp->current_chunk_fd),
333                            &(selfp->current_chunk_header))) {
334         amfree(cont_filename);
335         bzero(&(selfp->current_chunk_header),
336               sizeof(selfp->current_chunk_header));
337         aclose(selfp->current_chunk_fd);
338         return FALSE;
339     }
340
341     amfree(cont_filename);
342     selfp->current_chunk_position = 0;
343
344     return TRUE;
345 }
346
347 static ssize_t 
348 taper_file_source_read (TaperSource * pself, void * buf, size_t count) {
349     TaperFileSource * self = (TaperFileSource*) pself;
350     int read_result;
351
352     g_return_val_if_fail (self != NULL, -1);
353     g_return_val_if_fail (TAPER_IS_FILE_SOURCE (self), -1);
354     g_return_val_if_fail (buf != NULL, -1);
355     g_return_val_if_fail (count > 0, -1);
356     
357     if (!first_time_setup(self))
358         return -1;
359
360     if (pself->max_part_size > 0) {
361         count = MIN(count, pself->max_part_size - selfp->current_part_pos);
362     }
363     if (count <= 0) {
364         /* Was positive before. Thus we are at EOP. */
365         pself->end_of_part = TRUE;
366         return 0;
367     }
368
369     /* We don't use fullread, because we would rather return a partial
370      * read ASAP. */
371     read_result = retry_read(selfp->current_chunk_fd, buf, count);
372     if (read_result < 0) {
373         /* Nothing we can do. */
374         return read_result;
375     } else if (read_result == 0) {
376         if (!get_next_chunk(self)) {
377             return -1; 
378         }
379
380         if (selfp->current_chunk_fd >= 0) {
381             /* Try again with the next chunk. */
382             return taper_file_source_read(pself, buf, count);
383         } else {
384             pself->end_of_data = TRUE;
385             return 0;
386         }
387     } else {
388         /* Success. */
389         selfp->current_part_pos += read_result;
390         selfp->current_chunk_position += read_result;
391         return read_result;
392     }
393 }
394
395 static gboolean taper_file_source_seek_to_part_start (TaperSource * pself) {
396     TaperFileSource * self = (TaperFileSource*)pself;
397     off_t lseek_result;
398
399     g_return_val_if_fail (self != NULL, FALSE);
400     g_return_val_if_fail (TAPER_IS_FILE_SOURCE (self), FALSE);
401
402     aclose(selfp->current_chunk_fd);
403     if (!copy_chunk_data(&(selfp->part_start_chunk_fd),
404                          &(selfp->current_chunk_fd),
405                          &(selfp->part_start_chunk_header),
406                          &(selfp->current_chunk_header))) {
407         return FALSE;
408     }
409
410     selfp->current_chunk_position = selfp->part_start_chunk_offset;
411
412     lseek_result = lseek(selfp->current_chunk_fd,
413                          DISK_BLOCK_BYTES + selfp->current_chunk_position,
414                          SEEK_SET);
415     if (lseek_result < 0) {
416         g_fprintf(stderr, "Could not seek holding disk file: %s\n",
417                 strerror(errno));
418         return FALSE;
419     }
420
421     selfp->current_part_pos = 0;
422
423     if (parent_class->seek_to_part_start)
424         return parent_class->seek_to_part_start(pself);
425     else
426         return TRUE;
427 }
428
429 static void taper_file_source_start_new_part (TaperSource * pself) {
430     TaperFileSource * self = (TaperFileSource*)pself;
431     g_return_if_fail (self != NULL);
432     g_return_if_fail (TAPER_IS_FILE_SOURCE (self));
433
434     aclose(selfp->part_start_chunk_fd);
435     if (!copy_chunk_data(&(selfp->current_chunk_fd),
436                          &(selfp->part_start_chunk_fd),
437                          &(selfp->current_chunk_header),
438                          &(selfp->part_start_chunk_header))) {
439         /* We can't return FALSE. :-( Instead, we set things up so
440            they will fail on the next read(). */
441         aclose(selfp->current_chunk_fd);
442         aclose(selfp->part_start_chunk_fd);
443         return;
444     }
445
446     selfp->part_start_chunk_offset = selfp->current_chunk_position;
447     selfp->current_part_pos = 0;
448
449     if (parent_class->start_new_part)
450         parent_class->start_new_part(pself);
451 }
452