2 * Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
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.
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
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
17 * Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
18 * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
22 * $Id: taperscan.c,v 1.17 2006/07/12 12:28:19 martinea Exp $
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. */
33 #include "timestamp.h"
34 #include "taperscan.h"
36 struct taper_scan_tracker_s {
37 GHashTable * scanned_slots;
40 int scan_read_label (char *dev, char * slot, char *wantlabel,
41 char** label, char** timestamp,
43 int changer_taper_scan (char *wantlabel, char** gotlabel, char** timestamp,
44 char **tapedev, taper_scan_tracker_t * tracker,
45 TaperscanOutputFunctor output_functor,
47 TaperscanProlongFunctor prolong_functor,
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);
54 /* NO GLOBALS PLEASE! */
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
64 * 4) At this point, we give up.
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.
83 DeviceStatusFlags device_status;
85 g_return_val_if_fail(dev != NULL, -1);
87 if (*error_message == NULL)
88 *error_message = stralloc("");
90 *label = *timestamp = NULL;
91 device = device_open(dev);
92 g_assert(device != NULL);
94 if (device->status != DEVICE_STATUS_SUCCESS ) {
95 *error_message = newvstrallocf(*error_message,
96 _("%sError opening device %s: %s.\n"),
98 device_error_or_status(device));
99 g_object_unref(device);
105 if (!device_configure(device, TRUE)) {
106 *error_message = newvstrallocf(*error_message,
107 _("%sError configuring device %s: %s.\n"),
109 device_error_or_status(device));
110 g_object_unref(device);
116 device_status = device_read_label(device);
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"),
126 g_object_unref(device);
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"),
136 g_object_unref(device);
139 g_object_unref(device);
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);
150 *error_message = newvstrallocf(*error_message,
151 _("%sFound an empty tape, but have no labels left.\n"),
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,
161 g_free(label_errstr);
165 g_assert(*label != NULL && *timestamp != NULL);
166 g_object_unref(device);
168 *error_message = newvstrallocf(*error_message,
169 _("%sread label `%s', date `%s'.\n"),
170 *error_message, *label, *timestamp);
172 /* Register this with the barcode database, even if its not ours. */
174 changer_label(slot, *label);
177 if (desired_label != NULL && strcmp(*label, desired_label) == 0) {
178 /* Got desired label. */
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);
192 if (strcmp(*timestamp, "X") == 0) {
193 /* new, labeled tape. */
197 tp = lookup_tapelabel(*label);
201 newvstrallocf(*error_message,
202 _("%slabel \"%s\" matches labelstr but it is"
203 " not listed in the tapelist file.\n"),
204 *error_message, *label);
206 } else if(tp != NULL && !reusable_tape(tp)) {
208 newvstrallocf(*error_message,
209 _("%sTape with label %s is still active"
210 " and cannot be overwritten.\n"),
211 *error_message, *label);
216 /* Yay! We got a good tape! */
220 /* Interface is the same as taper_scan, with some additional bookkeeping. */
225 char **error_message;
227 char *slotstr; /* Best-choice slot number. */
228 char *first_labelstr_slot;
231 TaperscanOutputFunctor output_callback;
233 TaperscanProlongFunctor prolong_callback;
235 taper_scan_tracker_t * persistent;
246 changertrack_t *ct = ((changertrack_t*)data);
249 if (ct->prolong_callback &&
250 !ct->prolong_callback(ct->prolong_data)) {
254 if (ct->persistent != NULL) {
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,
265 if (*(ct->error_message) == NULL)
266 *(ct->error_message) = stralloc("");
270 *(ct->error_message) = newvstrallocf(*(ct->error_message),
271 _("%sfatal changer error: slot %s: %s\n"),
272 *(ct->error_message), slotstr, changer_resultstr);
277 *(ct->error_message) = newvstrallocf(*(ct->error_message),
278 _("%schanger error: slot %s: %s\n"),
279 *(ct->error_message), slotstr, changer_resultstr);
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;
296 ct->slotstr = stralloc(slotstr);
299 if ((label_result == 2) && (ct->first_labelstr_slot == NULL))
300 ct->first_labelstr_slot = stralloc(slotstr);
305 ct->output_callback(ct->output_data, *(ct->error_message));
306 amfree(*(ct->error_message));
314 G_GNUC_UNUSED int nslots,
316 G_GNUC_UNUSED int searchable)
318 changertrack_t *ct = ((changertrack_t*)data);
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));
328 ct->backwards = backwards;
338 taper_scan_tracker_t * tracker,
339 TaperscanOutputFunctor taperscan_output_callback,
341 TaperscanProlongFunctor prolong_callback,
344 char *error_message = NULL;
345 changertrack_t local_data;
346 char *outslotstr = NULL;
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;
365 changer_find(&local_data, scan_init, scan_slot, wantlabel);
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);
373 amfree(local_data.slotstr);
375 amfree(local_data.first_labelstr_slot);
376 return local_data.tape_status;
377 } else if (local_data.first_labelstr_slot) {
379 if (prolong_callback && !prolong_callback(prolong_data)) {
382 result = changer_loadslot(local_data.first_labelstr_slot,
383 &outslotstr, tapedev);
384 amfree(local_data.first_labelstr_slot);
389 result = scan_read_label(*tapedev, NULL, NULL,
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);
399 amfree(local_data.slotstr);
405 /* Didn't find a tape. :-( */
406 assert(local_data.tape_status <= 0);
410 int taper_scan(char* wantlabel,
411 char** gotlabel, char** timestamp, char** tapedev,
412 taper_scan_tracker_t * tracker,
413 TaperscanOutputFunctor output_functor,
415 TaperscanProlongFunctor prolong_functor,
416 void *prolong_data) {
417 char *error_message = NULL;
419 *gotlabel = *timestamp = NULL;
421 if (wantlabel == NULL) {
423 tmp = lookup_last_reusable_tape(0);
425 wantlabel = tmp->label;
429 if (changer_init()) {
430 result = changer_taper_scan(wantlabel, gotlabel, timestamp,
432 output_functor, output_data,
433 prolong_functor, prolong_data);
435 /* Note that the tracker is not used in this case. */
436 *tapedev = stralloc(getconf_str(CNF_TAPEDEV));
437 if (*tapedev == NULL) {
439 output_functor(output_data, _("No tapedev specified"));
441 result = scan_read_label(*tapedev, NULL, wantlabel, gotlabel,
442 timestamp, &error_message);
443 output_functor(output_data, error_message);
444 amfree(error_message);
451 #define AUTO_LABEL_MAX_LEN 1024
453 find_brand_new_tape_label(void)
456 char newlabel[AUTO_LABEL_MAX_LEN];
457 char tmpnum[30]; /* 64-bit integers can be 21 digists... */
459 char *auto_pos = NULL;
461 ssize_t label_len, auto_len;
464 if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
467 format = getconf_str(CNF_LABEL_NEW_TAPES);
469 memset(newlabel, 0, AUTO_LABEL_MAX_LEN);
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"));
478 if (*format == '\\') {
479 /* Copy the next character. */
480 newlabel[label_len++] = format[1];
482 } else if (*format == '%' && auto_len == -1) {
483 /* This is the format specifier. */
484 auto_pos = newlabel + label_len;
486 while (*format == '%' && label_len < AUTO_LABEL_MAX_LEN) {
487 newlabel[label_len++] = '%';
492 /* Just copy a character. */
493 newlabel[label_len++] = *(format++);
497 /* Sometimes we copy the null, sometimes not. */
498 if (newlabel[label_len] != '\0') {
499 newlabel[label_len++] = '\0';
502 if (auto_pos == NULL) {
503 g_fprintf(stderr, _("Auto label template contains no '%%'!\n"));
507 g_snprintf(tmpfmt, SIZEOF(tmpfmt), "%%0%zdd",
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"));
517 strncpy(auto_pos, tmpnum, (size_t)auto_len);
519 tp = lookup_tapelabel(newlabel);
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));
527 return stralloc(newlabel);
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."));
537 FILE_taperscan_output_callback(
542 if(strlen(msg) == 0) return;
545 g_fprintf((FILE *)data, "%s", msg);
551 CHAR_taperscan_output_callback(
552 /*@keep@*/ void *data,
555 char **s = (char **)data;
558 if(strlen(msg) == 0) return;
566 taper_scan_tracker_t * taper_scan_tracker_new(void) {
567 taper_scan_tracker_t * rval = malloc(sizeof(*rval));
569 rval->scanned_slots = g_hash_table_new_full(g_str_hash, g_str_equal,
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);