source: MondoRescue/branches/2.2.5/mondo/src/common/libmondo-verify.c@ 1645

Last change on this file since 1645 was 1645, checked in by Bruno Cornec, 17 years ago

Render bkpinfo global (potential issue on thread, but should not be a problem as that structure is indeed static during archive)
Should solve the tmpdir issue from previous rev.
May still not compile

  • Property svn:keywords set to Id
File size: 35.8 KB
RevLine 
[1]1/***************************************************************************
2libmondo-verify.c - description
3-----------------
4
5begin: Fri Apr 19 16:40:35 EDT 2002
6copyright : (C) 2002 Mondo Hugo Rabson
7email : Hugo Rabson <hugorabson@msn.com>
8edited by : by Stan Benoit 4/2002
9email : troff@nakedsoul.org
[117]10cvsid : $Id: libmondo-verify.c 1645 2007-09-24 01:04:43Z bruno $
[1]11***************************************************************************/
12
13/***************************************************************************
14 * *
15 * This program is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU General Public License as published by *
17 * the Free Software Foundation; either version 2 of the License, or *
18 * (at your option) any later version. *
19 * *
20 ***************************************************************************/
21/* mondo-verify
22
23
2407/07
25- added star verify support
26
2704/04/2004
28- added star support
29
3010/23/2003
31- changed "ISO #n" to "<media descriptor> #n"
32
3310/01
34- working on biggiefile verification (CDs)
35
3609/16
37- fixed bug in CD biggie verif'n
38
3909/15
40- state explicitly that we do not verify disk images
41
4209/13
43- working on verify_all_slices_on_CD()
44
4509/01
46- write list of changed biggiefiles (streaming only) to changed.files
47
4805/05
49- exclude /dev/ * from list of changed files
50- add Joshua Oreman's FreeBSD patches
51
5204/24/2003
53- added lots of assert()'s and log_OS_error()'s
54
5509/01 - 09/30/2002
56- run_program_and_log_output() now takes boolean operator to specify
57 whether it will log its activities in the event of _success_
58- eject_device() added
59- cleaned up 'changed file' feedback a little bit
60
6108/01 - 08/31
62- make sure to prefix bkpinfo->restore_path to local biggiefile fname when
63 comparing it to the archived copy of biggiefile; otherwise, biggiefile
64 not found & checksum missing & so on
65- exclude "incheckentry xwait()" from changed.files
66- cleaned up some log_it() calls
67
6807/01 - 07/31
69- renamed libmondo-verify.c
70- say 'bigfile' not 'biggiefile'
71- exclude /dev/ * from changed.files
72
7301/01 - 06/30
74- remove /tmp/changed.files.* dead files
75- don't pclose() tape after calling closein_tape(): the latter does it already
76- fixed bug in verify_cd_image()'s CD-mounting code
77- don't write to screen "old cksum.. curr cksum.."
78- changed the gawks to awks for the benefit of Debian
79- handles files >2GB in size
80- fixed bug in /tmp/changed.files-generator
81- re-enabled a lot of CD-verification code
82- afioballs are saved in %s/tmpfs/ now (%s=tmpdir)
83- changed some labels to make them more user-friendly
84- don't chdir() anywhere before verifying stuff
85- changed files are now detected by verify_tape_backup() and listed in
86 /tmp/changed.files
87- replaced &> with > .. 2>
88- still implementing improved tape support
89
90
91Started late Dec, 2001
92
93-----------------------------------------------------------------------------
94*/
95
96
97/**
98 * @file
99 * Functions for verifying backups (booted from hard drive, not CD).
100 */
101
102#include "my-stuff.h"
103#include "mondostructures.h"
104#include "libmondo-verify.h"
[541]105#include "libmondo-gui-EXT.h"
[1]106#include "libmondo-files-EXT.h"
107#include "libmondo-fork-EXT.h"
108#include "libmondo-stream-EXT.h"
109#include "libmondo-string-EXT.h"
110#include "libmondo-devices-EXT.h"
111#include "libmondo-tools-EXT.h"
[541]112#include "lib-common-externs.h"
[1]113
114/*@unused@*/
[117]115//static char cvsid[] = "$Id: libmondo-verify.c 1645 2007-09-24 01:04:43Z bruno $";
[1]116
[128]117char *vfy_tball_fname(struct s_bkpinfo *, char *, int);
[1]118
119
120/**
121 * The number of the most recently verified afioball.
122 * @ingroup globalGroup
123 */
124int g_last_afioball_number = -1;
125
[948]126extern char *g_getfacl;
127extern char *g_getfattr;
[1316]128extern char *MONDO_LOGFILE;
[1]129
[1645]130/* Reference to global bkpinfo */
131extern struct s_bkpinfo *bkpinfo;
132
[1]133/**
134 * Generate a list of the files that have changed, based on @c afio @c -r
135 * messages.
136 * @param changedfiles_fname Filename of the place to put a list of afio's reported changed.
137 * @param ignorefiles_fname Filename of a list of files to ignore (use "" if none).
138 * @param stderr_fname File containing afio's stderr output.
139 * @return The number of differences found (0 for a perfect backup).
140 * @bug This function seems orphaned.
141 * @ingroup utilityGroup
142 */
143long
[128]144generate_list_of_changed_files(char *changedfiles_fname,
145 char *ignorefiles_fname, char *stderr_fname)
[1]146{
[128]147 /*@ buffer ********************************************************** */
148 char *command;
[1]149 char *afio_found_changes;
150
[128]151 /*@ int ************************************************************* */
152 int res = 0;
[1]153
[128]154 /*@ long ************************************************************ */
155 long afio_diffs = 0;
[1]156
[128]157 command = malloc(2000);
158 afio_found_changes = malloc(500);
159 assert_string_is_neither_NULL_nor_zerolength(changedfiles_fname);
160 assert_string_is_neither_NULL_nor_zerolength(ignorefiles_fname);
161 assert_string_is_neither_NULL_nor_zerolength(stderr_fname);
[1]162
[128]163 sprintf(afio_found_changes, "%s.afio", ignorefiles_fname);
164 paranoid_system("sync");
165
[1]166/* sprintf (command,
[273]167 "grep \"afio: \" %s | awk '{j=substr($0,8); i=index(j,\": \");printf \"/%%s\\n\",substr(j,1,i-2);}' | sort -u | grep -v \"incheckentry.*xwait\" | grep -vx \"/afio:.*\" | grep -vx \"/dev/.*\" > %s",
[1]168 stderr_fname, afio_found_changes);
169*/
170
[128]171 log_msg(1, "Now scanning log file for 'afio: ' stuff");
172 sprintf(command,
[911]173 "grep \"afio: \" %s | sed 's/afio: //' | grep -vE '^/dev/.*$' >> %s",
[128]174 stderr_fname, afio_found_changes);
175 log_msg(2, command);
176 res = system(command);
177 if (res) {
178 log_msg(2, "Warning - failed to think");
179 }
180
181 log_msg(1, "Now scanning log file for 'star: ' stuff");
182 sprintf(command,
[911]183 "grep \"star: \" %s | sed 's/star: //' | grep -vE '^/dev/.*$' >> %s",
[128]184 stderr_fname, afio_found_changes);
185 log_msg(2, command);
186 res = system(command);
187 if (res) {
188 log_msg(2, "Warning - failed to think");
189 }
[1]190// exclude_nonexistent_files (afio_found_changes);
[128]191 afio_diffs = count_lines_in_file(afio_found_changes);
192 sprintf(command,
[273]193 "sort %s %s %s | uniq -c | awk '{ if ($1==\"2\") {print $2;};}' | grep -v \"incheckentry xwait()\" > %s",
[128]194 ignorefiles_fname, afio_found_changes, afio_found_changes,
195 changedfiles_fname);
196 log_msg(2, command);
197 paranoid_system(command);
198 paranoid_free(command);
199 paranoid_free(afio_found_changes);
200 return (afio_diffs);
[1]201}
202
203
204/**
205 * @addtogroup LLverifyGroup
206 * @{
207 */
208/**
209 * Verify all afioballs stored on the inserted CD (or an ISO image).
210 * @param bkpinfo The backup information structure. @c bkpinfo->backup_media_type
211 * is used in this function, and the structure is also passed to verify_an_afioball_from_CD().
212 * @param mountpoint The location the CD/DVD/ISO is mounted on.
213 * @return The number of sets containing differences (0 for success).
214 */
[1645]215int verify_afioballs_on_CD(char *mountpoint)
[1]216{
217
[128]218 /*@ buffers ********************************************************* */
219 char *tmp;
[1]220
[128]221 /*@ int ************************************************************* */
222 int set_number = 0;
[1]223 int retval = 0;
224 int total_sets = 0;
225 int percentage = 0;
226
[128]227 assert_string_is_neither_NULL_nor_zerolength(mountpoint);
228 assert(bkpinfo != NULL);
229 malloc_string(tmp);
230
231 for (set_number = 0;
232 set_number < 9999
233 &&
[1645]234 !does_file_exist(vfy_tball_fname(mountpoint, set_number));
[128]235 set_number++);
[1645]236 if (!does_file_exist(vfy_tball_fname(mountpoint, set_number))) {
[128]237 return (0);
[1]238 }
[128]239
240 if (g_last_afioball_number != set_number - 1) {
241 if (set_number == 0) {
242 log_msg(1,
243 "Weird error in verify_afioballs_on_CD() but it's really a cosmetic error, nothing more");
244 } else {
245 retval++;
246 sprintf(tmp, "Warning - missing set(s) between %d and %d\n",
247 g_last_afioball_number, set_number - 1);
248 log_to_screen(tmp);
249 }
250 }
251 sprintf(tmp, "Verifying %s #%d's tarballs",
252 media_descriptor_string(bkpinfo->backup_media_type),
253 g_current_media_number);
254 open_evalcall_form(tmp);
255
256 for (total_sets = set_number;
[1645]257 does_file_exist(vfy_tball_fname(mountpoint, total_sets));
[128]258 total_sets++) {
259 log_msg(1, "total_sets = %d", total_sets);
260 }
261 for (;
[1645]262 does_file_exist(vfy_tball_fname(mountpoint, set_number));
[128]263 set_number++) {
264 percentage =
265 (set_number - g_last_afioball_number) * 100 / (total_sets -
266 g_last_afioball_number);
267 update_evalcall_form(percentage);
268 log_msg(1, "set = %d", set_number);
269 retval +=
[1645]270 verify_an_afioball_from_CD(vfy_tball_fname(mountpoint, set_number));
[128]271 }
272 g_last_afioball_number = set_number - 1;
273 close_evalcall_form();
274 paranoid_free(tmp);
275 return (retval);
[1]276}
277
278/**
279 * Verify all slices stored on the inserted CD (or a mounted ISO image).
280 * @param bkpinfo The backup information structure. Fields used:
281 * - @c compression_level
282 * - @c restore_path
283 * - @c use_lzo
284 * - @c zip_exe
285 * - @c zip_suffix
286 * @param mtpt The mountpoint the CD/DVD/ISO is mounted on.
287 * @return The number of differences (0 for perfect biggiefiles).
288 */
[1645]289int verify_all_slices_on_CD(char *mtpt)
[1]290{
291
[128]292 /*@ buffer ********************************************************** */
293 char *tmp;
294 char *mountpoint;
[1]295// char ca, cb;
[128]296 char *command;
297 char *sz_exe;
298 static char *bufblkA = NULL;
299 static char *bufblkB = NULL;
300 const long maxbufsize = 65536L;
301 long currsizA = 0;
302 long currsizB = 0;
303 long j;
[1]304
[128]305 /*@ long ************************************************************ */
306 long bigfile_num = 0;
307 long slice_num = -1;
308 int res;
[1]309
[128]310 static FILE *forig = NULL;
311 static struct s_filename_and_lstat_info biggiestruct;
312 static long last_bigfile_num = -1;
313 static long last_slice_num = -1;
314 FILE *pin;
315 FILE *fin;
316 int retval = 0;
[1]317// long long outlen;
318
[128]319 malloc_string(tmp);
320 malloc_string(mountpoint);
321 malloc_string(command);
322 malloc_string(sz_exe);
323 if (!bufblkA) {
324 if (!(bufblkA = malloc(maxbufsize))) {
325 fatal_error("Cannot malloc bufblkA");
326 }
327 }
328 if (!bufblkB) {
329 if (!(bufblkB = malloc(maxbufsize))) {
330 fatal_error("Cannot malloc bufblkB");
331 }
332 }
[1]333
[128]334 assert(bkpinfo != NULL);
335 assert_string_is_neither_NULL_nor_zerolength(mtpt);
[1]336
[128]337 if (bkpinfo->compression_level > 0) {
338 if (bkpinfo->use_lzo) {
339 strcpy(sz_exe, "lzop");
[1003]340 } else if (bkpinfo->use_gzip) {
[998]341 strcpy(sz_exe, "gzip");
[128]342 } else {
343 strcpy(sz_exe, "bzip2");
344 }
345 } else {
346 sz_exe[0] = '\0';
347 }
[1]348
[128]349 iamhere("before vsbf");
350 sprintf(tmp, "Verifying %s#%d's big files",
351 media_descriptor_string(bkpinfo->backup_media_type),
352 g_current_media_number);
353 open_evalcall_form(tmp);
354 iamhere("after vsbf");
355 sprintf(mountpoint, "%s/archives", mtpt);
356 if (last_bigfile_num == -1) {
357 bigfile_num = 0;
358 slice_num = 0;
359 } else if (slice_num == 0) {
360 bigfile_num = last_bigfile_num + 1;
361 slice_num = 0;
362 } else {
363 bigfile_num = last_bigfile_num;
364 slice_num = last_slice_num + 1;
365 }
366 while (does_file_exist
367 (slice_fname
368 (bigfile_num, slice_num, mountpoint, bkpinfo->zip_suffix))
369 ||
370 does_file_exist(slice_fname
371 (bigfile_num, slice_num, mountpoint, ""))) {
[1236]372 // handle slices until end of CD
[128]373 if (slice_num == 0) {
374 log_msg(2, "ISO=%d bigfile=%ld --START--",
375 g_current_media_number, bigfile_num);
376 if (!
377 (fin =
378 fopen(slice_fname(bigfile_num, slice_num, mountpoint, ""),
379 "r"))) {
380 log_msg(2, "Cannot open bigfile's info file");
381 } else {
382 if (fread
383 ((void *) &biggiestruct, 1, sizeof(biggiestruct),
384 fin) < sizeof(biggiestruct)) {
385 log_msg(2, "Unable to get biggiestruct");
386 }
387 paranoid_fclose(fin);
388 }
389 sprintf(tmp, "%s/%s", bkpinfo->restore_path,
390 biggiestruct.filename);
391 log_msg(2, "Opening biggiefile #%ld - '%s'", bigfile_num, tmp);
392 if (!(forig = fopen(tmp, "r"))) {
393 log_msg(2, "Failed to open bigfile. Darn.");
[1262]394 log_to_screen("%s/%s not found on live filesystem",
[1236]395 bkpinfo->restore_path,
396 biggiestruct.filename);
[1644]397 asprintf(&tmp, "echo \"%s/%s not found\" >> %s/biggies.changed",
[1236]398 bkpinfo->restore_path,
[1644]399 biggiestruct.filename,
400 bkpinfo->tmpdir);
[1236]401 system(tmp);
402 paranoid_free(tmp);
403
404 bigfile_num++;
405 slice_num = 0;
[128]406 retval++;
[1236]407 } else {
408 slice_num++;
[128]409 }
[1236]410 } else if (does_file_exist(slice_fname(bigfile_num, slice_num, mountpoint, "")) &&
411 (length_of_file(slice_fname(bigfile_num, slice_num, mountpoint, "")) == 0)) {
[128]412 log_msg(2, "ISO=%d bigfile=%ld ---END---",
413 g_current_media_number, bigfile_num);
414 bigfile_num++;
415 paranoid_fclose(forig);
416 slice_num = 0;
417 } else {
[1261]418 log_msg(2, "ISO=%d bigfile=%ld slice=%ld",
[128]419 g_current_media_number, bigfile_num, slice_num);
[1236]420 if (!does_file_exist(slice_fname(bigfile_num, slice_num, mountpoint, ""))) {
[273]421 sprintf(command, "%s -dc %s 2>> %s",
422 sz_exe,
[1236]423 slice_fname(bigfile_num, slice_num, mountpoint, bkpinfo->zip_suffix),
[128]424 MONDO_LOGFILE);
425 } else {
[1236]426 sprintf(command, "cat %s 2>> %s",
427 slice_fname(bigfile_num, slice_num, mountpoint, ""), MONDO_LOGFILE);
[128]428 }
429 if ((pin = popen(command, "r"))) {
430 res = 0;
431 while (!feof(pin)) {
432 currsizA = fread(bufblkA, 1, maxbufsize, pin);
433 if (currsizA <= 0) {
434 break;
435 }
436 currsizB = fread(bufblkB, 1, currsizA, forig);
437 if (currsizA != currsizB) {
438 res++;
439 } else {
440 for (j = 0;
441 j < currsizA && bufblkA[j] == bufblkB[j];
442 j++);
443 if (j < currsizA) {
444 res++;
445 }
446 }
447 }
448 paranoid_pclose(pin);
[1236]449 if (res && !strncmp(biggiestruct.filename, "/dev/", 5)) {
[128]450 log_msg(3,
[296]451 "Ignoring differences between %s and live filesystem because it's a device and therefore the archives are stored via ntfsclone, not dd.",
[128]452 biggiestruct.filename);
453 log_msg(3,
454 "If you really want verification for %s, please contact the devteam and offer an incentive.",
455 biggiestruct.filename);
456 res = 0;
457 }
458 if (res) {
459 log_msg(0,
460 "afio: \"%s\": Corrupt biggie file, says libmondo-archive.c",
461 biggiestruct.filename);
462 retval++;
463 }
464 }
465 slice_num++;
466 }
[1]467 }
[128]468 last_bigfile_num = bigfile_num;
469 last_slice_num = slice_num - 1;
470 if (last_slice_num < 0) {
471 last_bigfile_num--;
[1]472 }
[128]473 close_evalcall_form();
474 if (bufblkA) {
475 paranoid_free(bufblkA);
[1]476 }
[128]477 if (bufblkB) {
478 paranoid_free(bufblkB);
479 }
480 paranoid_free(tmp);
481 paranoid_free(command);
482 paranoid_free(sz_exe);
483 paranoid_free(mountpoint);
484 return (0);
[1]485}
486
487
488
489
490
491
492/**
493 * Verify one afioball from the CD.
494 * You should be changed to the root directory (/) for this to work.
495 * @param bkpinfo The backup information structure. Fields used:
496 * - @c bkpinfo->use_lzo
497 * - @c bkpinfo->tmpdir
498 * - @c bkpinfo->zip_exe
499 * - @c bkpinfo->zip_suffix
500 * @param tarball_fname The filename of the afioball to verify.
501 * @return 0, always.
502 */
[1645]503int verify_a_tarball(char *tarball_fname)
[1]504{
[128]505 /*@ buffers ********************************************************* */
506 char *command;
[1]507 char *outlog;
508 char *tmp;
[128]509 // char *p;
[1]510
[128]511 /*@ pointers ******************************************************* */
512 FILE *pin;
[1]513
[128]514 /*@ long *********************************************************** */
515 long diffs = 0;
516 /* getcwd(old_pwd,MAX_STR_LEN-1); */
[1]517
518
[128]519 command = malloc(2000);
520 malloc_string(outlog);
521 malloc_string(tmp);
522 assert(bkpinfo != NULL);
523 assert_string_is_neither_NULL_nor_zerolength(tarball_fname);
[1]524
[128]525 log_it("Verifying fileset '%s'", tarball_fname);
526 /* chdir("/"); */
527 sprintf(outlog, "%s/afio.log", bkpinfo->tmpdir);
[1]528/* if programmer forgot to say which compression thingy to use then find out */
[128]529 if (strstr(tarball_fname, ".lzo")
530 && strcmp(bkpinfo->zip_suffix, "lzo")) {
531 log_msg(2, "OK, I'm going to start using lzop.");
532 strcpy(bkpinfo->zip_exe, "lzop");
533 strcpy(bkpinfo->zip_suffix, "lzo");
534 bkpinfo->use_lzo = TRUE;
[998]535 bkpinfo->use_gzip = FALSE;
[128]536 }
[998]537 if (strstr(tarball_fname, ".gz")
538 && strcmp(bkpinfo->zip_suffix, "gz")) {
539 log_msg(2, "OK, I'm going to start using gzip.");
540 strcpy(bkpinfo->zip_exe, "gzip");
541 strcpy(bkpinfo->zip_suffix, "gz");
542 bkpinfo->use_lzo = FALSE;
543 bkpinfo->use_gzip = TRUE;
544 }
[128]545 if (strstr(tarball_fname, ".bz2")
546 && strcmp(bkpinfo->zip_suffix, "bz2")) {
547 log_msg(2, "OK, I'm going to start using bzip2.");
548 strcpy(bkpinfo->zip_exe, "bzip2");
549 strcpy(bkpinfo->zip_suffix, "bz2");
550 bkpinfo->use_lzo = FALSE;
[998]551 bkpinfo->use_gzip = FALSE;
[128]552 }
553 unlink(outlog);
554 if (strstr(tarball_fname, ".star")) {
555 bkpinfo->use_star = TRUE;
556 if (strstr(tarball_fname, ".bz2"))
557 sprintf(command,
558 "star -diff diffopts=mode,size,data file=%s %s >> %s 2>> %s",
559 tarball_fname,
560 (strstr(tarball_fname, ".bz2")) ? "-bz" : " ", outlog,
561 outlog);
562 } else {
563 bkpinfo->use_star = FALSE;
564 sprintf(command, "afio -r -P %s -Z %s >> %s 2>> %s",
565 bkpinfo->zip_exe, tarball_fname, outlog, outlog);
566 }
567 log_msg(6, "command=%s", command);
568 paranoid_system(command);
569 if (length_of_file(outlog) < 10) {
570 sprintf(command, "cat %s >> %s", outlog, MONDO_LOGFILE);
571 } else {
[273]572 sprintf(command, "cut -d: -f%d %s | sort -u",
573 (bkpinfo->use_star) ? 1 : 2, outlog);
[128]574 pin = popen(command, "r");
575 if (pin) {
576 for (fgets(tmp, MAX_STR_LEN, pin); !feof(pin);
577 fgets(tmp, MAX_STR_LEN, pin)) {
578 if (bkpinfo->use_star) {
579 if (!strstr(tmp, "diffopts=")) {
580 while (strlen(tmp) > 0
581 && tmp[strlen(tmp) - 1] < 32) {
582 tmp[strlen(tmp) - 1] = '\0';
583 }
584 if (strchr(tmp, '/')) {
585 if (!diffs) {
586 log_msg(0, "'%s' - differences found",
587 tarball_fname);
588 }
589 log_msg(0, "star: /%s",
590 strip_afio_output_line(tmp));
591 diffs++;
592 }
593 }
594 } else {
595 if (!diffs) {
596 log_msg(0, "'%s' - differences found",
597 tarball_fname);
598 }
599 log_msg(0, "afio: /%s", strip_afio_output_line(tmp));
600 diffs++;
601 }
[1]602 }
[128]603 paranoid_pclose(pin);
604 } else {
605 log_OS_error(command);
606 }
607 }
608 /* chdir(old_pwd); */
[273]609 // sprintf (tmp, "uniq -u %s >> %s", "/tmp/mondo-verify.err", MONDO_LOGFILE);
[128]610 // paranoid_system (tmp);
611 // unlink ("/tmp/mondo-verify.err");
612 paranoid_free(command);
613 paranoid_free(outlog);
614 paranoid_free(tmp);
615 return (0);
[1]616}
617
618
619
620
621
622
623/**
624 * Verify one afioball from the CD.
625 * Checks for existence (calls fatal_error() if it does not exist) and
626 * then calls verify_an_afioball().
627 * @param bkpinfo The backup information structure. Passed to verify_an_afioball().
628 * @param tarball_fname The filename of the afioball to verify.
629 * @return The return value of verify_an_afioball().
630 * @see verify_an_afioball
631 */
632int
[1645]633verify_an_afioball_from_CD(char *tarball_fname)
[1]634{
635
[128]636 /*@ int ************************************************************* */
637 int res = 0;
[1]638
[128]639 assert_string_is_neither_NULL_nor_zerolength(tarball_fname);
[1]640
[128]641 log_msg(1, "Verifying %s", tarball_fname);
642 if (!does_file_exist(tarball_fname)) {
643 fatal_error("Cannot verify nonexistent afioball");
644 }
[1645]645 res = verify_a_tarball(tarball_fname);
[128]646 return (res);
[1]647}
648
649
650/**
651 * Verify one afioball from the opened tape/CD stream.
652 * Copies the file from tape to tmpdir and then calls verify_an_afioball().
653 * @param bkpinfo The backup information structure. Passed to verify_an_afioball().
654 * @param orig_fname The original filename of the afioball to verify.
655 * @param size The size of the afioball to verify.
656 * @return The return value of verify_an_afioball().
657 * @see verify_an_afioball
658 */
659int
[1645]660verify_an_afioball_from_stream(char *orig_fname, long long size)
[1]661{
662
[128]663 /*@ int ************************************************************** */
664 int retval = 0;
[1]665 int res = 0;
666
[128]667 /*@ buffers ********************************************************** */
668 char *tmp;
[1]669 char *tarball_fname;
670
[128]671 /*@ pointers ********************************************************* */
672 char *p;
[1]673
[128]674 malloc_string(tmp);
675 malloc_string(tarball_fname);
676 assert(bkpinfo != NULL);
677 assert_string_is_neither_NULL_nor_zerolength(orig_fname);
[1]678
[128]679 p = strrchr(orig_fname, '/');
680 if (!p) {
681 p = orig_fname;
682 } else {
683 p++;
684 }
685 sprintf(tmp, "mkdir -p %s/tmpfs", bkpinfo->tmpdir);
686 paranoid_system(tmp);
687 sprintf(tarball_fname, "%s/tmpfs/temporary-%s", bkpinfo->tmpdir, p);
688 sprintf(tmp, "Temporarily copying file from tape to '%s'",
689 tarball_fname);
[1]690/* log_it(tmp); */
[1645]691 read_file_from_stream_to_file(tarball_fname, size);
692 res = verify_a_tarball(tarball_fname);
[128]693 if (res) {
694 sprintf(tmp,
695 "Afioball '%s' no longer matches your live filesystem", p);
696 log_msg(0, tmp);
697 retval++;
698 }
699 unlink(tarball_fname);
700 paranoid_free(tmp);
701 paranoid_free(tarball_fname);
702 return (retval);
[1]703}
704
705
706/**
707 * Verify one biggiefile form the opened tape/CD stream.
708 * @param bkpinfo The backup information structure. @c bkpinfo->tmpdir is the only field used.
709 * @param biggie_fname The filename of the biggiefile to verify.
710 * @param size The size in bytes of said biggiefile.
711 * @return 0 for success (even if the file doesn't match); nonzero for a tape error.
712 */
713int
[1645]714verify_a_biggiefile_from_stream(char *biggie_fname, long long size)
[1]715{
716
[128]717 /*@ int ************************************************************* */
718 int retval = 0;
[1]719 int res = 0;
720 int current_slice_number = 0;
721 int ctrl_chr = '\0';
722
[128]723 /*@ char ************************************************************ */
[1]724 char *test_file;
725 char *biggie_cksum;
726 char *orig_cksum;
727 char *tmp;
728 char *slice_fnam;
[128]729
730 /*@ pointers ******************************************************** */
[1]731 char *p;
732
[128]733 /*@ long long ******************************************************* */
734 long long slice_siz;
[1]735
[128]736 malloc_string(test_file);
737 malloc_string(biggie_cksum);
738 malloc_string(orig_cksum);
739 malloc_string(tmp);
740 malloc_string(slice_fnam);
741 assert(bkpinfo != NULL);
742 assert_string_is_neither_NULL_nor_zerolength(biggie_fname);
[1]743
[128]744 p = strrchr(biggie_fname, '/');
745 if (!p) {
746 p = biggie_fname;
747 } else {
748 p++;
749 }
750 sprintf(test_file, "%s/temporary-%s", bkpinfo->tmpdir, p);
751 sprintf(tmp,
752 "Temporarily copying biggiefile %s's slices from tape to '%s'",
753 p, test_file);
[1]754/* log_it(tmp); */
[128]755 for (res =
756 read_header_block_from_stream(&slice_siz, slice_fnam, &ctrl_chr);
757 ctrl_chr != BLK_STOP_A_BIGGIE;
758 res =
759 read_header_block_from_stream(&slice_siz, slice_fnam,
760 &ctrl_chr)) {
761 if (ctrl_chr != BLK_START_AN_AFIO_OR_SLICE) {
762 wrong_marker(BLK_START_AN_AFIO_OR_SLICE, ctrl_chr);
763 }
[1645]764 res = read_file_from_stream_to_file(test_file, slice_siz);
[128]765 unlink(test_file);
766 res =
767 read_header_block_from_stream(&slice_siz, slice_fnam,
768 &ctrl_chr);
769 if (ctrl_chr != BLK_STOP_AN_AFIO_OR_SLICE) {
770 log_msg(2, "test_file = %s", test_file);
771 wrong_marker(BLK_STOP_AN_AFIO_OR_SLICE, ctrl_chr);
772 }
773 current_slice_number++;
774 retval += res;
[1]775 }
[128]776 strcpy(biggie_cksum, slice_fnam);
777 if (biggie_cksum[0] != '\0') {
778 strcpy(orig_cksum, calc_checksum_of_file(biggie_fname));
779 if (strcmp(biggie_cksum, orig_cksum)) {
780 sprintf(tmp, "orig cksum=%s; curr cksum=%s", biggie_cksum,
781 orig_cksum);
782 log_msg(2, tmp);
[541]783 sprintf(tmp, "%s has changed on live filesystem",
[128]784 biggie_fname);
785 log_to_screen(tmp);
[1644]786 sprintf(tmp, "echo \"%s\" >> %s/biggies.changed",
787 biggie_fname, bkpinfo->tmpdir);
[128]788 system(tmp);
789 }
[1]790 }
[128]791 paranoid_free(test_file);
792 paranoid_free(biggie_cksum);
793 paranoid_free(orig_cksum);
794 paranoid_free(tmp);
795 paranoid_free(slice_fnam);
796 return (retval);
[1]797}
798
799
800/**
801 * Verify all afioballs from the opened tape/CD stream.
802 * @param bkpinfo The backup information structure. Fields used:
803 * - @c bkpinfo->restore_path
804 * - @c bkpinfo->tmpdir
805 *
806 * @return 0 for success (even if there are differences); nonzero for a tape error.
807 */
[1645]808int verify_afioballs_from_stream()
[1]809{
[128]810 /*@ int ********************************************************** */
811 int retval = 0;
[1]812 int res = 0;
813 long current_afioball_number = 0;
814 int ctrl_chr = 0;
815 int total_afioballs = 0;
816
[128]817 /*@ buffers ***************************************************** */
818 char *tmp;
[1]819 char *fname;
[128]820 char *curr_xattr_list_fname;
821 char *curr_acl_list_fname;
[1]822
[128]823 /*@ long long *************************************************** */
824 long long size = 0;
[1]825
[128]826 assert(bkpinfo != NULL);
827 malloc_string(tmp);
828 malloc_string(fname);
829 malloc_string(curr_xattr_list_fname);
830 malloc_string(curr_acl_list_fname);
[1]831
[948]832 if (g_getfattr) {
833 sprintf(curr_xattr_list_fname, XATTR_BIGGLST_FNAME_RAW_SZ,
[128]834 bkpinfo->tmpdir);
[948]835 }
836 if (g_getfacl) {
837 sprintf(curr_acl_list_fname, ACL_BIGGLST_FNAME_RAW_SZ,
[128]838 bkpinfo->tmpdir);
[948]839 }
[541]840 log_to_screen("Verifying regular archives on tape");
[1645]841 total_afioballs = get_last_filelist_number() + 1;
[541]842 open_progress_form("Verifying filesystem",
843 "I am verifying archives against your live filesystem now.",
844 "Please wait. This may take a couple of hours.", "",
[128]845 total_afioballs);
846 res = read_header_block_from_stream(&size, fname, &ctrl_chr);
847 if (ctrl_chr != BLK_START_AFIOBALLS) {
848 iamhere("YOU SHOULD NOT GET HERE");
849 iamhere("Grabbing the EXAT files");
850 if (ctrl_chr == BLK_START_EXTENDED_ATTRIBUTES) {
851 res =
[1645]852 read_EXAT_files_from_tape(&size, fname, &ctrl_chr,
[128]853 curr_xattr_list_fname,
854 curr_acl_list_fname);
855 }
[1]856 }
[128]857 if (ctrl_chr != BLK_START_AFIOBALLS) {
858 wrong_marker(BLK_START_AFIOBALLS, ctrl_chr);
[1]859 }
[128]860
861 for (res = read_header_block_from_stream(&size, fname, &ctrl_chr);
862 ctrl_chr != BLK_STOP_AFIOBALLS;
863 res = read_header_block_from_stream(&size, fname, &ctrl_chr)) {
[948]864 if (g_getfattr) {
865 sprintf(curr_xattr_list_fname, XATTR_LIST_FNAME_RAW_SZ,
[128]866 bkpinfo->tmpdir, current_afioball_number);
[948]867 }
868 if (g_getfacl) {
869 sprintf(curr_acl_list_fname, ACL_LIST_FNAME_RAW_SZ,
[128]870 bkpinfo->tmpdir, current_afioball_number);
[948]871 }
[128]872 if (ctrl_chr == BLK_START_EXTENDED_ATTRIBUTES) {
873 iamhere("Reading EXAT files from tape");
874 res =
[1645]875 read_EXAT_files_from_tape(&size, fname, &ctrl_chr,
[128]876 curr_xattr_list_fname,
877 curr_acl_list_fname);
878 }
879 if (ctrl_chr != BLK_START_AN_AFIO_OR_SLICE) {
880 wrong_marker(BLK_START_AN_AFIO_OR_SLICE, ctrl_chr);
881 }
882 sprintf(tmp, "Verifying fileset #%ld", current_afioball_number);
883 /*log_it(tmp); */
884 update_progress_form(tmp);
[1645]885 res = verify_an_afioball_from_stream(fname, size);
[128]886 if (res) {
[541]887 sprintf(tmp, "Afioball %ld differs from live filesystem",
[128]888 current_afioball_number);
889 log_to_screen(tmp);
890 }
891 retval += res;
892 current_afioball_number++;
893 g_current_progress++;
894 res = read_header_block_from_stream(&size, fname, &ctrl_chr);
895 if (ctrl_chr != BLK_STOP_AN_AFIO_OR_SLICE) {
896 wrong_marker(BLK_STOP_AN_AFIO_OR_SLICE, ctrl_chr);
897 }
[1]898 }
[128]899 log_msg(1, "All done with afioballs");
900 close_progress_form();
901 paranoid_free(tmp);
902 paranoid_free(fname);
903 paranoid_free(curr_xattr_list_fname);
904 paranoid_free(curr_acl_list_fname);
905 return (retval);
[1]906}
907
908/**
909 * Verify all biggiefiles on the opened CD/tape stream.
910 * @param bkpinfo The backup information structure. Fields used:
911 * - @c bkpinfo->restore_path
912 * - @c bkpinfo->tmpdir
913 *
914 * @return 0 for success (even if there are differences); nonzero for a tape error.
915 */
[1645]916int verify_biggiefiles_from_stream()
[1]917{
918
[128]919 /*@ int ************************************************************ */
920 int retval = 0;
[1]921 int res = 0;
922 int ctrl_chr = 0;
923
[128]924 /*@ long *********************************************************** */
925 long noof_biggiefiles = 0;
[1]926 long current_biggiefile_number = 0;
927
[128]928 /*@ buffers ******************************************************** */
929 char *tmp;
[1]930 char *orig_fname, *logical_fname;
931 char *comment;
932 char *curr_xattr_list_fname;
933 char *curr_acl_list_fname;
[128]934 /*@ pointers ******************************************************* */
[1]935 char *p;
936
[128]937 /*@ long long size ************************************************* */
938 long long size = 0;
[1]939
[128]940 assert(bkpinfo != NULL);
941 malloc_string(tmp);
942 malloc_string(orig_fname);
943 malloc_string(logical_fname);
944 malloc_string(comment);
945 malloc_string(curr_xattr_list_fname);
946 malloc_string(curr_acl_list_fname);
[1]947
[948]948 if (g_getfattr) {
949 sprintf(curr_xattr_list_fname, XATTR_BIGGLST_FNAME_RAW_SZ,
[128]950 bkpinfo->tmpdir);
[948]951 }
952 if (g_getfacl) {
953 sprintf(curr_acl_list_fname, ACL_BIGGLST_FNAME_RAW_SZ,
[128]954 bkpinfo->tmpdir);
[948]955 }
[128]956 sprintf(comment, "Verifying all bigfiles.");
957 log_to_screen(comment);
958 sprintf(tmp, "%s/biggielist.txt", bkpinfo->tmpdir);
[1]959// noof_biggiefiles = count_lines_in_file (tmp); // pointless
[128]960 res = read_header_block_from_stream(&size, orig_fname, &ctrl_chr);
961 if (ctrl_chr != BLK_START_BIGGIEFILES) {
962 if (ctrl_chr == BLK_START_EXTENDED_ATTRIBUTES) {
963 iamhere("Grabbing the EXAT biggiefiles");
964 res =
[1645]965 read_EXAT_files_from_tape(&size, orig_fname,
[128]966 &ctrl_chr, curr_xattr_list_fname,
967 curr_acl_list_fname);
968 }
[1]969 }
[128]970 if (ctrl_chr != BLK_START_BIGGIEFILES) {
971 wrong_marker(BLK_START_BIGGIEFILES, ctrl_chr);
[1]972 }
[128]973 noof_biggiefiles = (long) size;
974 log_msg(1, "noof_biggiefiles = %ld", noof_biggiefiles);
[541]975 open_progress_form("Verifying big files", comment,
976 "Please wait. This may take some time.", "",
[128]977 noof_biggiefiles);
978 for (res = read_header_block_from_stream(&size, orig_fname, &ctrl_chr);
979 ctrl_chr != BLK_STOP_BIGGIEFILES;
980 res = read_header_block_from_stream(&size, orig_fname, &ctrl_chr))
[1]981 {
[128]982 if (ctrl_chr != BLK_START_A_NORMBIGGIE
983 && ctrl_chr != BLK_START_A_PIHBIGGIE) {
984 wrong_marker(BLK_START_A_NORMBIGGIE, ctrl_chr);
985 }
986 p = strrchr(orig_fname, '/');
987 if (!p) {
988 p = orig_fname;
989 } else {
990 p++;
991 }
[541]992 sprintf(comment, "Verifying bigfile #%ld (%ld K)",
[128]993 current_biggiefile_number, (long) size >> 10);
994 update_progress_form(comment);
995 sprintf(logical_fname, "%s/%s", bkpinfo->restore_path, orig_fname);
996 res =
[1645]997 verify_a_biggiefile_from_stream(logical_fname, size);
[128]998 retval += res;
999 current_biggiefile_number++;
1000 g_current_progress++;
[1]1001 }
[128]1002 close_progress_form();
1003 paranoid_free(orig_fname);
1004 paranoid_free(logical_fname);
1005 paranoid_free(curr_xattr_list_fname);
1006 paranoid_free(curr_acl_list_fname);
1007 paranoid_free(comment);
1008 paranoid_free(tmp);
1009 return (retval);
[1]1010}
1011
1012/* @} - end of LLverifyGroup */
1013
1014
1015/**
1016 * Verify the CD indicated by @c g_current_media_number.
1017 * @param bkpinfo The backup information structure. Fields used:
1018 * - @c bkpinfo->isodir
[20]1019 * - @c bkpinfo->prefix
[1]1020 * - @c bkpinfo->manual_cd_tray
1021 * - @c bkpinfo->media_device
1022 * - @c bkpinfo->nfs_remote_dir
1023 * - @c bkpinfo->tmpdir
1024 * - @c bkpinfo->verify_data
1025 *
1026 * @return 0 for success (even if differences are found), nonzero for failure.
1027 * @ingroup verifyGroup
1028 */
[1645]1029int verify_cd_image()
[1]1030{
1031
[128]1032 /*@ int ************************************************************ */
1033 int retval = 0;
[1]1034
[128]1035 /*@ buffers ******************************************************** */
1036 char *mountpoint;
[1]1037 char *command;
1038 char *tmp;
1039 char *fname;
1040#ifdef __FreeBSD__
[128]1041 char mdd[32];
[1]1042 char *mddevice = mdd;
[128]1043 int ret = 0;
1044 int vndev = 2;
[1]1045#else
1046//skip
1047#endif
1048
[128]1049 command = malloc(2000);
1050 malloc_string(mountpoint);
1051 malloc_string(tmp);
1052 malloc_string(fname);
[1]1053
[128]1054 assert(bkpinfo != NULL);
[1]1055
[128]1056 sprintf(mountpoint, "%s/cdrom", bkpinfo->tmpdir);
[543]1057 sprintf(fname, "%s/%s/%s-%d.iso", bkpinfo->isodir, bkpinfo->nfs_remote_dir,
1058 bkpinfo->prefix, g_current_media_number);
[128]1059
1060 mkdir(mountpoint, 1777);
1061 sync();
1062 if (!does_file_exist(fname)) {
1063 sprintf(tmp,
1064 "%s not found; assuming you backed up to CD; verifying CD...",
1065 fname);
1066 log_msg(2, tmp);
1067 if (bkpinfo->manual_cd_tray) {
[541]1068 popup_and_OK("Please push CD tray closed.");
[128]1069 }
[1645]1070 if (find_and_mount_actual_cd(mountpoint)) {
[541]1071 log_to_screen("failed to mount actual CD");
[128]1072 return (1);
1073 }
1074 } else {
1075 sprintf(tmp, "%s found; verifying ISO...", fname);
[1]1076#ifdef __FreeBSD__
[128]1077 ret = 0;
1078 vndev = 2;
1079 mddevice = make_vn(fname);
1080 if (ret) {
[541]1081 sprintf(tmp, "make_vn of %s failed; unable to verify ISO\n",
[128]1082 fname);
1083 log_to_screen(tmp);
1084 return (1);
1085 }
1086 sprintf(command, "mount_cd9660 %s %s", mddevice, mountpoint);
[1]1087#else
[128]1088 sprintf(command, "mount -o loop,ro -t iso9660 %s %s", fname,
1089 mountpoint);
[1]1090#endif
[128]1091 if (run_program_and_log_output(command, FALSE)) {
[541]1092 sprintf(tmp, "%s failed; unable to mount ISO image\n",
[128]1093 command);
1094 log_to_screen(tmp);
1095 return (1);
1096 }
[1]1097 }
[128]1098 log_msg(2, "OK, I've mounted the ISO/CD\n");
1099 sprintf(tmp, "%s/archives/NOT-THE-LAST", mountpoint);
1100 if (!does_file_exist(tmp)) {
1101 log_msg
1102 (2,
1103 "This is the last CD. I am therefore setting bkpinfo->verify_data to FALSE.");
1104 bkpinfo->verify_data = FALSE;
[1]1105/*
1106 (a) It's an easy way to tell the calling subroutine that we've finished &
1107 there are no more CD's to be verified; (b) It stops the post-backup verifier
1108 from running after the per-CD verifier has run too.
1109*/
[128]1110 }
[1645]1111 verify_afioballs_on_CD(mountpoint);
[128]1112 iamhere("before verify_all_slices");
[1645]1113 verify_all_slices_on_CD(mountpoint);
[1]1114
1115#ifdef __FreeBSD__
[128]1116 ret = 0;
1117 sprintf(command, "umount %s", mountpoint);
1118 ret += system(command);
1119 ret += kick_vn(mddevice);
1120 if (ret)
[1]1121#else
[128]1122 sprintf(command, "umount %s", mountpoint);
1123 if (system(command))
[1]1124#endif
1125 {
[128]1126 sprintf(tmp, "%s failed; unable to unmount ISO image\n", command);
1127 log_to_screen(tmp);
1128 retval++;
1129 } else {
1130 log_msg(2, "OK, I've unmounted the ISO file\n");
[1]1131 }
[128]1132 if (!does_file_exist(fname)) {
1133 sprintf(command, "umount %s", bkpinfo->media_device);
1134 run_program_and_log_output(command, 2);
1135 if (!bkpinfo->please_dont_eject
1136 && eject_device(bkpinfo->media_device)) {
1137 log_msg(2, "Failed to eject CD-ROM drive");
1138 }
1139 }
1140 paranoid_free(command);
1141 paranoid_free(mountpoint);
1142 paranoid_free(tmp);
1143 paranoid_free(fname);
1144 return (retval);
[1]1145}
1146
1147/**
1148 * Verify all backups on tape.
1149 * This should be done after the backup process has already closed the tape.
1150 * @param bkpinfo The backup information structure. Passed to various helper functions.
1151 * @return 0 for success (even if thee were differences), nonzero for failure.
1152 * @ingroup verifyGroup
1153 */
[1645]1154int verify_tape_backups()
[1]1155{
1156
[128]1157 /*@ int ************************************************************ */
1158 int retval = 0;
[1]1159
[128]1160 /*@ buffers ******************************************************** */
1161 char tmp[MAX_STR_LEN];
1162 char changed_files_fname[MAX_STR_LEN];
[1]1163
[128]1164 /*@ long *********************************************************** */
1165 long diffs = 0;
[1]1166
[128]1167 assert(bkpinfo != NULL);
[1]1168
[128]1169 log_msg(3, "verify_tape_backups --- starting");
[541]1170 log_to_screen("Verifying backups");
[1645]1171 openin_tape();
[1]1172/* verify archives themselves */
[1645]1173 retval += verify_afioballs_from_stream();
1174 retval += verify_biggiefiles_from_stream();
[1]1175/* find the final blocks */
[128]1176 paranoid_system("sync");
1177 sleep(2);
[1645]1178 closein_tape();
[1]1179/* close tape; exit */
1180// fclose(g_tape_stream); <-- not needed; is handled by closein_tape()
[1644]1181 sprintf(tmp, "rm -f %s/biggies.changed %s/changed.files 2> /dev/null", bkpinfo->tmpdir, bkpinfo->tmpdir);
1182 paranoid_system(tmp);
1183 sprintf(changed_files_fname, "%s/changed.files", bkpinfo->tmpdir);
[128]1184 sprintf(tmp,
[911]1185 "grep -E '^%s:.*$' %s | cut -d'\"' -f2 | sort -u | awk '{print \"/\"$0;};' | tr -s '/' '/' | grep -v \"(total of\" | grep -v \"incheckentry.*xwait\" | grep -vE '^/afio:.*$' | grep -vE '^dev/.*$' > %s",
[273]1186 (bkpinfo->use_star) ? "star" : "afio", MONDO_LOGFILE,
[128]1187 changed_files_fname);
1188 log_msg(2, "Running command to derive list of changed files");
1189 log_msg(2, tmp);
1190 if (system(tmp)) {
1191 if (does_file_exist(changed_files_fname)
1192 && length_of_file(changed_files_fname) > 2) {
1193 log_to_screen
[541]1194 ("Warning - unable to check logfile to derive list of changed files");
[128]1195 } else {
1196 log_to_screen
[541]1197 ("No differences found. Therefore, no 'changed.files' text file.");
[128]1198 }
1199 }
[1644]1200 sprintf(tmp, "cat %s/biggies.changed >> %s", bkpinfo->tmpdir, changed_files_fname);
[128]1201 paranoid_system(tmp);
[1]1202
[128]1203 diffs = count_lines_in_file(changed_files_fname);
1204 if (diffs > 0) {
[1644]1205 sprintf(tmp, "cp -f %s %s/changed.files", changed_files_fname,
1206 MINDI_CACHE);
[128]1207 run_program_and_log_output(tmp, FALSE);
1208 sprintf(tmp,
[1644]1209 "%ld files differed from live filesystem; type less %s or less %s/changed.files to see",
1210 diffs, changed_files_fname, MINDI_CACHE);
[128]1211 log_msg(0, tmp);
[1644]1212 log_to_screen("See "MINDI_CACHE"/changed.files for a list of nonmatching files.");
1213 log_to_screen("The files probably changed on filesystem, not on backup media.");
[128]1214 // retval++;
1215 }
1216 return (retval);
[1]1217}
1218
1219
1220
1221/**
1222 * Generate the filename of a tarball to verify.
1223 * @param bkpinfo The backup information structure. @c bkpinfo->zip_suffix is the only field used.
1224 * @param mountpoint The directory where the CD/DVD/ISO is mounted.
1225 * @param setno The afioball number to get the location of.
1226 * @return The absolute path to the afioball.
1227 * @note The returned string points to static data that will be overwritten with each call.
1228 * @ingroup stringGroup
1229 */
[1645]1230char *vfy_tball_fname(char *mountpoint, int setno)
[1]1231{
[128]1232 /*@ buffers ******************************************************* */
1233 static char output[MAX_STR_LEN];
[1]1234
[128]1235 assert(bkpinfo != NULL);
1236 assert_string_is_neither_NULL_nor_zerolength(mountpoint);
1237 sprintf(output, "%s/archives/%d.star.%s", mountpoint, setno,
1238 bkpinfo->zip_suffix);
1239 if (!does_file_exist(output)) {
1240 sprintf(output, "%s/archives/%d.afio.%s", mountpoint, setno,
1241 bkpinfo->zip_suffix);
1242 }
1243 return (output);
[1]1244}
Note: See TracBrowser for help on using the repository browser.