Imported Upstream version 2.5.2p1
[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 as published by the
6  * Free Software Foundation; either version 2 of the License, or (at your
7  * option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  * 
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17  * 
18  * Contact information: Zmanda Inc, 505 N Mathlida Ave, Suite 120
19  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20  */
21
22 /*
23  * $Id: taperscan.c,v 1.17 2006/07/12 12:28:19 martinea Exp $
24  *
25  * This contains the implementation of the taper-scan algorithm, as it is
26  * used by taper, amcheck, and amtape. See the header file taperscan.h for
27  * interface information. */
28
29 #include "amanda.h"
30 #include "tapeio.h"
31 #include "conffile.h"
32 #include "changer.h"
33 #include "tapefile.h"
34
35 int scan_read_label (char *dev, char *wantlabel,
36                        char** label, char** timestamp,
37                        char**error_message);
38 int changer_taper_scan (char *wantlabel, char** gotlabel, char** timestamp,
39                         char **tapedev, void (*)(void *data, char *msg),
40                         void *data);
41 int scan_slot (void *data, int rc, char *slotstr, char *device);
42 int taper_scan (char* wantlabel, char** gotlabel, char** timestamp,
43                 char** tapedev,
44                 void taperscan_output_callback(void *data, char *msg),
45                 void *data);
46 char *find_brand_new_tape_label (void);
47 void FILE_taperscan_output_callback (void *data, char *msg);
48 void CHAR_taperscan_output_callback (void *data, char *msg);
49
50 /* NO GLOBALS PLEASE! */
51
52 /* How does the taper scan algorithm work? Like this:
53  * 1) If there is a barcode reader, and we have a recyclable tape, use the
54  *    reader to load the oldest tape.
55  * 2) Otherwise, search through the changer until we find a new tape
56  *    or the oldest recyclable tape.
57  * 3) If we couldn't find the oldest recyclable tape or a new tape,
58  *    but if in #2 we found *some* recyclable tape, use the oldest one we
59  *    found.
60  * 4) At this point, we give up.
61  */
62
63 /* This function checks the label of a single tape, which may or may not
64  * have been loaded by the changer. With the addition of char *dev, it has
65  * the same interface as taper_scan. 
66  * Return value is the same as taper_scan.
67  */
68 int scan_read_label(
69     char *dev,
70     char *desired_label,
71     char** label,
72     char** timestamp,
73     char** error_message)
74 {
75     char *result = NULL;
76
77     *label = *timestamp = NULL;
78     result = tape_rdlabel(dev, timestamp, label);
79     if (result != NULL) {
80         if (CHECK_NOT_AMANDA_TAPE_MSG(result) &&
81             getconf_seen(CNF_LABEL_NEW_TAPES)) {
82             amfree(result);
83             
84             *label = find_brand_new_tape_label();
85             if (*label != NULL) {
86                 *timestamp = stralloc("X");
87                 vstrextend(error_message,
88                            "Found a non-amanda tape, will label it `",
89                            *label, "'.\n", NULL);
90                 return 3;
91             }
92             vstrextend(error_message,
93                        "Found a non-amanda tape, but have no labels left.\n",
94                         NULL);
95             return -1;
96         }
97         amfree(*timestamp);
98         amfree(*label);
99         vstrextend(error_message, result, "\n", NULL);
100         amfree(result);
101         return -1;
102     }
103     
104     if ((*label == NULL) || (*timestamp == NULL)) {
105         error("Invalid return from tape_rdlabel");
106     }
107
108     vstrextend(error_message, "read label `", *label, "', date `",
109                *timestamp, "'\n", NULL);
110
111     if (desired_label != NULL && strcmp(*label, desired_label) == 0) {
112         /* Got desired label. */
113         return 1;
114     }
115
116     /* Is this actually an acceptable tape? */
117     if (strcmp(*label, FAKE_LABEL) != 0) {
118         char *labelstr;
119         labelstr = getconf_str(CNF_LABELSTR);
120         if(!match(labelstr, *label)) {
121             vstrextend(error_message, "label ", *label,
122                        " doesn\'t match labelstr \"",
123                        labelstr, "\"\n", NULL);
124             return -1;
125         } else {
126             tape_t *tp;
127             if (strcmp(*timestamp, "X") == 0) {
128                 /* new, labeled tape. */
129                 return 1;
130             }
131
132             tp = lookup_tapelabel(*label);
133          
134             if(tp == NULL) {
135                 vstrextend(error_message, "label ", *label,
136                      " match labelstr but it not listed in the tapelist file.\n",
137                            NULL);
138                 return -1;
139             } else if(tp != NULL && !reusable_tape(tp)) {
140                 vstrextend(error_message, "cannot overwrite active tape ", *label,
141                            "\n", NULL);
142                 return -1;
143             }
144         }
145     }
146   
147     /* Yay! We got a good tape! */
148     return 2;
149 }
150
151 /* Interface is the same as taper_scan, with the addition of the tapedev
152  * output. */
153 typedef struct {
154     char *wantlabel;
155     char **gotlabel;
156     char **timestamp;
157     char **error_message;
158     char **tapedev;
159     char *first_labelstr_slot;
160     int backwards;
161     int tape_status;
162     void (*taperscan_output_callback)(void *data, char *msg);
163     void *data;
164 } changertrack_t;
165
166 int
167 scan_slot(
168      void *data,
169      int rc,
170      char *slotstr,
171      char *device)
172 {
173     int label_result;
174     changertrack_t *ct = ((changertrack_t*)data);
175     int result;
176
177     switch (rc) {
178     default:
179         vstrextend(ct->error_message,
180                    "fatal changer error: slot ", slotstr, ": ",
181                    changer_resultstr, "\n", NULL);
182         result = 1;
183         break;
184
185     case 1:
186         vstrextend(ct->error_message,
187                    "changer error: slot ", slotstr, ": ", changer_resultstr,
188                    "\n", NULL);
189         result = 0;
190         break;
191
192     case 0:
193         *(ct->error_message) = newvstralloc(*(ct->error_message), "slot ",
194                                             slotstr, ": ", NULL);
195         amfree(*ct->gotlabel);
196         amfree(*ct->timestamp);
197         label_result = scan_read_label(device, ct->wantlabel, ct->gotlabel,
198                                        ct->timestamp, ct->error_message);
199         if (label_result == 1 || label_result == 3 ||
200             (label_result == 2 && !ct->backwards)) {
201             *(ct->tapedev) = stralloc(device);
202             ct->tape_status = label_result;
203             result = 1;
204         } else {
205             if ((label_result == 2) && (ct->first_labelstr_slot == NULL))
206                 ct->first_labelstr_slot = stralloc(slotstr);
207             result = 0;
208         }
209         break;
210     }
211     ct->taperscan_output_callback(ct->data, *(ct->error_message));
212     amfree(*(ct->error_message));
213     return result;
214 }
215
216 static int 
217 scan_init(
218     void *data,
219     int rc,
220     int nslots,
221     int backwards,
222     int searchable)
223 {
224     changertrack_t *ct = ((changertrack_t*)data);
225     
226     (void)nslots;       /* Quiet unused parameter warning */
227     (void)searchable;   /* Quiet unused parameter warning */
228
229     if (rc) {
230         vstrextend(ct->error_message,
231                    "could not get changer info: ", changer_resultstr, "\n",
232                    NULL);
233         ct->taperscan_output_callback(ct->data, *(ct->error_message));
234         amfree(*(ct->error_message));
235     }
236
237     ct->backwards = backwards;
238     return 0;
239 }
240
241 int
242 changer_taper_scan(
243     char *wantlabel,
244     char **gotlabel,
245     char **timestamp,
246     char **tapedev,
247     void (*taperscan_output_callback)(void *data, char *msg),
248     void *data)
249 {
250     char *error_message = NULL;
251     changertrack_t local_data;
252     char *outslotstr = NULL;
253     int result;
254
255     *gotlabel = *timestamp = *tapedev = NULL;
256     local_data.wantlabel = wantlabel;
257     local_data.gotlabel  = gotlabel;
258     local_data.timestamp = timestamp;
259     local_data.error_message = &error_message;
260     local_data.tapedev = tapedev;
261     local_data.first_labelstr_slot = NULL;
262     local_data.backwards = 0;
263     local_data.tape_status = 0;
264     local_data.taperscan_output_callback  = taperscan_output_callback;
265     local_data.data = data;
266
267     changer_find(&local_data, scan_init, scan_slot, wantlabel);
268     
269     if (*(local_data.tapedev)) {
270         /* We got it, and it's loaded. */
271         return local_data.tape_status;
272     } else if (local_data.first_labelstr_slot) {
273         /* Use plan B. */
274         result = changer_loadslot(local_data.first_labelstr_slot,
275                                   &outslotstr, tapedev);
276         amfree(outslotstr);
277         if (result == 0) {
278             result = scan_read_label(*tapedev, NULL, gotlabel, timestamp,
279                                      &error_message);
280             taperscan_output_callback(data, error_message);
281             amfree(error_message);
282             return result;
283         }
284     }
285
286     /* Didn't find a tape. :-( */
287     assert(local_data.tape_status <= 0);
288     taperscan_output_callback(data, "changer problem: ");
289     taperscan_output_callback(data, changer_resultstr);
290     return -1;
291 }
292
293 int taper_scan(char* wantlabel,
294                char** gotlabel, char** timestamp, char** tapedev,
295                void (*taperscan_output_callback)(void *data, char *msg),
296                void *data) {
297
298     char *error_message = NULL;
299     int result;
300     *gotlabel = *timestamp = NULL;
301
302     if (wantlabel == NULL) {
303         tape_t *tmp;
304         tmp = lookup_last_reusable_tape(0);
305         if (tmp != NULL) {
306             wantlabel = tmp->label;
307         }
308     }
309
310     if (changer_init()) {
311         result =  changer_taper_scan(wantlabel, gotlabel, timestamp,
312                                      tapedev,
313                                      taperscan_output_callback, data);
314     }
315     else {
316         *tapedev = stralloc(getconf_str(CNF_TAPEDEV));
317         if (*tapedev == NULL) {
318             result = -1;
319             taperscan_output_callback(data, "No tapedev spefified");
320         } else {
321             *tapedev = stralloc(*tapedev);
322             result =  scan_read_label(*tapedev, wantlabel,
323                                       gotlabel, timestamp, &error_message);
324             taperscan_output_callback(data, error_message);
325             amfree(error_message);
326         }
327     }
328
329     return result;
330 }
331
332 #define AUTO_LABEL_MAX_LEN 1024
333 char *
334 find_brand_new_tape_label(void)
335 {
336     char *format;
337     char newlabel[AUTO_LABEL_MAX_LEN];
338     char tmpnum[30]; /* 64-bit integers can be 21 digists... */
339     char tmpfmt[16];
340     char *auto_pos = NULL;
341     int i;
342     ssize_t label_len, auto_len;
343     tape_t *tp;
344
345     if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
346         return NULL;
347     }
348     format = getconf_str(CNF_LABEL_NEW_TAPES);
349
350     memset(newlabel, 0, AUTO_LABEL_MAX_LEN);
351     label_len = 0;
352     auto_len = -1; /* Only find the first '%' */
353     while (*format != '\0') {
354         if (label_len + 4 > AUTO_LABEL_MAX_LEN) {
355             fprintf(stderr, "Auto label format is too long!\n");
356             return NULL;
357         }
358
359         if (*format == '\\') {
360             /* Copy the next character. */
361             newlabel[label_len++] = format[1];
362             format += 2;
363         } else if (*format == '%' && auto_len == -1) {
364             /* This is the format specifier. */
365             auto_pos = newlabel + label_len;
366             auto_len = 0;
367             while (*format == '%' && label_len < AUTO_LABEL_MAX_LEN) {
368                 newlabel[label_len++] = '%';
369                 auto_len ++;
370                 format ++;
371             }
372         } else {
373             /* Just copy a character. */
374             newlabel[label_len++] = *(format++);
375         }     
376     }
377
378     /* Sometimes we copy the null, sometimes not. */
379     if (newlabel[label_len] != '\0') {
380         newlabel[label_len++] = '\0';
381     }
382
383     if (auto_pos == NULL) {
384         fprintf(stderr, "Auto label template contains no '%%'!\n");
385         return NULL;
386     }
387
388     snprintf(tmpfmt, SIZEOF(tmpfmt), "%%0" SIZE_T_FMT "d",
389              (SIZE_T_FMT_TYPE)auto_len);
390
391     for (i = 1; i < INT_MAX; i ++) {
392         snprintf(tmpnum, SIZEOF(tmpnum), tmpfmt, i);
393         if (strlen(tmpnum) != (size_t)auto_len) {
394             fprintf(stderr, "All possible auto-labels used.\n");
395             return NULL;
396         }
397
398         strncpy(auto_pos, tmpnum, (size_t)auto_len);
399
400         tp = lookup_tapelabel(newlabel);
401         if (tp == NULL) {
402             /* Got it. Double-check that this is a labelstr match. */
403             if (!match(getconf_str(CNF_LABELSTR), newlabel)) {
404                 fprintf(stderr, "New label %s does not match labelstr %s!\n",
405                         newlabel, getconf_str(CNF_LABELSTR));
406                 return 0;
407             }
408             return stralloc(newlabel);
409         }
410     }
411
412     /* Should not get here unless you have over two billion tapes. */
413     fprintf(stderr, "Taper internal error in find_brand_new_tape_label.");
414     return 0;
415 }
416
417 void
418 FILE_taperscan_output_callback(
419     void *data,
420     char *msg)
421 {
422     if(!msg) return;
423     if(strlen(msg) == 0) return;
424
425     if(data)
426         fprintf((FILE *)data, "%s", msg);
427     else
428         printf("%s", msg);
429 }
430
431 void
432 CHAR_taperscan_output_callback(
433     /*@keep@*/  void *data,
434                 char *msg)
435 {
436     char **s = (char **)data;
437
438     if(!msg) return;
439     if(strlen(msg) == 0) return;
440
441     if(*s)
442         strappend(*s, msg);
443     else
444         *s = stralloc(msg);
445 }