3b5ba5518876f19fcc303d40290c0ab6964b6f59
[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.9 2006/03/10 14:29:22 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 P((char *dev, char *wantlabel,
36                        char** label, char** timestamp,
37                        char**error_message));
38 int changer_taper_scan P((char *wantlabel, char** gotlabel, char**timestamp,
39                           char**error_message, char **tapedev));
40 char *find_brand_new_tape_label();
41
42 /* NO GLOBALS PLEASE! */
43
44 /* How does the taper scan algorithm work? Like this:
45  * 1) If there is a barcode reader, and we have a recyclable tape, use the
46  *    reader to load the oldest tape.
47  * 2) Otherwise, search through the changer until we find a new tape
48  *    or the oldest recyclable tape.
49  * 3) If we couldn't find the oldest recyclable tape or a new tape,
50  *    but if in #2 we found *some* recyclable tape, use the oldest one we
51  *    found.
52  * 4) At this point, we give up.
53  */
54
55 /* This function checks the label of a single tape, which may or may not
56  * have been loaded by the changer. With the addition of char *dev, it has
57  * the same interface as taper_scan. 
58  * Return value is the same as taper_scan.
59  */
60 int scan_read_label(char *dev, char *desired_label,
61                     char** label, char** timestamp, char** error_message) {
62     char *result = NULL;
63     char *errstr = NULL;
64
65     *label = *timestamp = NULL;
66     result = tape_rdlabel(dev, timestamp, label);
67     if (result != NULL) {
68         if (CHECK_NOT_AMANDA_TAPE_MSG(result) &&
69             getconf_seen(CNF_LABEL_NEW_TAPES)) {
70             amfree(result);
71             
72             *label = find_brand_new_tape_label();
73             if (*label != NULL) {
74                 *timestamp = stralloc("X");
75                 vstrextend(error_message,
76                            "Found a non-amanda tape, will label it `",
77                            *label, "'.\n", NULL);
78                 return 3;
79             }
80             vstrextend(error_message,
81                        "Found a non-amanda tape, but have no labels left.\n",
82                         NULL);
83             return -1;
84         }
85         amfree(*timestamp);
86         amfree(*label);
87         vstrextend(error_message, result, "\n", NULL);
88         amfree(result);
89         return -1;
90     }
91     
92     if ((*label == NULL) || (*timestamp == NULL)) {
93         error("Invalid return from tape_rdlabel");
94     }
95
96     vstrextend(error_message, "read label `", *label, "', date `",
97                *timestamp, "'\n", NULL);
98
99     if (desired_label != NULL && strcmp(*label, desired_label) == 0) {
100         /* Got desired label. */
101         return 1;
102     }
103
104     /* Is this actually an acceptable tape? */
105     if (strcmp(*label, FAKE_LABEL) != 0) {
106         char *labelstr;
107         labelstr = getconf_str(CNF_LABELSTR);
108         if(!match(labelstr, *label)) {
109             vstrextend(&errstr, "label ", *label,
110                        " doesn\'t match labelstr \"",
111                        labelstr, "\"\n", NULL);
112             return -1;
113         } else {
114             tape_t *tp;
115             if (strcmp(*timestamp, "X") == 0) {
116                 /* new, labeled tape. */
117                 return 1;
118             }
119
120             tp = lookup_tapelabel(*label);
121          
122             if(tp == NULL) {
123                 vstrextend(&errstr, "label ", *label,
124                      " match labelstr but it not listed in the tapelist file.\n",
125                            NULL);
126                 return -1;
127             } else if(tp != NULL && !reusable_tape(tp)) {
128                 vstrextend(&errstr, "cannot overwrite active tape ", *label,
129                            "\n", NULL);
130                 return -1;
131             }
132         }
133     }
134   
135     /* Yay! We got a good tape! */
136     return 2;
137 }
138
139 /* Interface is the same as taper_scan, with the addition of the tapedev
140  * output. */
141 typedef struct {
142     char *wantlabel;
143     char **gotlabel;
144     char **timestamp;
145     char **error_message;
146     char **tapedev;
147     char *first_labelstr_slot;
148     int backwards;
149     int tape_status;
150 } changertrack_t;
151
152 int scan_slot(void *data, int rc, char *slotstr, char *device) {
153     int label_result;
154     changertrack_t *ct = ((changertrack_t*)data);
155
156     switch (rc) {
157     default:
158         newvstralloc(*(ct->error_message), *(ct->error_message),
159                      "fatal changer error ", slotstr, ": ",
160                      changer_resultstr, NULL);
161         return 1;
162     case 1:
163         newvstralloc(*(ct->error_message), *(ct->error_message),
164                      "changer error ", slotstr, ": ", changer_resultstr, NULL);
165         return 0;
166     case 0:
167         vstrextend(ct->error_message, "slot ", slotstr, ": ", NULL);
168         label_result = scan_read_label(device, ct->wantlabel, ct->gotlabel,
169                                        ct->timestamp, ct->error_message);
170         if (label_result == 1 || label_result == 3 ||
171             (label_result == 2 && !ct->backwards)) {
172             *(ct->tapedev) = stralloc(device);
173             ct->tape_status = label_result;
174             return 1;
175         } else if (label_result == 2) {
176             if (ct->first_labelstr_slot == NULL)
177                 ct->first_labelstr_slot = stralloc(slotstr);
178             return 0;
179         } else {
180             return 0;
181         }
182     }
183     /* NOTREACHED */
184     return 1;
185 }
186
187 static int 
188 scan_init(void *data, int rc, int nslots, int backwards, int searchable) {
189     changertrack_t *ct = ((changertrack_t*)data);
190     
191     if (rc) {
192         newvstralloc(*(ct->error_message), *(ct->error_message),
193                      "could not get changer info: ", changer_resultstr, NULL);
194     }
195
196     ct->backwards = backwards;
197     return 0;
198 }
199
200 int changer_taper_scan(char* wantlabel,
201                        char** gotlabel, char** timestamp,
202                        char** error_message, char **tapedev) {
203     changertrack_t local_data = {wantlabel, gotlabel, timestamp,
204                                  error_message, tapedev, NULL, 0, 0};
205
206     *gotlabel = *timestamp = *tapedev = NULL;
207     
208     changer_find(&local_data, scan_init, scan_slot, wantlabel);
209     
210     if (*(local_data.tapedev)) {
211         /* We got it, and it's loaded. */
212         return local_data.tape_status;
213     } else if (local_data.first_labelstr_slot) {
214         /* Use plan B. */
215         if (changer_loadslot(local_data.first_labelstr_slot,
216                              NULL, tapedev) == 0) {
217             return scan_read_label(*tapedev, NULL, gotlabel, timestamp,
218                                    error_message);
219         }
220     }
221
222     /* Didn't find a tape. :-( */
223     assert(local_data.tape_status <= 0);
224     return -1;
225 }
226
227 int taper_scan(char* wantlabel,
228                char** gotlabel, char** timestamp, char** error_message,
229                char** tapedev) {
230
231     *gotlabel = *timestamp = *error_message = NULL;
232     *tapedev = getconf_str(CNF_TAPEDEV);
233
234     if (wantlabel == NULL) {
235         tape_t *tmp;
236         tmp = lookup_last_reusable_tape(0);
237         if (tmp != NULL) {
238             wantlabel = tmp->label;
239         }
240     }
241
242     if (changer_init()) {
243         return changer_taper_scan(wantlabel, gotlabel, timestamp,
244                                     error_message, tapedev);
245     }
246
247     return scan_read_label(*tapedev, wantlabel,
248                            gotlabel, timestamp, error_message);
249 }
250
251 #define AUTO_LABEL_MAX_LEN 1024
252 char* find_brand_new_tape_label() {
253     char *format;
254     char newlabel[AUTO_LABEL_MAX_LEN];
255     char tmpnum[12];
256     char tmpfmt[16];
257     char *auto_pos = NULL;
258     int i, format_len, label_len, auto_len;
259     tape_t *tp;
260
261     if (!getconf_seen(CNF_LABEL_NEW_TAPES)) {
262         return NULL;
263     }
264     format = getconf_str(CNF_LABEL_NEW_TAPES);
265     format_len = strlen(format);
266
267     memset(newlabel, 0, AUTO_LABEL_MAX_LEN);
268     label_len = 0;
269     auto_len = -1; /* Only find the first '%' */
270     while (*format != '\0') {
271         if (label_len + 4 > AUTO_LABEL_MAX_LEN) {
272             fprintf(stderr, "Auto label format is too long!\n");
273             return NULL;
274         }
275
276         if (*format == '\\') {
277             /* Copy the next character. */
278             newlabel[label_len++] = format[1];
279             format += 2;
280         } else if (*format == '%' && auto_len == -1) {
281             /* This is the format specifier. */
282             auto_pos = newlabel + label_len;
283             auto_len = 0;
284             while (*format == '%' && label_len < AUTO_LABEL_MAX_LEN) {
285                 newlabel[label_len++] = '%';
286                 auto_len ++;
287                 format ++;
288             }
289         } else {
290             /* Just copy a character. */
291             newlabel[label_len++] = *(format++);
292         }     
293     }
294
295     /* Sometimes we copy the null, sometimes not. */
296     if (newlabel[label_len] != '\0') {
297         newlabel[label_len++] = '\0';
298     }
299
300     if (auto_pos == NULL) {
301         fprintf(stderr, "Auto label template contains no '%%'!\n");
302         return NULL;
303     }
304
305     sprintf(tmpfmt, "%%0%dd", auto_len);
306
307     for (i = 1; i < INT_MAX; i ++) {
308         sprintf(tmpnum, tmpfmt, i);
309         if (strlen(tmpnum) != auto_len) {
310             fprintf(stderr, "All possible auto-labels used.\n");
311             return NULL;
312         }
313
314         strncpy(auto_pos, tmpnum, auto_len);
315
316         tp = lookup_tapelabel(newlabel);
317         if (tp == NULL) {
318             /* Got it. Double-check that this is a labelstr match. */
319             if (!match(getconf_str(CNF_LABELSTR), newlabel)) {
320                 fprintf(stderr, "New label %s does not match labelstr %s!\n",
321                         newlabel, getconf_str(CNF_LABELSTR));
322                 return 0;
323             }
324             return stralloc(newlabel);
325         }
326     }
327
328     /* NOTREACHED. Unless you have over two billion tapes. */
329     fprintf(stderr, "Taper internal error in find_brand_new_tape_label.");
330     return 0;
331 }