Imported Upstream version 2.6.1p2
[debian/amanda] / server-src / taperscan.c
1 /*
2  * Copyright (c) 2005-2008 Zmanda Inc.  All Rights Reserved.
3  * 
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 as published
6  * by the Free Software Foundation.
7  * 
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16  * 
17  * Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
18  * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19  */
20
21 /*
22  * $Id: taperscan.c,v 1.17 2006/07/12 12:28:19 martinea Exp $
23  *
24  * This contains the implementation of the taper-scan algorithm, as it is
25  * used by taper, amcheck, and amtape. See the header file taperscan.h for
26  * interface information. */
27
28 #include "amanda.h"
29 #include "conffile.h"
30 #include "changer.h"
31 #include "tapefile.h"
32 #include "device.h"
33 #include "timestamp.h"
34 #include "taperscan.h"
35
36 struct taper_scan_tracker_s {
37     GHashTable * scanned_slots;
38 };
39
40 int scan_read_label (char *dev, char * slot, char *wantlabel,
41                        char** label, char** timestamp,
42                        char**error_message);
43 int changer_taper_scan (char *wantlabel, char** gotlabel, char** timestamp,
44                         char **tapedev, taper_scan_tracker_t * tracker,
45                         TaperscanOutputFunctor output_functor,
46                         void *output_data,
47                         TaperscanProlongFunctor prolong_functor,
48                         void *prolong_data);
49 int scan_slot (void *data, int rc, char *slotstr, char *device);
50 char *find_brand_new_tape_label (void);
51 void FILE_taperscan_output_callback (void *data, char *msg);
52 void CHAR_taperscan_output_callback (void *data, char *msg);
53
54 /* NO GLOBALS PLEASE! */
55
56 /* How does the taper scan algorithm work? Like this:
57  * 1) If there is a barcode reader, and we have a recyclable tape, use the
58  *    reader to load the oldest tape.
59  * 2) Otherwise, search through the changer until we find a new tape
60  *    or the oldest recyclable tape.
61  * 3) If we couldn't find the oldest recyclable tape or a new tape,
62  *    but if in #2 we found *some* recyclable tape, use the oldest one we
63  *    found.
64  * 4) At this point, we give up.
65  */
66
67 /* This function checks the label of a single tape, which may or may not
68  * have been loaded by the changer. With the addition of char *dev, and *slot,
69  * it has the same interface as taper_scan. slot should be the slot where
70  * this tape is found, or NULL if no changer is in use.
71  * Return value is the same as taper_scan.
72  */
73 int scan_read_label(
74     char *dev,
75     char *slot,
76     char *desired_label,
77     char** label,
78     char** timestamp,
79     char** error_message)
80 {
81     Device * device;
82     char *labelstr;
83     DeviceStatusFlags device_status;
84
85     g_return_val_if_fail(dev != NULL, -1);
86
87     if (*error_message == NULL)
88         *error_message = stralloc("");
89
90     *label = *timestamp = NULL;
91     device = device_open(dev);
92     g_assert(device != NULL);
93
94     if (device->status != DEVICE_STATUS_SUCCESS ) {
95         *error_message = newvstrallocf(*error_message,
96                                        _("%sError opening device %s: %s.\n"),
97                                        *error_message, dev,
98                                        device_error_or_status(device));
99         g_object_unref(device);
100         amfree(*timestamp);
101         amfree(*label);
102         return -1;
103     }
104
105     if (!device_configure(device, TRUE)) {
106         *error_message = newvstrallocf(*error_message,
107                                        _("%sError configuring device %s: %s.\n"),
108                                        *error_message, dev,
109                                        device_error_or_status(device));
110         g_object_unref(device);
111         amfree(*timestamp);
112         amfree(*label);
113         return -1;
114     }
115
116     device_status = device_read_label(device);
117     
118     if (device_status == DEVICE_STATUS_SUCCESS && device->volume_label != NULL) {
119         *label = g_strdup(device->volume_label);
120         *timestamp = strdup(device->volume_time);
121     } else if (device_status & DEVICE_STATUS_VOLUME_UNLABELED) {
122         if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
123             *error_message = newvstrallocf(*error_message,
124                                            _("%sFound an empty or non-amanda tape.\n"),
125                                            *error_message);
126             g_object_unref(device);
127             return -1;
128         }
129
130         /* If we got a header, but the Device doesn't think it's labeled, then this
131          * tape probably has some data on it, so refuse to automatically label it */
132         if (device->volume_header && device->volume_header->type != F_EMPTY) {
133             *error_message = newvstrallocf(*error_message,
134                        _("%sFound a non-amanda tape; check and relabel it with 'amlabel -f'\n"),
135                        *error_message);
136             g_object_unref(device);
137             return -1;
138         }
139         g_object_unref(device);
140
141         *label = find_brand_new_tape_label();
142         if (*label != NULL) {
143             *timestamp = stralloc("X");
144             *error_message = newvstrallocf(*error_message,
145                      _("%sFound an empty tape, will label it `%s'.\n"),
146                                            *error_message, *label);
147
148             return 3;
149         }
150         *error_message = newvstrallocf(*error_message,
151                  _("%sFound an empty tape, but have no labels left.\n"),
152                                        *error_message);
153
154         return -1;
155     } else {
156         char * label_errstr;
157         label_errstr = g_strdup_printf(_("Error reading label: %s.\n"),
158                                        device_error_or_status(device));
159         *error_message = newvstralloc(*error_message, *error_message,
160                                       label_errstr, NULL);
161         g_free(label_errstr);
162         return -1;
163     }
164
165     g_assert(*label != NULL && *timestamp != NULL);
166     g_object_unref(device);
167
168     *error_message = newvstrallocf(*error_message,
169                                    _("%sread label `%s', date `%s'.\n"),
170                                    *error_message, *label, *timestamp);
171
172     /* Register this with the barcode database, even if its not ours. */
173     if (slot != NULL) {
174         changer_label(slot, *label);
175     }
176     
177     if (desired_label != NULL && strcmp(*label, desired_label) == 0) {
178         /* Got desired label. */
179         return 1;
180     }
181
182     /* Is this actually an acceptable tape? */
183     labelstr = getconf_str(CNF_LABELSTR);
184     if(!match(labelstr, *label)) {
185         *error_message = newvstrallocf(*error_message,
186                               _("%slabel \"%s\" doesn't match \"%s\".\n"),
187                                        *error_message, *label, labelstr);
188
189         return -1;
190     } else {
191         tape_t *tp;
192         if (strcmp(*timestamp, "X") == 0) {
193             /* new, labeled tape. */
194             return 1;
195         }
196         
197         tp = lookup_tapelabel(*label);
198         
199         if(tp == NULL) {
200             *error_message =
201                 newvstrallocf(*error_message, 
202                               _("%slabel \"%s\" matches labelstr but it is" 
203                                 " not listed in the tapelist file.\n"),
204                               *error_message, *label);
205             return -1;
206         } else if(tp != NULL && !reusable_tape(tp)) {
207             *error_message = 
208                 newvstrallocf(*error_message,
209                               _("%sTape with label %s is still active" 
210                                 " and cannot be overwritten.\n"),
211                               *error_message, *label);
212             return -1;
213         }
214     }
215   
216     /* Yay! We got a good tape! */
217     return 2;
218 }
219
220 /* Interface is the same as taper_scan, with some additional bookkeeping. */
221 typedef struct {
222     char *wantlabel;
223     char **gotlabel;
224     char **timestamp;
225     char **error_message;
226     char **tapedev;
227     char *slotstr; /* Best-choice slot number. */
228     char *first_labelstr_slot;
229     int backwards;
230     int tape_status;
231     TaperscanOutputFunctor output_callback;
232     void *output_data;
233     TaperscanProlongFunctor prolong_callback;
234     void * prolong_data;
235     taper_scan_tracker_t * persistent;
236 } changertrack_t;
237
238 int
239 scan_slot(
240      void *data,
241      int rc,
242      char *slotstr,
243      char *device)
244 {
245     int label_result;
246     changertrack_t *ct = ((changertrack_t*)data);
247     int result;
248
249     if (ct->prolong_callback &&
250         !ct->prolong_callback(ct->prolong_data)) {
251         return 1;
252     }
253
254     if (ct->persistent != NULL) {
255         gpointer key;
256         gpointer value;
257         if (g_hash_table_lookup_extended(ct->persistent->scanned_slots,
258                                          slotstr, &key, &value)) {
259             /* We already returned this slot in a previous invocation,
260                skip it now. */
261             return 0;
262         }
263     }
264
265     if (*(ct->error_message) == NULL)
266         *(ct->error_message) = stralloc("");
267
268     switch (rc) {
269     default:
270         *(ct->error_message) = newvstrallocf(*(ct->error_message),
271                    _("%sfatal changer error: slot %s: %s\n"),
272                    *(ct->error_message), slotstr, changer_resultstr);
273         result = 1;
274         break;
275
276     case 1:
277         *(ct->error_message) = newvstrallocf(*(ct->error_message),
278                    _("%schanger error: slot %s: %s\n"),
279                    *(ct->error_message), slotstr, changer_resultstr);
280         result = 0;
281         break;
282
283     case 0:
284         *(ct->error_message) = newvstrallocf(*(ct->error_message),
285                                         _("slot %s:"), slotstr);
286         amfree(*ct->gotlabel);
287         amfree(*ct->timestamp);
288         label_result = scan_read_label(device, slotstr,
289                                        ct->wantlabel, ct->gotlabel,
290                                        ct->timestamp, ct->error_message);
291         if (label_result == 1 || label_result == 3 ||
292             (label_result == 2 && !ct->backwards)) {
293             *(ct->tapedev) = stralloc(device);
294             ct->tape_status = label_result;
295             amfree(ct->slotstr);
296             ct->slotstr = stralloc(slotstr);
297             result = 1;
298         } else {
299             if ((label_result == 2) && (ct->first_labelstr_slot == NULL))
300                 ct->first_labelstr_slot = stralloc(slotstr);
301             result = 0;
302         }
303         break;
304     }
305     ct->output_callback(ct->output_data, *(ct->error_message));
306     amfree(*(ct->error_message));
307     return result;
308 }
309
310 static int 
311 scan_init(
312     void *data,
313     int rc,
314     G_GNUC_UNUSED int nslots,
315     int backwards,
316     G_GNUC_UNUSED int searchable)
317 {
318     changertrack_t *ct = ((changertrack_t*)data);
319
320     if (rc) {
321         *(ct->error_message) = newvstrallocf(*(ct->error_message),
322                 _("%scould not get changer info: %s\n"),
323                 *(ct->error_message), changer_resultstr);
324         ct->output_callback(ct->output_data, *(ct->error_message));
325         amfree(*(ct->error_message));
326     }
327
328     ct->backwards = backwards;
329     return 0;
330 }
331
332 int
333 changer_taper_scan(
334     char *wantlabel,
335     char **gotlabel,
336     char **timestamp,
337     char **tapedev,
338     taper_scan_tracker_t * tracker,
339     TaperscanOutputFunctor taperscan_output_callback,
340     void *output_data,
341     TaperscanProlongFunctor prolong_callback,
342     void * prolong_data)
343 {
344     char *error_message = NULL;
345     changertrack_t local_data;
346     char *outslotstr = NULL;
347     int result;
348
349     *gotlabel = *timestamp = *tapedev = NULL;
350     local_data.wantlabel = wantlabel;
351     local_data.gotlabel  = gotlabel;
352     local_data.timestamp = timestamp;
353     local_data.error_message = &error_message;
354     local_data.tapedev = tapedev;
355     local_data.first_labelstr_slot = NULL;
356     local_data.backwards = 0;
357     local_data.tape_status = 0;
358     local_data.output_callback  = taperscan_output_callback;
359     local_data.output_data = output_data;
360     local_data.prolong_callback = prolong_callback;
361     local_data.prolong_data = prolong_data;
362     local_data.persistent = tracker;
363     local_data.slotstr = NULL;
364
365     changer_find(&local_data, scan_init, scan_slot, wantlabel);
366     
367     if (*(local_data.tapedev)) {
368         /* We got it, and it's loaded. */
369         if (local_data.persistent != NULL && local_data.slotstr != NULL) {
370             g_hash_table_insert(local_data.persistent->scanned_slots,
371                                 local_data.slotstr, NULL);
372         } else {
373             amfree(local_data.slotstr);
374         }
375         amfree(local_data.first_labelstr_slot);
376         return local_data.tape_status;
377     } else if (local_data.first_labelstr_slot) {
378         /* Use plan B. */
379         if (prolong_callback && !prolong_callback(prolong_data)) {
380             return -1;
381         }
382         result = changer_loadslot(local_data.first_labelstr_slot,
383                                   &outslotstr, tapedev);
384         amfree(local_data.first_labelstr_slot);
385         amfree(outslotstr);
386         if (result == 0) {
387             amfree(*gotlabel);
388             amfree(*timestamp);
389             result = scan_read_label(*tapedev, NULL, NULL,
390                                      gotlabel, timestamp,
391                                      &error_message);
392             taperscan_output_callback(output_data, error_message);
393             amfree(error_message);
394             if (result > 0 && local_data.persistent != NULL &&
395                 local_data.slotstr != NULL) {
396                 g_hash_table_insert(local_data.persistent->scanned_slots,
397                                     local_data.slotstr, NULL);
398             } else {
399                 amfree(local_data.slotstr);
400             }
401             return result;
402         }
403     }
404
405     /* Didn't find a tape. :-( */
406     assert(local_data.tape_status <= 0);
407     return -1;
408 }
409
410 int taper_scan(char* wantlabel,
411                char** gotlabel, char** timestamp, char** tapedev,
412                taper_scan_tracker_t * tracker,
413                TaperscanOutputFunctor output_functor,
414                void *output_data,
415                TaperscanProlongFunctor prolong_functor,
416                void *prolong_data) {
417     char *error_message = NULL;
418     int result;
419     *gotlabel = *timestamp = NULL;
420
421     if (wantlabel == NULL) {
422         tape_t *tmp;
423         tmp = lookup_last_reusable_tape(0);
424         if (tmp != NULL) {
425             wantlabel = tmp->label;
426         }
427     }
428
429     if (changer_init()) {
430         result =  changer_taper_scan(wantlabel, gotlabel, timestamp,
431                                      tapedev, tracker,
432                                      output_functor, output_data,
433                                      prolong_functor, prolong_data);
434     } else {
435         /* Note that the tracker is not used in this case. */
436         *tapedev = stralloc(getconf_str(CNF_TAPEDEV));
437         if (*tapedev == NULL) {
438             result = -1;
439             output_functor(output_data, _("No tapedev specified"));
440         } else {
441             result =  scan_read_label(*tapedev, NULL, wantlabel, gotlabel,
442                                       timestamp, &error_message);
443             output_functor(output_data, error_message);
444             amfree(error_message);
445         }
446     }
447
448     return result;
449 }
450
451 #define AUTO_LABEL_MAX_LEN 1024
452 char *
453 find_brand_new_tape_label(void)
454 {
455     char *format;
456     char newlabel[AUTO_LABEL_MAX_LEN];
457     char tmpnum[30]; /* 64-bit integers can be 21 digists... */
458     char tmpfmt[16];
459     char *auto_pos = NULL;
460     int i;
461     ssize_t label_len, auto_len;
462     tape_t *tp;
463
464     if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
465         return NULL;
466     }
467     format = getconf_str(CNF_LABEL_NEW_TAPES);
468
469     memset(newlabel, 0, AUTO_LABEL_MAX_LEN);
470     label_len = 0;
471     auto_len = -1; /* Only find the first '%' */
472     while (*format != '\0') {
473         if (label_len + 4 > AUTO_LABEL_MAX_LEN) {
474             g_fprintf(stderr, _("Auto label format is too long!\n"));
475             return NULL;
476         }
477
478         if (*format == '\\') {
479             /* Copy the next character. */
480             newlabel[label_len++] = format[1];
481             format += 2;
482         } else if (*format == '%' && auto_len == -1) {
483             /* This is the format specifier. */
484             auto_pos = newlabel + label_len;
485             auto_len = 0;
486             while (*format == '%' && label_len < AUTO_LABEL_MAX_LEN) {
487                 newlabel[label_len++] = '%';
488                 auto_len ++;
489                 format ++;
490             }
491         } else {
492             /* Just copy a character. */
493             newlabel[label_len++] = *(format++);
494         }     
495     }
496
497     /* Sometimes we copy the null, sometimes not. */
498     if (newlabel[label_len] != '\0') {
499         newlabel[label_len++] = '\0';
500     }
501
502     if (auto_pos == NULL) {
503         g_fprintf(stderr, _("Auto label template contains no '%%'!\n"));
504         return NULL;
505     }
506
507     g_snprintf(tmpfmt, SIZEOF(tmpfmt), "%%0%zdd",
508              (size_t)auto_len);
509
510     for (i = 1; i < INT_MAX; i ++) {
511         g_snprintf(tmpnum, SIZEOF(tmpnum), tmpfmt, i);
512         if (strlen(tmpnum) != (size_t)auto_len) {
513             g_fprintf(stderr, _("All possible auto-labels used.\n"));
514             return NULL;
515         }
516
517         strncpy(auto_pos, tmpnum, (size_t)auto_len);
518
519         tp = lookup_tapelabel(newlabel);
520         if (tp == NULL) {
521             /* Got it. Double-check that this is a labelstr match. */
522             if (!match(getconf_str(CNF_LABELSTR), newlabel)) {
523                 g_fprintf(stderr, _("New label %s does not match labelstr %s from amanda.conf\n"),
524                         newlabel, getconf_str(CNF_LABELSTR));
525                 return 0;
526             }
527             return stralloc(newlabel);
528         }
529     }
530
531     /* Should not get here unless you have over two billion tapes. */
532     g_fprintf(stderr, _("Taper internal error in find_brand_new_tape_label."));
533     return 0;
534 }
535
536 void
537 FILE_taperscan_output_callback(
538     void *data,
539     char *msg)
540 {
541     if(!msg) return;
542     if(strlen(msg) == 0) return;
543
544     if(data)
545         g_fprintf((FILE *)data, "%s", msg);
546     else
547         g_printf("%s", msg);
548 }
549
550 void
551 CHAR_taperscan_output_callback(
552     /*@keep@*/  void *data,
553                 char *msg)
554 {
555     char **s = (char **)data;
556
557     if(!msg) return;
558     if(strlen(msg) == 0) return;
559
560     if(*s)
561         strappend(*s, msg);
562     else
563         *s = stralloc(msg);
564 }
565
566 taper_scan_tracker_t * taper_scan_tracker_new(void) {
567     taper_scan_tracker_t * rval = malloc(sizeof(*rval));
568     
569     rval->scanned_slots = g_hash_table_new_full(g_str_hash, g_str_equal,
570                                                 g_free, NULL);
571
572     return rval;
573 }
574
575 void taper_scan_tracker_free(taper_scan_tracker_t * tracker) {
576     if (tracker->scanned_slots != NULL) {
577         g_hash_table_destroy(tracker->scanned_slots);
578     }
579     
580     free(tracker);
581 }
582