Imported Upstream version 2.6.0
[debian/amanda] / server-src / taperscan.c
1 /*
2  * Copyright (c) 2005 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, 505 N Mathlida Ave, Suite 120
18  * Sunnyvale, CA 94085, 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     ReadLabelStatusFlags label_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
92     device = device_open(dev);
93     if (device == NULL ) {
94         *error_message = newvstrallocf(*error_message,
95                                        _("%sError opening device %s.\n"),
96                                        *error_message, dev);
97         amfree(*timestamp);
98         amfree(*label);
99         return -1;
100     }
101
102     device_set_startup_properties_from_config(device);
103
104     label_status = device_read_label(device);
105     g_assert((device->volume_label != NULL) ==
106              (label_status == READ_LABEL_STATUS_SUCCESS));
107     
108     if (device->volume_label != NULL) { 
109         *label = g_strdup(device->volume_label);
110         *timestamp = strdup(device->volume_time);
111     } else if (label_status & READ_LABEL_STATUS_VOLUME_UNLABELED) {
112         g_object_unref(device);
113         if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
114             *error_message = newvstrallocf(*error_message,
115                                            _("%sFound a non-amanda tape.\n"),
116                                            *error_message);
117
118             return -1;
119         }
120         *label = find_brand_new_tape_label();
121         if (*label != NULL) {
122             *timestamp = stralloc("X");
123             *error_message = newvstrallocf(*error_message,
124                      _("%sFound a non-amanda tape, will label it `%s'.\n"),
125                                            *error_message, *label);
126
127             return 3;
128         }
129         *error_message = newvstrallocf(*error_message,
130                  _("%sFound a non-amanda tape, but have no labels left.\n"),
131                                        *error_message);
132
133         return -1;
134     } else {
135         char * label_errstr;
136         char ** label_strv =
137             g_flags_nick_to_strv(label_status, READ_LABEL_STATUS_FLAGS_TYPE);
138         
139         switch (g_strv_length(label_strv)) {
140         case 0:
141             label_errstr = g_strdup(_("Unknown error reading volume label.\n"));
142             break;
143
144         case 1:
145             label_errstr =
146                 g_strdup_printf(_("Error reading volume label: %s\n"),
147                                 *label_strv);
148             break;
149
150         default:
151             {
152                 char * tmp_str = g_english_strjoinv(label_strv, "or");
153                 label_errstr =
154                     g_strdup_printf(_("Error reading label: One of %s\n"),
155                                     tmp_str);
156                 g_free(tmp_str);
157             }
158         }
159         
160         g_strfreev(label_strv);
161
162         *error_message = newvstralloc(*error_message, *error_message,
163                                       label_errstr, NULL);
164         g_free(label_errstr);
165         return -1;
166     }
167
168     g_assert(*label != NULL && *timestamp != NULL);
169     g_object_unref(device);
170
171     *error_message = newvstrallocf(*error_message,
172                                    _("%sread label `%s', date `%s'.\n"),
173                                    *error_message, *label, *timestamp);
174
175     /* Register this with the barcode database, even if its not ours. */
176     if (slot != NULL) {
177         changer_label(slot, *label);
178     }
179     
180     if (desired_label != NULL && strcmp(*label, desired_label) == 0) {
181         /* Got desired label. */
182         return 1;
183     }
184
185     /* Is this actually an acceptable tape? */
186     labelstr = getconf_str(CNF_LABELSTR);
187     if(!match(labelstr, *label)) {
188         *error_message = newvstrallocf(*error_message,
189                               _("%slabel \"%s\" doesn't match \"%s\".\n"),
190                                        *error_message, *label, labelstr);
191
192         return -1;
193     } else {
194         tape_t *tp;
195         if (strcmp(*timestamp, "X") == 0) {
196             /* new, labeled tape. */
197             return 1;
198         }
199         
200         tp = lookup_tapelabel(*label);
201         
202         if(tp == NULL) {
203             *error_message =
204                 newvstrallocf(*error_message, 
205                               _("%slabel \"%s\" matches labelstr but it is" 
206                                 " not listed in the tapelist file.\n"),
207                               *error_message, *label);
208             return -1;
209         } else if(tp != NULL && !reusable_tape(tp)) {
210             *error_message = 
211                 newvstrallocf(*error_message,
212                               _("%sTape with label %s is still active" 
213                                 " and cannot be overwriten.\n"),
214                               *error_message, *label);
215             return -1;
216         }
217     }
218   
219     /* Yay! We got a good tape! */
220     return 2;
221 }
222
223 /* Interface is the same as taper_scan, with some additional bookkeeping. */
224 typedef struct {
225     char *wantlabel;
226     char **gotlabel;
227     char **timestamp;
228     char **error_message;
229     char **tapedev;
230     char *slotstr; /* Best-choice slot number. */
231     char *first_labelstr_slot;
232     int backwards;
233     int tape_status;
234     TaperscanOutputFunctor output_callback;
235     void *output_data;
236     TaperscanProlongFunctor prolong_callback;
237     void * prolong_data;
238     taper_scan_tracker_t * persistent;
239 } changertrack_t;
240
241 int
242 scan_slot(
243      void *data,
244      int rc,
245      char *slotstr,
246      char *device)
247 {
248     int label_result;
249     changertrack_t *ct = ((changertrack_t*)data);
250     int result;
251
252     if (ct->prolong_callback &&
253         !ct->prolong_callback(ct->prolong_data)) {
254         return 1;
255     }
256
257     if (ct->persistent != NULL) {
258         gpointer key;
259         gpointer value;
260         if (g_hash_table_lookup_extended(ct->persistent->scanned_slots,
261                                          slotstr, &key, &value)) {
262             /* We already returned this slot in a previous invocation,
263                skip it now. */
264             return 0;
265         }
266     }
267
268     if (*(ct->error_message) == NULL)
269         *(ct->error_message) = stralloc("");
270
271     switch (rc) {
272     default:
273         *(ct->error_message) = newvstrallocf(*(ct->error_message),
274                    _("%sfatal changer error: slot %s: %s\n"),
275                    *(ct->error_message), slotstr, changer_resultstr);
276         result = 1;
277         break;
278
279     case 1:
280         *(ct->error_message) = newvstrallocf(*(ct->error_message),
281                    _("%schanger error: slot %s: %s\n"),
282                    *(ct->error_message), slotstr, changer_resultstr);
283         result = 0;
284         break;
285
286     case 0:
287         *(ct->error_message) = newvstrallocf(*(ct->error_message),
288                                         _("slot %s:"), slotstr);
289         amfree(*ct->gotlabel);
290         amfree(*ct->timestamp);
291         label_result = scan_read_label(device, slotstr,
292                                        ct->wantlabel, ct->gotlabel,
293                                        ct->timestamp, ct->error_message);
294         if (label_result == 1 || label_result == 3 ||
295             (label_result == 2 && !ct->backwards)) {
296             *(ct->tapedev) = stralloc(device);
297             ct->tape_status = label_result;
298             amfree(ct->slotstr);
299             ct->slotstr = stralloc(slotstr);
300             result = 1;
301         } else {
302             if ((label_result == 2) && (ct->first_labelstr_slot == NULL))
303                 ct->first_labelstr_slot = stralloc(slotstr);
304             result = 0;
305         }
306         break;
307     }
308     ct->output_callback(ct->output_data, *(ct->error_message));
309     amfree(*(ct->error_message));
310     return result;
311 }
312
313 static int 
314 scan_init(
315     void *data,
316     int rc,
317     G_GNUC_UNUSED int nslots,
318     int backwards,
319     G_GNUC_UNUSED int searchable)
320 {
321     changertrack_t *ct = ((changertrack_t*)data);
322
323     if (rc) {
324         *(ct->error_message) = newvstrallocf(*(ct->error_message),
325                 _("%scould not get changer info: %s\n"),
326                 *(ct->error_message), changer_resultstr);
327         ct->output_callback(ct->output_data, *(ct->error_message));
328         amfree(*(ct->error_message));
329     }
330
331     ct->backwards = backwards;
332     return 0;
333 }
334
335 int
336 changer_taper_scan(
337     char *wantlabel,
338     char **gotlabel,
339     char **timestamp,
340     char **tapedev,
341     taper_scan_tracker_t * tracker,
342     TaperscanOutputFunctor taperscan_output_callback,
343     void *output_data,
344     TaperscanProlongFunctor prolong_callback,
345     void * prolong_data)
346 {
347     char *error_message = NULL;
348     changertrack_t local_data;
349     char *outslotstr = NULL;
350     int result;
351
352     *gotlabel = *timestamp = *tapedev = NULL;
353     local_data.wantlabel = wantlabel;
354     local_data.gotlabel  = gotlabel;
355     local_data.timestamp = timestamp;
356     local_data.error_message = &error_message;
357     local_data.tapedev = tapedev;
358     local_data.first_labelstr_slot = NULL;
359     local_data.backwards = 0;
360     local_data.tape_status = 0;
361     local_data.output_callback  = taperscan_output_callback;
362     local_data.output_data = output_data;
363     local_data.prolong_callback = prolong_callback;
364     local_data.prolong_data = prolong_data;
365     local_data.persistent = tracker;
366     local_data.slotstr = NULL;
367
368     changer_find(&local_data, scan_init, scan_slot, wantlabel);
369     
370     if (*(local_data.tapedev)) {
371         /* We got it, and it's loaded. */
372         if (local_data.persistent != NULL && local_data.slotstr != NULL) {
373             g_hash_table_insert(local_data.persistent->scanned_slots,
374                                 local_data.slotstr, NULL);
375         } else {
376             amfree(local_data.slotstr);
377         }
378         amfree(local_data.first_labelstr_slot);
379         return local_data.tape_status;
380     } else if (local_data.first_labelstr_slot) {
381         /* Use plan B. */
382         if (prolong_callback && !prolong_callback(prolong_data)) {
383             return -1;
384         }
385         result = changer_loadslot(local_data.first_labelstr_slot,
386                                   &outslotstr, tapedev);
387         amfree(local_data.first_labelstr_slot);
388         amfree(outslotstr);
389         if (result == 0) {
390             amfree(*gotlabel);
391             amfree(*timestamp);
392             result = scan_read_label(*tapedev, NULL, NULL,
393                                      gotlabel, timestamp,
394                                      &error_message);
395             taperscan_output_callback(output_data, error_message);
396             amfree(error_message);
397             if (result > 0 && local_data.persistent != NULL &&
398                 local_data.slotstr != NULL) {
399                 g_hash_table_insert(local_data.persistent->scanned_slots,
400                                     local_data.slotstr, NULL);
401             } else {
402                 amfree(local_data.slotstr);
403             }
404             return result;
405         }
406     }
407
408     /* Didn't find a tape. :-( */
409     assert(local_data.tape_status <= 0);
410     return -1;
411 }
412
413 int taper_scan(char* wantlabel,
414                char** gotlabel, char** timestamp, char** tapedev,
415                taper_scan_tracker_t * tracker,
416                TaperscanOutputFunctor output_functor,
417                void *output_data,
418                TaperscanProlongFunctor prolong_functor,
419                void *prolong_data) {
420     char *error_message = NULL;
421     int result;
422     *gotlabel = *timestamp = NULL;
423
424     if (wantlabel == NULL) {
425         tape_t *tmp;
426         tmp = lookup_last_reusable_tape(0);
427         if (tmp != NULL) {
428             wantlabel = tmp->label;
429         }
430     }
431
432     if (changer_init()) {
433         result =  changer_taper_scan(wantlabel, gotlabel, timestamp,
434                                      tapedev, tracker,
435                                      output_functor, output_data,
436                                      prolong_functor, prolong_data);
437     } else {
438         /* Note that the tracker is not used in this case. */
439         *tapedev = stralloc(getconf_str(CNF_TAPEDEV));
440         if (*tapedev == NULL) {
441             result = -1;
442             output_functor(output_data, _("No tapedev specified"));
443         } else {
444             result =  scan_read_label(*tapedev, NULL, wantlabel, gotlabel,
445                                       timestamp, &error_message);
446             output_functor(output_data, error_message);
447             amfree(error_message);
448         }
449     }
450
451     return result;
452 }
453
454 #define AUTO_LABEL_MAX_LEN 1024
455 char *
456 find_brand_new_tape_label(void)
457 {
458     char *format;
459     char newlabel[AUTO_LABEL_MAX_LEN];
460     char tmpnum[30]; /* 64-bit integers can be 21 digists... */
461     char tmpfmt[16];
462     char *auto_pos = NULL;
463     int i;
464     ssize_t label_len, auto_len;
465     tape_t *tp;
466
467     if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
468         return NULL;
469     }
470     format = getconf_str(CNF_LABEL_NEW_TAPES);
471
472     memset(newlabel, 0, AUTO_LABEL_MAX_LEN);
473     label_len = 0;
474     auto_len = -1; /* Only find the first '%' */
475     while (*format != '\0') {
476         if (label_len + 4 > AUTO_LABEL_MAX_LEN) {
477             g_fprintf(stderr, _("Auto label format is too long!\n"));
478             return NULL;
479         }
480
481         if (*format == '\\') {
482             /* Copy the next character. */
483             newlabel[label_len++] = format[1];
484             format += 2;
485         } else if (*format == '%' && auto_len == -1) {
486             /* This is the format specifier. */
487             auto_pos = newlabel + label_len;
488             auto_len = 0;
489             while (*format == '%' && label_len < AUTO_LABEL_MAX_LEN) {
490                 newlabel[label_len++] = '%';
491                 auto_len ++;
492                 format ++;
493             }
494         } else {
495             /* Just copy a character. */
496             newlabel[label_len++] = *(format++);
497         }     
498     }
499
500     /* Sometimes we copy the null, sometimes not. */
501     if (newlabel[label_len] != '\0') {
502         newlabel[label_len++] = '\0';
503     }
504
505     if (auto_pos == NULL) {
506         g_fprintf(stderr, _("Auto label template contains no '%%'!\n"));
507         return NULL;
508     }
509
510     g_snprintf(tmpfmt, SIZEOF(tmpfmt), "%%0%zdd",
511              (size_t)auto_len);
512
513     for (i = 1; i < INT_MAX; i ++) {
514         g_snprintf(tmpnum, SIZEOF(tmpnum), tmpfmt, i);
515         if (strlen(tmpnum) != (size_t)auto_len) {
516             g_fprintf(stderr, _("All possible auto-labels used.\n"));
517             return NULL;
518         }
519
520         strncpy(auto_pos, tmpnum, (size_t)auto_len);
521
522         tp = lookup_tapelabel(newlabel);
523         if (tp == NULL) {
524             /* Got it. Double-check that this is a labelstr match. */
525             if (!match(getconf_str(CNF_LABELSTR), newlabel)) {
526                 g_fprintf(stderr, _("New label %s does not match labelstr %s from amanda.conf\n"),
527                         newlabel, getconf_str(CNF_LABELSTR));
528                 return 0;
529             }
530             return stralloc(newlabel);
531         }
532     }
533
534     /* Should not get here unless you have over two billion tapes. */
535     g_fprintf(stderr, _("Taper internal error in find_brand_new_tape_label."));
536     return 0;
537 }
538
539 void
540 FILE_taperscan_output_callback(
541     void *data,
542     char *msg)
543 {
544     if(!msg) return;
545     if(strlen(msg) == 0) return;
546
547     if(data)
548         g_fprintf((FILE *)data, "%s", msg);
549     else
550         g_printf("%s", msg);
551 }
552
553 void
554 CHAR_taperscan_output_callback(
555     /*@keep@*/  void *data,
556                 char *msg)
557 {
558     char **s = (char **)data;
559
560     if(!msg) return;
561     if(strlen(msg) == 0) return;
562
563     if(*s)
564         strappend(*s, msg);
565     else
566         *s = stralloc(msg);
567 }
568
569 taper_scan_tracker_t * taper_scan_tracker_new(void) {
570     taper_scan_tracker_t * rval = malloc(sizeof(*rval));
571     
572     rval->scanned_slots = g_hash_table_new_full(g_str_hash, g_str_equal,
573                                                 g_free, NULL);
574
575     return rval;
576 }
577
578 void taper_scan_tracker_free(taper_scan_tracker_t * tracker) {
579     if (tracker->scanned_slots != NULL) {
580         g_hash_table_destroy(tracker->scanned_slots);
581     }
582     
583     free(tracker);
584 }
585