1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * Copyright (c) 2007-2013 Zmanda, Inc.  All Rights Reserved.
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation, and that the name of U.M. not be used in advertising or
12  * publicity pertaining to distribution of the software without specific,
13  * written prior permission.  U.M. makes no representations about the
14  * suitability of this software for any purpose.  It is provided "as is"
15  * without express or implied warranty.
16  *
17  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  *
24  * Authors: the Amanda Development Team.  Its members are listed in a
25  * file named AUTHORS, in the root directory of this distribution.
26  */
27 /*
28  * $Id: tapefile.c,v 1.37 2006/07/21 00:25:52 martinea Exp $
29  *
30  * routines to read and write the amanda active tape list
31  */
32 #include "amanda.h"
33 #include "match.h"
34 #include "tapefile.h"
35 #include "conffile.h"
36 
37 static tape_t *tape_list = NULL;
38 
39 /* local functions */
40 static tape_t *parse_tapeline(int *status, char *line);
41 static tape_t *insert(tape_t *list, tape_t *tp);
42 static time_t stamp2time(char *datestamp);
43 
44 int
read_tapelist(char * tapefile)45 read_tapelist(
46     char *tapefile)
47 {
48     tape_t *tp;
49     FILE *tapef;
50     int pos;
51     char *line = NULL;
52     int status = 0;
53 
54     clear_tapelist();
55     if((tapef = fopen(tapefile,"r")) == NULL) {
56 	if (errno == ENOENT) {
57 	    /* no tapelist is equivalent to an empty tapelist */
58 	    return 0;
59 	} else {
60 	    g_debug("Error opening '%s': %s", tapefile, strerror(errno));
61 	    return 1;
62 	}
63     }
64 
65     while((line = agets(tapef)) != NULL) {
66 	if (line[0] == '\0') {
67 	    amfree(line);
68 	    continue;
69 	}
70 	tp = parse_tapeline(&status, line);
71 	amfree(line);
72 	if(tp == NULL && status != 0)
73 	    return 1;
74 	if(tp != NULL)
75 	    tape_list = insert(tape_list, tp);
76     }
77     afclose(tapef);
78 
79     for(pos=1,tp=tape_list; tp != NULL; pos++,tp=tp->next) {
80 	tp->position = pos;
81     }
82 
83     return 0;
84 }
85 
86 int
write_tapelist(char * tapefile)87 write_tapelist(
88     char *tapefile)
89 {
90     tape_t *tp;
91     FILE *tapef;
92     char *newtapefile;
93     int rc;
94 
95     newtapefile = stralloc2(tapefile, ".new");
96 
97     if((tapef = fopen(newtapefile,"w")) == NULL) {
98 	amfree(newtapefile);
99 	return 1;
100     }
101 
102     for(tp = tape_list; tp != NULL; tp = tp->next) {
103 	g_fprintf(tapef, "%s %s", tp->datestamp, tp->label);
104 	if(tp->reuse) g_fprintf(tapef, " reuse");
105 	else g_fprintf(tapef, " no-reuse");
106 	if (tp->barcode)
107 	    g_fprintf(tapef, " BARCODE:%s", tp->barcode);
108 	if (tp->meta)
109 	    g_fprintf(tapef, " META:%s", tp->meta);
110 	if (tp->blocksize)
111 	    g_fprintf(tapef, " BLOCKSIZE:%jd", (intmax_t)tp->blocksize);
112 	if (tp->comment)
113 	    g_fprintf(tapef, " #%s", tp->comment);
114 	g_fprintf(tapef, "\n");
115     }
116 
117     if (fclose(tapef) == EOF) {
118 	g_fprintf(stderr,_("error [closing %s: %s]"), newtapefile, strerror(errno));
119 	amfree(newtapefile);
120 	return 1;
121     }
122     rc = rename(newtapefile, tapefile);
123     amfree(newtapefile);
124 
125     return(rc != 0);
126 }
127 
128 void
clear_tapelist(void)129 clear_tapelist(void)
130 {
131     tape_t *tp, *next;
132 
133     for(tp = tape_list; tp; tp = next) {
134 	amfree(tp->label);
135 	amfree(tp->datestamp);
136 	amfree(tp->barcode);
137 	amfree(tp->meta);
138 	amfree(tp->comment);
139 	next = tp->next;
140 	amfree(tp);
141     }
142     tape_list = NULL;
143 }
144 
145 tape_t *
lookup_tapelabel(const char * label)146 lookup_tapelabel(
147     const char *label)
148 {
149     tape_t *tp;
150 
151     for(tp = tape_list; tp != NULL; tp = tp->next) {
152 	if(strcmp(label, tp->label) == 0) return tp;
153     }
154     return NULL;
155 }
156 
157 
158 
159 tape_t *
lookup_tapepos(int pos)160 lookup_tapepos(
161     int pos)
162 {
163     tape_t *tp;
164 
165     for(tp = tape_list; tp != NULL; tp = tp->next) {
166 	if(tp->position == pos) return tp;
167     }
168     return NULL;
169 }
170 
171 
172 tape_t *
lookup_tapedate(char * datestamp)173 lookup_tapedate(
174     char *datestamp)
175 {
176     tape_t *tp;
177 
178     for(tp = tape_list; tp != NULL; tp = tp->next) {
179 	if(strcmp(tp->datestamp, datestamp) == 0) return tp;
180     }
181     return NULL;
182 }
183 
184 int
lookup_nb_tape(void)185 lookup_nb_tape(void)
186 {
187     tape_t *tp;
188     int pos=0;
189 
190     for(tp = tape_list; tp != NULL; tp = tp->next) {
191 	pos=tp->position;
192     }
193     return pos;
194 }
195 
196 
197 char *
get_last_reusable_tape_label(int skip)198 get_last_reusable_tape_label(
199      int skip)
200 {
201     tape_t *tp = lookup_last_reusable_tape(skip);
202     return (tp != NULL) ? tp->label : NULL;
203 }
204 
205 tape_t *
lookup_last_reusable_tape(int skip)206 lookup_last_reusable_tape(
207      int skip)
208 {
209     tape_t *tp, **tpsave;
210     int count=0;
211     int s;
212     int tapecycle = getconf_int(CNF_TAPECYCLE);
213     char *labelstr = getconf_str (CNF_LABELSTR);
214 
215     /*
216      * The idea here is we keep the last "several" reusable tapes we
217      * find in a stack and then return the n-th oldest one to the
218      * caller.  If skip is zero, the oldest is returned, if it is
219      * one, the next oldest, two, the next to next oldest and so on.
220      */
221     tpsave = alloc((skip + 1) * SIZEOF(*tpsave));
222     for(s = 0; s <= skip; s++) {
223 	tpsave[s] = NULL;
224     }
225     for(tp = tape_list; tp != NULL; tp = tp->next) {
226 	if(tp->reuse == 1 && strcmp(tp->datestamp,"0") != 0 && match (labelstr, tp->label)) {
227 	    count++;
228 	    for(s = skip; s > 0; s--) {
229 	        tpsave[s] = tpsave[s - 1];
230 	    }
231 	    tpsave[0] = tp;
232 	}
233     }
234     s = tapecycle - count;
235     if(s < 0) s = 0;
236     if(count < tapecycle - skip) tp = NULL;
237     else tp = tpsave[skip - s];
238     amfree(tpsave);
239     return tp;
240 }
241 
242 int
reusable_tape(tape_t * tp)243 reusable_tape(
244     tape_t *tp)
245 {
246     int count = 0;
247 
248     if(tp == NULL) return 0;
249     if(tp->reuse == 0) return 0;
250     if( strcmp(tp->datestamp,"0") == 0) return 1;
251     while(tp != NULL) {
252 	if(tp->reuse == 1) count++;
253 	tp = tp->prev;
254     }
255     return (count >= getconf_int(CNF_TAPECYCLE));
256 }
257 
258 void
remove_tapelabel(char * label)259 remove_tapelabel(
260     char *label)
261 {
262     tape_t *tp, *prev, *next;
263 
264     tp = lookup_tapelabel(label);
265     if(tp != NULL) {
266 	prev = tp->prev;
267 	next = tp->next;
268 	/*@ignore@*/
269 	if(prev != NULL)
270 	    prev->next = next;
271 	else /* begin of list */
272 	    tape_list = next;
273 	if(next != NULL)
274 	    next->prev = prev;
275 	/*@end@*/
276 	while (next != NULL) {
277 	    next->position--;
278 	    next = next->next;
279 	}
280 	amfree(tp->datestamp);
281 	amfree(tp->label);
282 	amfree(tp->meta);
283 	amfree(tp->comment);
284 	amfree(tp->barcode);
285 	amfree(tp);
286     }
287 }
288 
289 tape_t *
add_tapelabel(char * datestamp,char * label,char * comment)290 add_tapelabel(
291     char *datestamp,
292     char *label,
293     char *comment)
294 {
295     tape_t *cur, *new;
296 
297     /* insert a new record to the front of the list */
298 
299     new = g_new0(tape_t, 1);
300 
301     new->datestamp = stralloc(datestamp);
302     new->position = 0;
303     new->reuse = 1;
304     new->label = stralloc(label);
305     new->comment = comment? stralloc(comment) : NULL;
306 
307     new->prev  = NULL;
308     if(tape_list != NULL) tape_list->prev = new;
309     new->next = tape_list;
310     tape_list = new;
311 
312     /* scan list, updating positions */
313     cur = tape_list;
314     while(cur != NULL) {
315 	cur->position++;
316 	cur = cur->next;
317     }
318 
319     return new;
320 }
321 
322 int
guess_runs_from_tapelist(void)323 guess_runs_from_tapelist(void)
324 {
325     tape_t *tp;
326     int i, ntapes, tape_ndays, dumpcycle, runtapes, runs;
327     time_t tape_time, today;
328 
329     today = time(0);
330     dumpcycle = getconf_int(CNF_DUMPCYCLE);
331     runtapes = getconf_int(CNF_RUNTAPES);
332     if(runtapes == 0) runtapes = 1;	/* just in case */
333 
334     ntapes = 0;
335     tape_ndays = 0;
336     for(i = 1; i < getconf_int(CNF_TAPECYCLE); i++) {
337 	if((tp = lookup_tapepos(i)) == NULL) break;
338 
339 	tape_time  = stamp2time(tp->datestamp);
340 	tape_ndays = (int)days_diff(tape_time, today);
341 
342 	if(tape_ndays < dumpcycle) ntapes++;
343 	else break;
344     }
345 
346     if(tape_ndays < dumpcycle)	{
347 	/* scale for best guess */
348 	if(tape_ndays == 0) ntapes = dumpcycle * runtapes;
349 	else ntapes = ntapes * dumpcycle / tape_ndays;
350     }
351     else if(ntapes == 0) {
352 	/* no dumps within the last dumpcycle, guess as above */
353 	ntapes = dumpcycle * runtapes;
354     }
355 
356     runs = (ntapes + runtapes - 1) / runtapes;
357     if (runs <= 0)
358       runs = 1;
359     return runs;
360 }
361 
362 static tape_t *
parse_tapeline(int * status,char * line)363 parse_tapeline(
364     int *status,
365     char *line)
366 {
367     tape_t *tp = NULL;
368     char *s, *s1;
369     int ch;
370     char *cline;
371 
372     *status = 0;
373 
374     s = line;
375     ch = *s++;
376 
377     skip_whitespace(s, ch);
378     if(ch == '\0') {
379 	return NULL;
380     }
381 
382     cline = g_strdup(line);
383     tp = g_new0(tape_t, 1);
384 
385     s1 = s - 1;
386     skip_non_whitespace(s, ch);
387     s[-1] = '\0';
388     tp->datestamp = stralloc(s1);
389 
390     skip_whitespace(s, ch);
391     s1 = s - 1;
392     skip_non_whitespace(s, ch);
393     s[-1] = '\0';
394     tp->label = stralloc(s1);
395 
396     skip_whitespace(s, ch);
397     tp->reuse = 1;
398     if(strncmp_const(s - 1, "reuse") == 0) {
399 	tp->reuse = 1;
400 	s1 = s - 1;
401 	skip_non_whitespace(s, ch);
402 	s[-1] = '\0';
403 	skip_whitespace(s, ch);
404     }
405     if(strncmp_const(s - 1, "no-reuse") == 0) {
406 	tp->reuse = 0;
407 	s1 = s - 1;
408 	skip_non_whitespace(s, ch);
409 	s[-1] = '\0';
410 	skip_whitespace(s, ch);
411     }
412 
413     if (strncmp_const(s - 1, "BARCODE:") == 0) {
414 	s1 = s - 1 + 8;
415 	skip_non_whitespace(s, ch);
416 	s[-1] = '\0';
417 	skip_whitespace(s, ch);
418 	tp->barcode = stralloc(s1);
419     }
420 
421     if (strncmp_const(s - 1, "META:") == 0) {
422 	s1 = s - 1 + 5;
423 	skip_non_whitespace(s, ch);
424 	s[-1] = '\0';
425 	skip_whitespace(s, ch);
426 	tp->meta = stralloc(s1);
427     }
428 
429     if (strncmp_const(s - 1, "BLOCKSIZE:") == 0) {
430 	s1 = s - 1 + 10;
431 	skip_non_whitespace(s, ch);
432 	s[-1] = '\0';
433 	skip_whitespace(s, ch);
434 	tp->blocksize = atol(s1);
435     }
436     if (*(s - 1) == '#') {
437 	tp->comment = stralloc(s); /* skip leading '#' */
438     } else if (*(s-1)) {
439 	g_critical("Bogus line in the tapelist file: %s", cline);
440     }
441     g_free(cline);
442 
443     return tp;
444 }
445 
446 
447 /* insert in reversed datestamp order */
448 /*@ignore@*/
449 static tape_t *
insert(tape_t * list,tape_t * tp)450 insert(
451     tape_t *list,
452     tape_t *tp)
453 {
454     tape_t *prev, *cur;
455 
456     prev = NULL;
457     cur = list;
458 
459     while(cur != NULL && strcmp(cur->datestamp, tp->datestamp) >= 0) {
460 	prev = cur;
461 	cur = cur->next;
462     }
463     tp->prev = prev;
464     tp->next = cur;
465     if(prev == NULL) {
466 	list = tp;
467 #ifndef __lint
468     } else {
469 	prev->next = tp;
470 #endif
471     }
472     if(cur !=NULL)
473 	cur->prev = tp;
474 
475     return list;
476 }
477 /*@end@*/
478 
479 /*
480  * Converts datestamp (an char of the form YYYYMMDD or YYYYMMDDHHMMSS) into a real
481  * time_t value.
482  * Since the datestamp contains no timezone or hh/mm/ss information, the
483  * value is approximate.  This is ok for our purposes, since we round off
484  * scheduling calculations to the nearest day.
485  */
486 
487 static time_t
stamp2time(char * datestamp)488 stamp2time(
489     char *datestamp)
490 {
491     struct tm *tm;
492     time_t now;
493     char date[9];
494     int dateint;
495 
496     strncpy(date, datestamp, 8);
497     date[8] = '\0';
498     dateint = atoi(date);
499     now = time(0);
500     tm = localtime(&now);	/* initialize sec/min/hour & gmtoff */
501 
502     if (!tm) {
503 	tm = alloc(SIZEOF(struct tm));
504 	tm->tm_sec   = 0;
505 	tm->tm_min   = 0;
506 	tm->tm_hour  = 0;
507 	tm->tm_wday  = 0;
508 	tm->tm_yday  = 0;
509 	tm->tm_isdst = 0;
510     }
511 
512 
513     tm->tm_year = ( dateint          / 10000) - 1900;
514     tm->tm_mon  = ((dateint % 10000) /   100) - 1;
515     tm->tm_mday = ((dateint %   100)        );
516 
517     return mktime(tm);
518 }
519 
520 char *
list_new_tapes(int nb)521 list_new_tapes(
522     int nb)
523 {
524     tape_t *lasttp, *iter;
525     char *result = NULL;
526 
527     /* Find latest reusable new tape */
528     lasttp = lookup_tapepos(lookup_nb_tape());
529     while (lasttp && lasttp->reuse == 0)
530 	lasttp = lasttp->prev;
531 
532     if(lasttp && nb > 0 && strcmp(lasttp->datestamp,"0") == 0) {
533 	int c = 0;
534 	iter = lasttp;
535 	/* count the number of tapes we *actually* used */
536 	while(iter && nb > 0 && strcmp(iter->datestamp,"0") == 0) {
537 	    if (iter->reuse) {
538 		c++;
539 		nb--;
540 	    }
541 	    iter = iter->prev;
542 	}
543 
544 	if(c == 1) {
545 	    result = g_strdup_printf(
546 			_("The next new tape already labelled is: %s."),
547 			lasttp->label);
548 	} else {
549 	    result = g_strdup_printf(
550 			_("The next %d new tapes already labelled are: %s"),
551 			c, lasttp->label);
552 	    iter = lasttp->prev;
553 	    c--;
554 	    while(iter && c > 0 && strcmp(iter->datestamp,"0") == 0) {
555 		if (iter->reuse) {
556 		    result = vstrextend(&result, ", ", iter->label, NULL);
557 		    c--;
558 		}
559 		iter = iter->prev;
560 	    }
561 	}
562     }
563     return result;
564 }
565 
566 void
print_new_tapes(FILE * output,int nb)567 print_new_tapes(
568     FILE *output,
569     int   nb)
570 {
571     char *result = list_new_tapes(nb);
572 
573     if (result) {
574 	g_fprintf(output,"%s\n", result);
575 	amfree(result);
576     }
577 }
578