From 6d844f6edfe53fef244c92e8b418886fc29935ae Mon Sep 17 00:00:00 2001 From: Kern Sibbald Date: Tue, 22 Feb 2005 07:52:16 +0000 Subject: [PATCH] - Add some changes submitted by a user for HP client build. Not all changes accepted. - Rework code in filed/backup.c to ease #ifdefing and make program flow more obvious. - Split DVD code out of dev.c into dvd.c git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@1838 91ce42f0-d328-0410-95d8-f526ca767f89 --- bacula/autoconf/configure.in | 1 + bacula/configure | 1 + bacula/kernstodo | 3 +- bacula/src/console/conio.c | 19 +- bacula/src/filed/backup.c | 159 ++++---- bacula/src/lib/bsys.c | 2 +- bacula/src/stored/Makefile.in | 16 +- bacula/src/stored/askdir.c | 4 + bacula/src/stored/dev.c | 713 ++-------------------------------- bacula/src/stored/dvd.c | 695 +++++++++++++++++++++++++++++++++ bacula/src/version.h | 3 +- 11 files changed, 845 insertions(+), 771 deletions(-) create mode 100644 bacula/src/stored/dvd.c diff --git a/bacula/autoconf/configure.in b/bacula/autoconf/configure.in index cd22084c4a..846afa936d 100644 --- a/bacula/autoconf/configure.in +++ b/bacula/autoconf/configure.in @@ -1766,6 +1766,7 @@ hpux) CFLAGS="$(CFLAGS) -D_XOPEN_SOURCE_EXTENDED=1" DISTVER=`uname -r` TAPEDRIVE="/dev/rmt/0hnb" + PTHREAD_LIB="-lpthread" ;; irix) DISTVER=`uname -r` diff --git a/bacula/configure b/bacula/configure index 1c1693a48e..df29d69ca6 100755 --- a/bacula/configure +++ b/bacula/configure @@ -21323,6 +21323,7 @@ hpux) CFLAGS="$(CFLAGS) -D_XOPEN_SOURCE_EXTENDED=1" DISTVER=`uname -r` TAPEDRIVE="/dev/rmt/0hnb" + PTHREAD_LIB="-lpthread" ;; irix) DISTVER=`uname -r` diff --git a/bacula/kernstodo b/bacula/kernstodo index 229c8bc80a..2348d10e62 100644 --- a/bacula/kernstodo +++ b/bacula/kernstodo @@ -30,6 +30,8 @@ Suggestions for Preben: - Optimized bootstrap. For 1.37: +- Implement "update device" from SD so that DIR will + always have current version of device. - Add disk seeking on restore. - Add Python writable variable for changing the Priority, Client, Storage, JobStatus (error), ... @@ -1218,4 +1220,3 @@ Block Position: 0 - Try to open a device on each Job if it was not opened when the SD started. - Add dump of VolSessionId/Time and FileIndex with bls. - diff --git a/bacula/src/console/conio.c b/bacula/src/console/conio.c index 19f10bde3f..94ffeaea1c 100755 --- a/bacula/src/console/conio.c +++ b/bacula/src/console/conio.c @@ -59,6 +59,8 @@ extern "C" int tgetent(void *, const char *); extern "C" int tgetnum(const char *); extern "C" char *tgetstr (const char*, char**); extern "C" char *tgoto (const char *, int, int); +#elif HAVE_HPUX_OS +#include #else #include #endif @@ -66,8 +68,13 @@ extern "C" char *tgoto (const char *, int, int); /* From termios library */ +#ifdef HAVE_HPUX_OS +static char *BC; +static char *UP; +#else extern char *BC; extern char *UP; +#endif /* Forward referenced functions */ extern "C" { @@ -362,7 +369,7 @@ static void dump_stab() for (j=0; jlen; j++) { c = tstab->str[j]; if (c < 0x20 || c > 0x7F) { - sprintf(buf, " 0x%x ", c); + sprintf(buf, " 0x%x ", c); t_send(buf); } else { buf[0] = c; @@ -370,7 +377,7 @@ static void dump_stab() t_sendl(buf, 1); } } - sprintf(buf, " func=%d len=%d\n\r", tstab->func, tstab->len); + sprintf(buf, " func=%d len=%d\n\r", tstab->func, tstab->len); t_send(buf); } } @@ -460,7 +467,7 @@ input_line(char *string, int length) } switch (c=input_char()) { case F_RETURN: /* CR */ - t_sendl("\r\n", 2); /* yes, print it and */ + t_sendl("\r\n", 2); /* yes, print it and */ goto done; /* get out */ case F_CLRSCRN: /* clear screen */ asclrs(); @@ -496,7 +503,7 @@ input_line(char *string, int length) backup(curline); delchr(1, curline, sizeof(curline)); if (cp == 0) { - t_char(' '); + t_char(' '); t_char(0x8); } break; @@ -534,7 +541,7 @@ input_line(char *string, int length) t_clrline(0, t_width); /* erase line */ cp = 0; cl = 0; /* reset cursor counter */ - t_char(' '); + t_char(' '); t_char(0x8); break; case F_SOL: @@ -873,7 +880,7 @@ dump(struct lstr *ptr, char *msg) printf("%s buf=%x nextl=%x prevl=%x len=%d used=%d\n", msg,ptr,ptr->nextl,ptr->prevl,ptr->len,ptr->used); if (ptr->used) - printf("line=%s\n",&ptr->line); + printf("line=%s\n",&ptr->line); } #endif /* DEBUGOUT */ diff --git a/bacula/src/filed/backup.c b/bacula/src/filed/backup.c index de5de2e02e..545ae8e3a6 100644 --- a/bacula/src/filed/backup.c +++ b/bacula/src/filed/backup.c @@ -30,11 +30,11 @@ #include "bacula.h" #include "filed.h" +/* Forward referenced functions */ static int save_file(FF_PKT *ff_pkt, void *pkt); static int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, struct CHKSUM *chksum); -#ifdef HAVE_ACL -static int read_and_send_acl(JCR *jcr, int acltype, int stream); -#endif +static bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream); +static bool read_and_send_acl(JCR *jcr, int acltype, int stream); /* * Find all the requested files and send them @@ -129,9 +129,7 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr) */ static int save_file(FF_PKT *ff_pkt, void *vjcr) { - char attribs[MAXSTRING]; - char attribsEx[MAXSTRING]; - int stat, attr_stream, data_stream; + int stat, data_stream; struct CHKSUM chksum; BSOCK *sd; JCR *jcr = (JCR *)vjcr; @@ -227,65 +225,9 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr) Dmsg1(130, "bfiled: sending %s to stored\n", ff_pkt->fname); - /* Find what data stream we will use, then encode the attributes */ - data_stream = select_data_stream(ff_pkt); - encode_stat(attribs, ff_pkt, data_stream); - - /* Now possibly extend the attributes */ - attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt); - - Dmsg3(300, "File %s\nattribs=%s\nattribsEx=%s\n", ff_pkt->fname, attribs, attribsEx); - - P(jcr->mutex); - jcr->JobFiles++; /* increment number of files sent */ - ff_pkt->FileIndex = jcr->JobFiles; /* return FileIndex */ - pm_strcpy(jcr->last_fname, ff_pkt->fname); - V(jcr->mutex); - - /* - * Send Attributes header to Storage daemon - * - */ - if (!bnet_fsend(sd, "%ld %d 0", jcr->JobFiles, attr_stream)) { - Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), - bnet_strerror(sd)); - return 0; - } - Dmsg1(300, ">stored: attrhdr %s\n", sd->msg); - - /* - * Send file attributes to Storage daemon - * File_index - * File type - * Filename (full path) - * Encoded attributes - * Link name (if type==FT_LNK or FT_LNKSAVED) - * Encoded extended-attributes (for Win32) - * - * For a directory, link is the same as fname, but with trailing - * slash. For a linked file, link is the link. - */ - if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) { - Dmsg2(300, "Link %s to %s\n", ff_pkt->fname, ff_pkt->link); - stat = bnet_fsend(sd, "%ld %d %s%c%s%c%s%c%s%c", jcr->JobFiles, - ff_pkt->type, ff_pkt->fname, 0, attribs, 0, ff_pkt->link, 0, - attribsEx, 0); - } else if (ff_pkt->type == FT_DIREND) { - /* Here link is the canonical filename (i.e. with trailing slash) */ - stat = bnet_fsend(sd, "%ld %d %s%c%s%c%c%s%c", jcr->JobFiles, - ff_pkt->type, ff_pkt->link, 0, attribs, 0, 0, attribsEx, 0); - } else { - stat = bnet_fsend(sd, "%ld %d %s%c%s%c%c%s%c", jcr->JobFiles, - ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0); - } - - Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg); - if (!stat) { - Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), - bnet_strerror(sd)); + if (!encode_and_send_attributes(jcr, ff_pkt, data_stream)) { return 0; } - bnet_sig(sd, BNET_EOD); /* indicate end of attributes data */ /* * Setup for signature handling. @@ -378,7 +320,6 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr) } #endif -#ifdef HAVE_ACL if (ff_pkt->flags & FO_ACL) { /* Read access ACLs for files, dirs and links */ if (!read_and_send_acl(jcr, BACL_TYPE_ACCESS, STREAM_UNIX_ATTRIBUTES_ACCESS_ACL)) { @@ -391,7 +332,6 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr) } } } -#endif /* Terminate any signature and send it to Storage daemon and the Director */ if (chksum.updated) { @@ -571,12 +511,12 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, struct CHKSUM *chksum) return 1; } -#ifdef HAVE_ACL /* * Read and send an ACL for the last encountered file. */ -static int read_and_send_acl(JCR *jcr, int acltype, int stream) +static bool read_and_send_acl(JCR *jcr, int acltype, int stream) { +#ifdef HAVE_ACL BSOCK *sd = jcr->store_bsock; POOLMEM *msgsave; int len; @@ -584,17 +524,17 @@ static int read_and_send_acl(JCR *jcr, int acltype, int stream) len = bacl_get(jcr, acltype); if (len < 0) { Jmsg1(jcr, M_WARNING, 0, "Error reading ACL of %s\n", jcr->last_fname); - return 1; + return true; } if (len == 0) { - return 1; /* no ACL */ + return true; /* no ACL */ } /* Send header */ if (!bnet_fsend(sd, "%ld %d 0", jcr->JobFiles, stream)) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), bnet_strerror(sd)); - return 0; + return false; } /* Send the buffer to the storage deamon */ @@ -607,7 +547,7 @@ static int read_and_send_acl(JCR *jcr, int acltype, int stream) sd->msglen = 0; Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), bnet_strerror(sd)); - return 0; + return false; } jcr->JobBytes += sd->msglen; @@ -615,10 +555,83 @@ static int read_and_send_acl(JCR *jcr, int acltype, int stream) if (!bnet_sig(sd, BNET_EOD)) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), bnet_strerror(sd)); - return 0; + return false; } Dmsg1(200, "ACL of file: %s successfully backed up!\n", jcr->last_fname); - return 1; +#endif + return true; } + +static bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_stream) +{ + BSOCK *sd = jcr->store_bsock; + char attribs[MAXSTRING]; + char attribsEx[MAXSTRING]; + int attr_stream; + int stat; +#ifdef FD_NO_SEND_TEST + return true; #endif + + /* Find what data stream we will use, then encode the attributes */ + data_stream = select_data_stream(ff_pkt); + encode_stat(attribs, ff_pkt, data_stream); + + /* Now possibly extend the attributes */ + attr_stream = encode_attribsEx(jcr, attribsEx, ff_pkt); + + Dmsg3(300, "File %s\nattribs=%s\nattribsEx=%s\n", ff_pkt->fname, attribs, attribsEx); + + P(jcr->mutex); + jcr->JobFiles++; /* increment number of files sent */ + ff_pkt->FileIndex = jcr->JobFiles; /* return FileIndex */ + pm_strcpy(jcr->last_fname, ff_pkt->fname); + V(jcr->mutex); + + /* + * Send Attributes header to Storage daemon + * + */ + if (!bnet_fsend(sd, "%ld %d 0", jcr->JobFiles, attr_stream)) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + bnet_strerror(sd)); + return false; + } + Dmsg1(300, ">stored: attrhdr %s\n", sd->msg); + + /* + * Send file attributes to Storage daemon + * File_index + * File type + * Filename (full path) + * Encoded attributes + * Link name (if type==FT_LNK or FT_LNKSAVED) + * Encoded extended-attributes (for Win32) + * + * For a directory, link is the same as fname, but with trailing + * slash. For a linked file, link is the link. + */ + if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) { + Dmsg2(300, "Link %s to %s\n", ff_pkt->fname, ff_pkt->link); + stat = bnet_fsend(sd, "%ld %d %s%c%s%c%s%c%s%c", jcr->JobFiles, + ff_pkt->type, ff_pkt->fname, 0, attribs, 0, ff_pkt->link, 0, + attribsEx, 0); + } else if (ff_pkt->type == FT_DIREND) { + /* Here link is the canonical filename (i.e. with trailing slash) */ + stat = bnet_fsend(sd, "%ld %d %s%c%s%c%c%s%c", jcr->JobFiles, + ff_pkt->type, ff_pkt->link, 0, attribs, 0, 0, attribsEx, 0); + } else { + stat = bnet_fsend(sd, "%ld %d %s%c%s%c%c%s%c", jcr->JobFiles, + ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0); + } + + Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg); + if (!stat) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + bnet_strerror(sd)); + return false; + } + bnet_sig(sd, BNET_EOD); /* indicate end of attributes data */ + return true; +} diff --git a/bacula/src/lib/bsys.c b/bacula/src/lib/bsys.c index ba14452c0e..a66914b085 100644 --- a/bacula/src/lib/bsys.c +++ b/bacula/src/lib/bsys.c @@ -8,7 +8,7 @@ * Version $Id$ */ /* - Copyright (C) 2000-2004 Kern Sibbald + Copyright (C) 2000-2005 Kern Sibbald This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/bacula/src/stored/Makefile.in b/bacula/src/stored/Makefile.in index 782721c1f8..bb3165c2da 100644 --- a/bacula/src/stored/Makefile.in +++ b/bacula/src/stored/Makefile.in @@ -22,7 +22,7 @@ SVRSRCS = stored.c ansi_label.c \ autochanger.c acquire.c append.c \ askdir.c authenticate.c \ block.c butil.c dev.c \ - device.c dircmd.c fd_cmds.c job.c \ + device.c dircmd.c dvd.c fd_cmds.c job.c \ label.c match_bsr.c mount.c parse_bsr.c \ python.c \ read.c read_record.c record.c \ @@ -31,7 +31,7 @@ SVROBJS = stored.o ansi_label.o \ autochanger.o acquire.o append.o \ askdir.o authenticate.o \ block.o butil.o dev.o \ - device.o dircmd.o fd_cmds.o job.o \ + device.o dircmd.o dvd.o fd_cmds.o job.o \ label.o match_bsr.o mount.o parse_bsr.o \ python.o \ read.o read_record.o record.o \ @@ -39,35 +39,35 @@ SVROBJS = stored.o ansi_label.o \ # btape TAPESRCS = btape.c block.c butil.c dev.c device.c label.c \ - ansi_label.c \ + ansi_label.c dvd.c \ acquire.c mount.c record.c read_record.c \ stored_conf.c match_bsr.c parse_bsr.c spool.c TAPEOBJS = btape.o block.o butil.o dev.o device.o label.o \ - ansi_label.o \ + ansi_label.o dvd.o \ autochanger.o acquire.o mount.o record.o read_record.o \ stored_conf.o match_bsr.o parse_bsr.o spool.o # bls BLSOBJS = bls.o block.o butil.o device.o dev.o label.o match_bsr.o \ - ansi_label.o \ + ansi_label.o dvd.o \ autochanger.o acquire.o mount.o parse_bsr.o record.o \ read_record.o stored_conf.o spool.o # bextract BEXTOBJS = bextract.o block.o device.o dev.o label.o record.o \ - ansi_label.o \ + ansi_label.o dvd.o \ autochanger.o acquire.o mount.o match_bsr.o parse_bsr.o butil.o \ read_record.o stored_conf.o spool.o # bscan SCNOBJS = bscan.o block.o device.o dev.o label.o \ - ansi_label.o \ + ansi_label.o dvd.o \ autochanger.o acquire.o mount.o record.o match_bsr.o parse_bsr.o \ butil.o read_record.o stored_conf.o spool.o # bcopy COPYOBJS = bcopy.o block.o device.o dev.o label.o \ - ansi_label.o \ + ansi_label.o dvd.o \ autochanger.o acquire.o mount.o record.o match_bsr.o parse_bsr.o \ butil.o read_record.o stored_conf.o spool.o diff --git a/bacula/src/stored/askdir.c b/bacula/src/stored/askdir.c index 7706e0458a..ac170c3bfa 100644 --- a/bacula/src/stored/askdir.c +++ b/bacula/src/stored/askdir.c @@ -307,6 +307,10 @@ bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) BSOCK *dir = jcr->dir_bsock; ser_declare; +#ifdef NO_ATTRIBUTES_TEST + return true; +#endif + dir->msglen = sprintf(dir->msg, FileAttributes, jcr->Job); dir->msg = check_pool_memory_size(dir->msg, dir->msglen + sizeof(DEV_RECORD) + rec->data_len); diff --git a/bacula/src/stored/dev.c b/bacula/src/stored/dev.c index 339007acdb..326f09e991 100644 --- a/bacula/src/stored/dev.c +++ b/bacula/src/stored/dev.c @@ -80,14 +80,16 @@ #include "bacula.h" #include "stored.h" +/* Functions in dvd.c */ +void get_filename(DEVICE *dev, char *VolName, POOL_MEM& archive_name); +int mount_dev(DEVICE* dev, int timeout); +int unmount_dev(DEVICE *dev, int timeout); +void update_free_space_dev(DEVICE* dev); + + /* Forward referenced functions */ void set_os_device_parameters(DEVICE *dev); -int mount_dev(DEVICE* dev, int timeout); -int unmount_dev(DEVICE* dev, int timeout); -int write_part(DEVICE *dev); -char *edit_device_codes_dev(DEVICE* dev, char *omsg, const char *imsg); -static void get_filename(DEVICE *dev, char *VolName, POOL_MEM& archive_name); -static void update_free_space_dev(DEVICE* dev); +static bool dev_get_os_pos(DEVICE *dev, struct mtget *mt_stat); /* * Allocate and initialize the DEVICE structure @@ -252,42 +254,6 @@ init_dev(JCR *jcr, DEVICE *dev, DEVRES *device) return dev; } -/* - * Write the current volume/part filename to archive_name. - */ -static void get_filename(DEVICE *dev, char *VolName, POOL_MEM& archive_name) -{ - char partnumber[20]; - - if (dev->is_dvd()) { - /* If we try to open the last part, just open it from disk, - * otherwise, open it from the spooling directory */ - if (dev->part < dev->num_parts) { - pm_strcpy(archive_name, dev->device->mount_point); - } else { - /* Use the working directory if spool directory is not defined */ - if (dev->device->spool_directory) { - pm_strcpy(archive_name, dev->device->spool_directory); - } else { - pm_strcpy(archive_name, working_directory); - } - } - } else { - pm_strcpy(archive_name, dev->dev_name); - } - - if (archive_name.c_str()[strlen(archive_name.c_str())-1] != '/') { - pm_strcat(archive_name, "/"); - } - pm_strcat(archive_name, VolName); - /* if part != 0, append .# to the filename (where # is the part number) */ - if (dev->is_dvd() && dev->part != 0) { - pm_strcat(archive_name, "."); - bsnprintf(partnumber, sizeof(partnumber), "%d", dev->part); - pm_strcat(archive_name, partnumber); - } -} - /* * Open the device with the operating system and * initialize buffer pointers. @@ -406,14 +372,12 @@ open_dev(DEVICE *dev, char *VolName, int mode) } get_filename(dev, VolName, archive_name); - if (dev->is_dvd()) { - if (mount_dev(dev, 1) < 0) { - Mmsg(dev->errmsg, _("Could not mount archive device %s.\n"), - dev->dev_name); - Emsg0(M_FATAL, 0, dev->errmsg); - dev->fd = -1; - return dev->fd; - } + if (mount_dev(dev, 1) < 0) { + Mmsg(dev->errmsg, _("Could not mount archive device %s.\n"), + dev->dev_name); + Emsg0(M_FATAL, 0, dev->errmsg); + dev->fd = -1; + return dev->fd; } Dmsg2(29, "open_dev: device is disk %s (mode:%d)\n", archive_name.c_str(), mode); @@ -465,562 +429,6 @@ open_dev(DEVICE *dev, char *VolName, int mode) return dev->fd; } -/* (Un)mount the device */ -int do_mount_dev(DEVICE* dev, int mount, int dotimeout) { - POOL_MEM ocmd(PM_FNAME); - POOLMEM* results; - results = get_pool_memory(PM_MESSAGE); - char* icmd; - int status, timeout; - - if (mount) { - icmd = dev->device->mount_command; - } - else { - icmd = dev->device->unmount_command; - } - - edit_device_codes_dev(dev, ocmd.c_str(), icmd); - - Dmsg2(29, "do_mount_dev: cmd=%s state=%d\n", ocmd.c_str(), dev->state & ST_MOUNTED); - - if (dotimeout) { - /* Try at most 5 times to (un)mount the device. This should perhaps be configurable. */ - timeout = 5; - } - else { - timeout = 0; - } - /* If busy retry each second */ - while ((status = run_program_full_output(ocmd.c_str(), dev->max_open_wait/2, results)) != 0) { - if (--timeout > 0) { - Dmsg2(40, "Device %s cannot be (un)mounted. Retrying... ERR=%s\n", dev->dev_name, results); - /* Sometimes the device cannot be mounted because it is already mounted. - * Try to unmount it, then remount it */ - if (mount) { - Dmsg1(40, "Trying to unmount the device %s...\n", dev->dev_name); - do_mount_dev(dev, 0, 0); - } - bmicrosleep(1, 0); - continue; - } - free_pool_memory(results); - Dmsg2(40, "Device %s cannot be mounted. ERR=%s\n", dev->dev_name, results); - return -1; - } - - if (mount) { - dev->state |= ST_MOUNTED; - } - else { - dev->state &= ~ST_MOUNTED; - } - free_pool_memory(results); - - Dmsg1(29, "do_mount_dev: end_state=%d\n", dev->state & ST_MOUNTED); - return 0; -} - -/* Only for devices that requires mount. - * Try to find the volume name of the loaded device, and open the - * first part of this volume. - * - * Returns 0 if read_dev_volume_label can now read the label, - * -1 if an error occured, and read_dev_volume_label_guess must abort with an IO_ERROR. - * - * To guess the device name, it lists all the files on the DVD, and searches for a - * file which has a minimum size (500 bytes). If this file has a numeric extension, - * like part files, try to open the file which has no extension (e.g. the first - * part file). - * So, if the DVD does not contains a Bacula volume, a random file is opened, - * and no valid label could be read from this file. - * - * It is useful, so the operator can be told that a wrong volume is mounted, with - * the label name of the current volume. We can also check that the currently - * mounted disk is writable. (See also read_dev_volume_label_guess in label.c). - * - * Note that if the right volume is mounted, open_guess_name_dev returns the same - * result as an usual open_dev. - */ -int open_guess_name_dev(DEVICE *dev) -{ - Dmsg1(29, "open_guess_name_dev: dev=%s\n", dev->dev_name); - POOL_MEM guessedname(PM_FNAME); - DIR* dp; - struct dirent *entry, *result; - struct stat statp; - int index; - int name_max; - - if (!dev->is_dvd()) { - Dmsg1(100, "open_guess_name_dev: device does not require mount, returning 0. dev=%s\n", dev->dev_name); - return 0; - } - -#ifndef HAVE_DIRENT_H - Dmsg0(29, "open_guess_name_dev: readdir not available, cannot guess volume name\n"); - return 0; -#endif - - update_free_space_dev(dev); - - if (mount_dev(dev, 1) < 0) { - /* If the device cannot be mounted, check if it is writable */ - if (dev->free_space_errno >= 0) { - Dmsg1(100, "open_guess_name_dev: device cannot be mounted, but it seems to be writable, returning 0. dev=%s\n", dev->dev_name); - return 0; - } else { - Dmsg1(100, "open_guess_name_dev: device cannot be mounted, and is not writable, returning 0. dev=%s\n", dev->dev_name); - /* read_dev_volume_label_guess must now check dev->free_space_errno to understand that the media is not writable. */ - return 0; - } - } - - name_max = pathconf(".", _PC_NAME_MAX); - if (name_max < 1024) { - name_max = 1024; - } - - if (!(dp = opendir(dev->device->mount_point))) { - berrno be; - dev->dev_errno = errno; - Dmsg3(29, "open_guess_name_dev: failed to open dir %s (dev=%s), ERR=%s\n", dev->device->mount_point, dev->dev_name, be.strerror()); - return -1; - } - - entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 100); - while (1) { - if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) { - dev->dev_errno = ENOENT; - Dmsg2(29, "open_guess_name_dev: failed to find suitable file in dir %s (dev=%s)\n", dev->device->mount_point, dev->dev_name); - closedir(dp); - return -1; - } - - ASSERT(name_max+1 > (int)sizeof(struct dirent) + (int)NAMELEN(entry)); - - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { - continue; - } - - pm_strcpy(guessedname, dev->device->mount_point); - if (guessedname.c_str()[strlen(guessedname.c_str())-1] != '/') { - pm_strcat(guessedname, "/"); - } - pm_strcat(guessedname, entry->d_name); - - if (stat(guessedname.c_str(), &statp) < 0) { - berrno be; - Dmsg3(29, "open_guess_name_dev: failed to stat %s (dev=%s), ERR=%s\n", - guessedname.c_str(), dev->dev_name, be.strerror()); - continue; - } - - if (!S_ISREG(statp.st_mode) || (statp.st_size < 500)) { - Dmsg2(100, "open_guess_name_dev: %s is not a regular file, or less than 500 bytes (dev=%s)\n", - guessedname.c_str(), dev->dev_name); - continue; - } - - /* Ok, we found a good file, remove the part extension if possible. */ - for (index = strlen(guessedname.c_str())-1; index >= 0; index--) { - if ((guessedname.c_str()[index] == '/') || - (guessedname.c_str()[index] < '0') || - (guessedname.c_str()[index] > '9')) { - break; - } - if (guessedname.c_str()[index] == '.') { - guessedname.c_str()[index] = '\0'; - break; - } - } - - if ((stat(guessedname.c_str(), &statp) < 0) || (statp.st_size < 500)) { - /* The file with extension truncated does not exists or is too small, so use it with its extension. */ - berrno be; - Dmsg3(100, "open_guess_name_dev: failed to stat %s (dev=%s), using the file with its extension, ERR=%s\n", - guessedname.c_str(), dev->dev_name, be.strerror()); - pm_strcpy(guessedname, dev->device->mount_point); - if (guessedname.c_str()[strlen(guessedname.c_str())-1] != '/') { - pm_strcat(guessedname, "/"); - } - pm_strcat(guessedname, entry->d_name); - continue; - } - break; - } - - closedir(dp); - - if (dev->fd >= 0) { - close(dev->fd); - } - - if ((dev->fd = open(guessedname.c_str(), O_RDONLY | O_BINARY)) < 0) { - berrno be; - dev->dev_errno = errno; - Dmsg3(29, "open_guess_name_dev: failed to open %s (dev=%s), ERR=%s\n", - guessedname.c_str(), dev->dev_name, be.strerror()); - if (open_first_part(dev) < 0) { - berrno be; - dev->dev_errno = errno; - Mmsg1(&dev->errmsg, _("Could not open_first_part, ERR=%s\n"), be.strerror()); - Emsg0(M_FATAL, 0, dev->errmsg); - } - return -1; - } - dev->part_start = 0; - dev->part_size = statp.st_size; - dev->part = 0; - dev->state |= ST_OPENED; - dev->use_count = 1; - - Dmsg2(29, "open_guess_name_dev: %s opened (dev=%s)\n", guessedname.c_str(), dev->dev_name); - - return 0; -} - -/* Mount the device. - * If timeout, wait until the mount command returns 0. - * If !timeout, try to mount the device only once. - */ -int mount_dev(DEVICE* dev, int timeout) -{ - if (dev->state & ST_MOUNTED) { - Dmsg0(100, "mount_dev: Device already mounted\n"); - return 0; - } else { - return do_mount_dev(dev, 1, timeout); - } -} - -/* Unmount the device - * If timeout, wait until the unmount command returns 0. - * If !timeout, try to unmount the device only once. - */ -int unmount_dev(DEVICE* dev, int timeout) -{ - if (dev->state & ST_MOUNTED) { - return do_mount_dev(dev, 0, timeout); - } else { - Dmsg0(100, "mount_dev: Device already unmounted\n"); - return 0; - } -} - -/* Update the free space on the device */ -static void update_free_space_dev(DEVICE* dev) -{ - POOL_MEM ocmd(PM_FNAME); - POOLMEM* results; - char* icmd; - int timeout; - long long int free; - - icmd = dev->device->free_space_command; - - if (!icmd) { - dev->free_space = 0; - dev->free_space_errno = 0; - Dmsg2(29, "update_free_space_dev: free_space=%d, free_space_errno=%d (!icmd)\n", dev->free_space, dev->free_space_errno); - return; - } - - edit_device_codes_dev(dev, ocmd.c_str(), icmd); - - Dmsg1(29, "update_free_space_dev: cmd=%s\n", ocmd.c_str()); - - results = get_pool_memory(PM_MESSAGE); - - /* Try at most 3 times to get the free space on the device. This should perhaps be configurable. */ - timeout = 3; - - while (1) { - char ed1[50]; - if (run_program_full_output(ocmd.c_str(), dev->max_open_wait/2, results) == 0) { - Dmsg1(100, "Free space program run : %s\n", results); - free = str_to_int64(results); - if (free >= 0) { - dev->free_space = free; - dev->free_space_errno = 1; - Mmsg0(dev->errmsg, ""); - break; - } - } - dev->free_space = 0; - dev->free_space_errno = -EPIPE; - Mmsg1(dev->errmsg, "Cannot run free space command (%s)\n", results); - - if (--timeout > 0) { - Dmsg4(40, "Cannot get free space on device %s. free_space=%s, " - "free_space_errno=%d ERR=%s\n", dev->dev_name, - edit_uint64(dev->free_space, ed1), dev->free_space_errno, - dev->errmsg); - bmicrosleep(1, 0); - continue; - } - - dev->dev_errno = -dev->free_space_errno; - Dmsg4(40, "Cannot get free space on device %s. free_space=%s, " - "free_space_errno=%d ERR=%s\n", - dev->dev_name, edit_uint64(dev->free_space, ed1), - dev->free_space_errno, dev->errmsg); - break; - } - - free_pool_memory(results); - Dmsg2(29, "update_free_space_dev: free_space=%lld, free_space_errno=%d\n", dev->free_space, dev->free_space_errno); - return; -} - -int write_part(DEVICE *dev) -{ - Dmsg1(29, "write_part: device is %s\n", dev->dev_name); - - if (unmount_dev(dev, 1) < 0) { - Dmsg0(29, "write_part: unable to unmount the device\n"); - } - - POOL_MEM ocmd(PM_FNAME); - POOLMEM *results; - results = get_pool_memory(PM_MESSAGE); - char* icmd; - int status; - int timeout; - - icmd = dev->device->write_part_command; - - edit_device_codes_dev(dev, ocmd.c_str(), icmd); - - /* Wait at most the time a maximum size part is written in DVD 0.5x speed - * FIXME: Minimum speed should be in device configuration - */ - timeout = dev->max_open_wait + (dev->max_part_size/(1350*1024/2)); - - Dmsg2(29, "write_part: cmd=%s timeout=%d\n", ocmd.c_str(), timeout); - - status = run_program_full_output(ocmd.c_str(), timeout, results); - if (status != 0) { - Mmsg1(dev->errmsg, "Error while writing current part to the DVD: %s", results); - dev->dev_errno = EIO; - free_pool_memory(results); - return -1; - } - else { - Dmsg1(29, "write_part: command output=%s\n", results); - POOL_MEM archive_name(PM_FNAME); - get_filename(dev, dev->VolCatInfo.VolCatName, archive_name); - unlink(archive_name.c_str()); - free_pool_memory(results); - return 0; - } -} - -/* Open the next part file. - * - Close the fd - * - Increment part number - * - Reopen the device - */ -int open_next_part(DEVICE *dev) { - int state; - - Dmsg3(29, "open_next_part %s %s %d\n", dev->dev_name, dev->VolCatInfo.VolCatName, dev->openmode); - /* When appending, do not open a new part if the current is empty */ - if (dev->can_append() && (dev->part == dev->num_parts) && - (dev->part_size == 0)) { - Dmsg0(29, "open_next_part exited immediately (dev->part_size == 0).\n"); - return dev->fd; - } - - if (dev->fd >= 0) { - close(dev->fd); - } - - dev->fd = -1; - - state = dev->state; - dev->state &= ~ST_OPENED; - - if (dev->is_dvd() && (dev->part == dev->num_parts) && dev->can_append()) { - if (write_part(dev) < 0) { - return -1; - } - } - - dev->part_start += dev->part_size; - dev->part++; - - if ((dev->num_parts < dev->part) && (dev->state & ST_APPEND)) { - dev->num_parts = dev->part; - - /* Check that the next part file does not exists. - * If it does, move it away... */ - POOL_MEM archive_name(PM_FNAME); - POOL_MEM archive_bkp_name(PM_FNAME); - struct stat buf; - - get_filename(dev, dev->VolCatInfo.VolCatName, archive_name); - - /* Check if the next part exists. */ - if ((stat(archive_name.c_str(), &buf) == 0) || (errno != ENOENT)) { - Dmsg1(29, "open_next_part %s is in the way, moving it away...\n", archive_name.c_str()); - pm_strcpy(archive_bkp_name, archive_name.c_str()); - pm_strcat(archive_bkp_name, ".bak"); - unlink(archive_bkp_name.c_str()); - - /* First try to rename it */ - if (rename(archive_name.c_str(), archive_bkp_name.c_str()) < 0) { - berrno be; - Dmsg3(29, "open_next_part can't rename %s to %s, ERR=%s\n", - archive_name.c_str(), archive_bkp_name.c_str(), be.strerror()); - /* Then try to unlink it */ - if (unlink(archive_name.c_str()) < 0) { - berrno be; - dev->dev_errno = errno; - Mmsg2(&dev->errmsg, _("open_next_part can't unlink existing part %s, ERR=%s\n"), - archive_name.c_str(), be.strerror()); - Emsg0(M_FATAL, 0, dev->errmsg); - return -1; - } - } - } - } - - if (open_dev(dev, dev->VolCatInfo.VolCatName, dev->openmode) < 0) { - return -1; - } else { - dev->state = state; - return dev->fd; - } -} - -/* Open the first part file. - * - Close the fd - * - Reopen the device - */ -int open_first_part(DEVICE *dev) { - int state; - - Dmsg3(29, "open_first_part %s %s %d\n", dev->dev_name, dev->VolCatInfo.VolCatName, dev->openmode); - if (dev->fd >= 0) { - close(dev->fd); - } - - dev->fd = -1; - state = dev->state; - dev->state &= ~ST_OPENED; - - dev->part_start = 0; - dev->part = 0; - - if (open_dev(dev, dev->VolCatInfo.VolCatName, dev->openmode)) { - dev->state = state; - return dev->fd; - } else { - return 0; - } -} - - -/* Protected version of lseek, which opens the right part if necessary */ -off_t lseek_dev(DEVICE *dev, off_t offset, int whence) -{ - int pos, openmode; - - if (dev->num_parts == 0) { /* If there is only one part, simply call lseek. */ - return lseek(dev->fd, offset, whence); - } - - switch(whence) { - case SEEK_SET: - Dmsg1(100, "lseek_dev SEEK_SET called %d\n", offset); - if ((uint64_t)offset >= dev->part_start) { - if ((uint64_t)(offset - dev->part_start) < dev->part_size) { - /* We are staying in the current part, just seek */ - if ((pos = lseek(dev->fd, (off_t)(offset-dev->part_start), SEEK_SET)) < 0) { - return pos; - } else { - return pos + dev->part_start; - } - } else { - /* Load next part, and start again */ - if (open_next_part(dev) < 0) { - Dmsg0(100, "lseek_dev failed while trying to open the next part\n"); - return -1; - } - return lseek_dev(dev, offset, SEEK_SET); - } - } else { - /* pos < dev->part_start : - * We need to access a previous part, - * so just load the first one, and seek again - * until the right one is loaded */ - if (open_first_part(dev) < 0) { - Dmsg0(100, "lseek_dev failed while trying to open the first part\n"); - return -1; - } - return lseek_dev(dev, offset, SEEK_SET); - } - break; - case SEEK_CUR: - Dmsg1(100, "lseek_dev SEEK_CUR called %d\n", offset); - if ((pos = lseek(dev->fd, (off_t)0, SEEK_CUR)) < 0) { - return pos; - } - pos += dev->part_start; - if (offset == 0) { - return pos; - } - else { /* Not used in Bacula, but should work */ - return lseek_dev(dev, pos, SEEK_SET); - } - break; - case SEEK_END: - Dmsg1(100, "lseek_dev SEEK_END called %d\n", offset); - if (offset > 0) { /* Not used by bacula */ - Dmsg1(100, "lseek_dev SEEK_END called with an invalid offset %d\n", offset); - errno = EINVAL; - return -1; - } - - if (dev->part == dev->num_parts) { /* The right part is already loaded */ - if ((pos = lseek(dev->fd, (off_t)0, SEEK_END)) < 0) { - return pos; - } else { - return pos + dev->part_start; - } - } else { - /* Load the first part, then load the next until we reach the last one. - * This is the only way to be sure we compute the right file address. */ - /* Save previous openmode, and open all but last part read-only (useful for DVDs) */ - openmode = dev->openmode; - dev->openmode = OPEN_READ_ONLY; - - /* Works because num_parts > 0. */ - if (open_first_part(dev) < 0) { - Dmsg0(100, "lseek_dev failed while trying to open the first part\n"); - return -1; - } - while (dev->part < (dev->num_parts-1)) { - if (open_next_part(dev) < 0) { - Dmsg0(100, "lseek_dev failed while trying to open the next part\n"); - return -1; - } - } - dev->openmode = openmode; - if (open_next_part(dev) < 0) { - Dmsg0(100, "lseek_dev failed while trying to open the next part\n"); - return -1; - } - return lseek_dev(dev, 0, SEEK_END); - } - break; - default: - errno = EINVAL; - return -1; - } -} - #ifdef debug_tracing #undef rewind_dev bool _rewind_dev(char *file, int line, DEVICE *dev) @@ -1151,12 +559,10 @@ eod_dev(DEVICE *dev) return 0; } #ifdef MTEOM - - if (dev_cap(dev, CAP_MTIOCGET) && dev_cap(dev, CAP_FASTFSF) && - !dev_cap(dev, CAP_EOM)) { - struct mtget mt_stat; + if (dev_cap(dev, CAP_FASTFSF) && !dev_cap(dev, CAP_EOM)) { Dmsg0(100,"Using FAST FSF for EOM\n"); - if (ioctl(dev->fd, MTIOCGET, (char *)&mt_stat) == 0 && mt_stat.mt_fileno <= 0) { + /* If unknown position, rewind */ + if (!dev_get_os_pos(dev, &mt_stat)) { if (!rewind_dev(dev)) { return 0; } @@ -1189,7 +595,7 @@ eod_dev(DEVICE *dev) return 0; } - if (ioctl(dev->fd, MTIOCGET, (char *)&mt_stat) < 0) { + if (!dev_get_os_pos(dev, &mt_stat)) { berrno be; clrerror_dev(dev, -1); Mmsg2(&dev->errmsg, _("ioctl MTIOCGET error on %s. ERR=%s.\n"), @@ -1200,13 +606,13 @@ eod_dev(DEVICE *dev) dev->set_eof(); dev->file = mt_stat.mt_fileno; - /* - * Rewind then use FSF until EOT reached - */ } else { #else { #endif + /* + * Rewind then use FSF until EOT reached + */ if (!rewind_dev(dev)) { return 0; } @@ -1227,8 +633,7 @@ eod_dev(DEVICE *dev) if (file_num == (int)dev->file) { struct mtget mt_stat; Dmsg1(100, "fsf_dev did not advance from file %d\n", file_num); - if (dev_cap(dev, CAP_MTIOCGET) && ioctl(dev->fd, MTIOCGET, (char *)&mt_stat) == 0 && - mt_stat.mt_fileno >= 0) { + if (dev_get_os_pos(dev, &mt_stat)) { Dmsg2(100, "Adjust file from %d to %d\n", dev->file , mt_stat.mt_fileno); dev->set_eof(); dev->file = mt_stat.mt_fileno; @@ -1248,7 +653,7 @@ eod_dev(DEVICE *dev) /* Backup over EOF */ stat = bsf_dev(dev, 1); /* If BSF worked and fileno is known (not -1), set file */ - if (dev_cap(dev, CAP_MTIOCGET) && ioctl(dev->fd, MTIOCGET, (char *)&mt_stat) == 0 && mt_stat.mt_fileno >= 0) { + if (dev_get_os_pos(dev, &mt_stat)) { Dmsg2(100, "BSFATEOF adjust file from %d to %d\n", dev->file , mt_stat.mt_fileno); dev->file = mt_stat.mt_fileno; } else { @@ -1536,7 +941,7 @@ fsf_dev(DEVICE *dev, int num) mt_com.mt_op = MTFSF; mt_com.mt_count = num; stat = ioctl(dev->fd, MTIOCTOP, (char *)&mt_com); - if (stat < 0 || ioctl(dev->fd, MTIOCGET, (char *)&mt_stat) < 0) { + if (stat < 0 || !dev_get_os_pos(dev, &mt_stat)) { berrno be; dev->state |= ST_EOT; Dmsg0(200, "Set ST_EOT\n"); @@ -1726,7 +1131,7 @@ fsr_dev(DEVICE *dev, int num) struct mtget mt_stat; clrerror_dev(dev, MTFSR); Dmsg1(100, "FSF fail: ERR=%s\n", be.strerror()); - if (dev_cap(dev, CAP_MTIOCGET) && ioctl(dev->fd, MTIOCGET, (char *)&mt_stat) == 0 && mt_stat.mt_fileno >= 0) { + if (dev_get_os_pos(dev, &mt_stat)) { Dmsg4(100, "Adjust from %d:%d to %d:%d\n", dev->file, dev->block_num, mt_stat.mt_fileno, mt_stat.mt_blkno); dev->file = mt_stat.mt_fileno; @@ -2047,10 +1452,9 @@ static void do_close(DEVICE *dev) if (dev->fd >= 0) { close(dev->fd); } - if (dev_cap(dev, CAP_REQMOUNT)) { - if (unmount_dev(dev, 1) < 0) { - Dmsg1(0, "Cannot unmount device %s.\n", dev->dev_name); - } + + if (unmount_dev(dev, 1) < 0) { + Dmsg1(0, "Cannot unmount device %s.\n", dev->dev_name); } /* Remove the last part file if it is empty */ @@ -2345,62 +1749,9 @@ void set_os_device_parameters(DEVICE *dev) #endif } -/* - * Edit codes into (Un)MountCommand, Write(First)PartCommand - * %% = % - * %a = archive device name - * %m = mount point - * %v = last part name - * - * omsg = edited output message - * imsg = input string containing edit codes (%x) - * - */ -char *edit_device_codes_dev(DEVICE* dev, char *omsg, const char *imsg) +static bool dev_get_os_pos(DEVICE *dev, struct mtget *mt_stat) { - const char *p; - const char *str; - char add[20]; - - POOL_MEM archive_name(PM_FNAME); - get_filename(dev, dev->VolCatInfo.VolCatName, archive_name); - - *omsg = 0; - Dmsg1(800, "edit_device_codes: %s\n", imsg); - for (p=imsg; *p; p++) { - if (*p == '%') { - switch (*++p) { - case '%': - str = "%"; - break; - case 'n': - bsnprintf(add, sizeof(add), "%d", dev->part); - str = add; - break; - case 'a': - str = dev->dev_name; - break; - case 'm': - str = dev->device->mount_point; - break; - case 'v': - str = archive_name.c_str(); - break; - default: - add[0] = '%'; - add[1] = *p; - add[2] = 0; - str = add; - break; - } - } else { - add[0] = *p; - add[1] = 0; - str = add; - } - Dmsg1(900, "add_str %s\n", str); - pm_strcat(&omsg, (char *)str); - Dmsg1(800, "omsg=%s\n", omsg); - } - return omsg; + return dev_cap(dev, CAP_MTIOCGET) && + ioctl(dev->fd, MTIOCGET, (char *)mt_stat) == 0 && + mt_stat->mt_fileno >= 0; } diff --git a/bacula/src/stored/dvd.c b/bacula/src/stored/dvd.c new file mode 100644 index 0000000000..7d46115f97 --- /dev/null +++ b/bacula/src/stored/dvd.c @@ -0,0 +1,695 @@ +/* + * + * dvd.c -- Routines specific to DVD devices (and + * possibly other removable hard media). + * + * Nicolas Boichat, MMV + * + * Version $Id$ + */ +/* + Copyright (C) 2005 Kern Sibbald + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + */ + +#include "bacula.h" +#include "stored.h" + +int mount_dev(DEVICE *dev, int timeout); +int unmount_dev(DEVICE *dev, int timeout); +void update_free_space_dev(DEVICE *dev); +void get_filename(DEVICE *dev, char *VolName, POOL_MEM& archive_name); + +/* Forward referenced functions */ +static char *edit_device_codes_dev(DEVICE *dev, char *omsg, const char *imsg); +static int do_mount_dev(DEVICE* dev, int mount, int dotimeout); +static int write_part(DEVICE *dev); + + +/* + * Write the current volume/part filename to archive_name. + */ +void get_filename(DEVICE *dev, char *VolName, POOL_MEM& archive_name) +{ + char partnumber[20]; + + if (dev->is_dvd()) { + /* If we try to open the last part, just open it from disk, + * otherwise, open it from the spooling directory */ + if (dev->part < dev->num_parts) { + pm_strcpy(archive_name, dev->device->mount_point); + } else { + /* Use the working directory if spool directory is not defined */ + if (dev->device->spool_directory) { + pm_strcpy(archive_name, dev->device->spool_directory); + } else { + pm_strcpy(archive_name, working_directory); + } + } + } else { + pm_strcpy(archive_name, dev->dev_name); + } + + if (archive_name.c_str()[strlen(archive_name.c_str())-1] != '/') { + pm_strcat(archive_name, "/"); + } + pm_strcat(archive_name, VolName); + /* if part != 0, append .# to the filename (where # is the part number) */ + if (dev->is_dvd() && dev->part != 0) { + pm_strcat(archive_name, "."); + bsnprintf(partnumber, sizeof(partnumber), "%d", dev->part); + pm_strcat(archive_name, partnumber); + } +} + +/* Mount the device. + * If timeout, wait until the mount command returns 0. + * If !timeout, try to mount the device only once. + */ +int mount_dev(DEVICE* dev, int timeout) +{ + if (dev->state & ST_MOUNTED) { + Dmsg0(100, "mount_dev: Device already mounted\n"); + return 0; + } else if (dev_cap(dev, CAP_REQMOUNT)) { + return do_mount_dev(dev, 1, timeout); + } + return 0; +} + +/* Unmount the device + * If timeout, wait until the unmount command returns 0. + * If !timeout, try to unmount the device only once. + */ +int unmount_dev(DEVICE *dev, int timeout) +{ + if (dev->state & ST_MOUNTED) { + return do_mount_dev(dev, 0, timeout); + } + Dmsg0(100, "mount_dev: Device already unmounted\n"); + return 0; +} + +/* (Un)mount the device */ +static int do_mount_dev(DEVICE* dev, int mount, int dotimeout) { + POOL_MEM ocmd(PM_FNAME); + POOLMEM* results; + results = get_pool_memory(PM_MESSAGE); + char* icmd; + int status, timeout; + + if (mount) { + icmd = dev->device->mount_command; + } + else { + icmd = dev->device->unmount_command; + } + + edit_device_codes_dev(dev, ocmd.c_str(), icmd); + + Dmsg2(29, "do_mount_dev: cmd=%s state=%d\n", ocmd.c_str(), dev->state & ST_MOUNTED); + + if (dotimeout) { + /* Try at most 5 times to (un)mount the device. This should perhaps be configurable. */ + timeout = 5; + } + else { + timeout = 0; + } + /* If busy retry each second */ + while ((status = run_program_full_output(ocmd.c_str(), dev->max_open_wait/2, results)) != 0) { + if (--timeout > 0) { + Dmsg2(40, "Device %s cannot be (un)mounted. Retrying... ERR=%s\n", dev->dev_name, results); + /* Sometimes the device cannot be mounted because it is already mounted. + * Try to unmount it, then remount it */ + if (mount) { + Dmsg1(40, "Trying to unmount the device %s...\n", dev->dev_name); + do_mount_dev(dev, 0, 0); + } + bmicrosleep(1, 0); + continue; + } + free_pool_memory(results); + Dmsg2(40, "Device %s cannot be mounted. ERR=%s\n", dev->dev_name, results); + return -1; + } + + if (mount) { + dev->state |= ST_MOUNTED; + } else { + dev->state &= ~ST_MOUNTED; + } + free_pool_memory(results); + + Dmsg1(29, "do_mount_dev: end_state=%d\n", dev->state & ST_MOUNTED); + return 0; +} + +/* Only for devices that require a mount. + * Try to find the volume name of the loaded device, and open the + * first part of this volume. + * + * Returns 0 if read_dev_volume_label can now read the label, + * -1 if an error occured, and read_dev_volume_label_guess must abort with an IO_ERROR. + * + * To guess the device name, it lists all the files on the DVD, and searches for a + * file which has a minimum size (500 bytes). If this file has a numeric extension, + * like part files, try to open the file which has no extension (e.g. the first + * part file). + * So, if the DVD does not contains a Bacula volume, a random file is opened, + * and no valid label could be read from this file. + * + * It is useful, so the operator can be told that a wrong volume is mounted, with + * the label name of the current volume. We can also check that the currently + * mounted disk is writable. (See also read_dev_volume_label_guess in label.c). + * + * Note that if the right volume is mounted, open_guess_name_dev returns the same + * result as an usual open_dev. + */ +int open_guess_name_dev(DEVICE *dev) +{ + Dmsg1(29, "open_guess_name_dev: dev=%s\n", dev->dev_name); + POOL_MEM guessedname(PM_FNAME); + DIR* dp; + struct dirent *entry, *result; + struct stat statp; + int index; + int name_max; + + if (!dev->is_dvd()) { + Dmsg1(100, "open_guess_name_dev: device does not require mount, returning 0. dev=%s\n", dev->dev_name); + return 0; + } + +#ifndef HAVE_DIRENT_H + Dmsg0(29, "open_guess_name_dev: readdir not available, cannot guess volume name\n"); + return 0; +#endif + + update_free_space_dev(dev); + + if (mount_dev(dev, 1) < 0) { + /* If the device cannot be mounted, check if it is writable */ + if (dev->free_space_errno >= 0) { + Dmsg1(100, "open_guess_name_dev: device cannot be mounted, but it seems to be writable, returning 0. dev=%s\n", dev->dev_name); + return 0; + } else { + Dmsg1(100, "open_guess_name_dev: device cannot be mounted, and is not writable, returning 0. dev=%s\n", dev->dev_name); + /* read_dev_volume_label_guess must now check dev->free_space_errno to understand that the media is not writable. */ + return 0; + } + } + + name_max = pathconf(".", _PC_NAME_MAX); + if (name_max < 1024) { + name_max = 1024; + } + + if (!(dp = opendir(dev->device->mount_point))) { + berrno be; + dev->dev_errno = errno; + Dmsg3(29, "open_guess_name_dev: failed to open dir %s (dev=%s), ERR=%s\n", dev->device->mount_point, dev->dev_name, be.strerror()); + return -1; + } + + entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 100); + while (1) { + if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) { + dev->dev_errno = ENOENT; + Dmsg2(29, "open_guess_name_dev: failed to find suitable file in dir %s (dev=%s)\n", dev->device->mount_point, dev->dev_name); + closedir(dp); + return -1; + } + + ASSERT(name_max+1 > (int)sizeof(struct dirent) + (int)NAMELEN(entry)); + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + pm_strcpy(guessedname, dev->device->mount_point); + if (guessedname.c_str()[strlen(guessedname.c_str())-1] != '/') { + pm_strcat(guessedname, "/"); + } + pm_strcat(guessedname, entry->d_name); + + if (stat(guessedname.c_str(), &statp) < 0) { + berrno be; + Dmsg3(29, "open_guess_name_dev: failed to stat %s (dev=%s), ERR=%s\n", + guessedname.c_str(), dev->dev_name, be.strerror()); + continue; + } + + if (!S_ISREG(statp.st_mode) || (statp.st_size < 500)) { + Dmsg2(100, "open_guess_name_dev: %s is not a regular file, or less than 500 bytes (dev=%s)\n", + guessedname.c_str(), dev->dev_name); + continue; + } + + /* Ok, we found a good file, remove the part extension if possible. */ + for (index = strlen(guessedname.c_str())-1; index >= 0; index--) { + if ((guessedname.c_str()[index] == '/') || + (guessedname.c_str()[index] < '0') || + (guessedname.c_str()[index] > '9')) { + break; + } + if (guessedname.c_str()[index] == '.') { + guessedname.c_str()[index] = '\0'; + break; + } + } + + if ((stat(guessedname.c_str(), &statp) < 0) || (statp.st_size < 500)) { + /* The file with extension truncated does not exists or is too small, so use it with its extension. */ + berrno be; + Dmsg3(100, "open_guess_name_dev: failed to stat %s (dev=%s), using the file with its extension, ERR=%s\n", + guessedname.c_str(), dev->dev_name, be.strerror()); + pm_strcpy(guessedname, dev->device->mount_point); + if (guessedname.c_str()[strlen(guessedname.c_str())-1] != '/') { + pm_strcat(guessedname, "/"); + } + pm_strcat(guessedname, entry->d_name); + continue; + } + break; + } + + closedir(dp); + + if (dev->fd >= 0) { + close(dev->fd); + } + + if ((dev->fd = open(guessedname.c_str(), O_RDONLY | O_BINARY)) < 0) { + berrno be; + dev->dev_errno = errno; + Dmsg3(29, "open_guess_name_dev: failed to open %s (dev=%s), ERR=%s\n", + guessedname.c_str(), dev->dev_name, be.strerror()); + if (open_first_part(dev) < 0) { + berrno be; + dev->dev_errno = errno; + Mmsg1(&dev->errmsg, _("Could not open_first_part, ERR=%s\n"), be.strerror()); + Emsg0(M_FATAL, 0, dev->errmsg); + } + return -1; + } + dev->part_start = 0; + dev->part_size = statp.st_size; + dev->part = 0; + dev->state |= ST_OPENED; + dev->use_count = 1; + + Dmsg2(29, "open_guess_name_dev: %s opened (dev=%s)\n", guessedname.c_str(), dev->dev_name); + + return 0; +} + + +/* Update the free space on the device */ +void update_free_space_dev(DEVICE* dev) +{ + POOL_MEM ocmd(PM_FNAME); + POOLMEM* results; + char* icmd; + int timeout; + long long int free; + + icmd = dev->device->free_space_command; + + if (!icmd) { + dev->free_space = 0; + dev->free_space_errno = 0; + Dmsg2(29, "update_free_space_dev: free_space=%d, free_space_errno=%d (!icmd)\n", dev->free_space, dev->free_space_errno); + return; + } + + edit_device_codes_dev(dev, ocmd.c_str(), icmd); + + Dmsg1(29, "update_free_space_dev: cmd=%s\n", ocmd.c_str()); + + results = get_pool_memory(PM_MESSAGE); + + /* Try at most 3 times to get the free space on the device. This should perhaps be configurable. */ + timeout = 3; + + while (1) { + char ed1[50]; + if (run_program_full_output(ocmd.c_str(), dev->max_open_wait/2, results) == 0) { + Dmsg1(100, "Free space program run : %s\n", results); + free = str_to_int64(results); + if (free >= 0) { + dev->free_space = free; + dev->free_space_errno = 1; + Mmsg0(dev->errmsg, ""); + break; + } + } + dev->free_space = 0; + dev->free_space_errno = -EPIPE; + Mmsg1(dev->errmsg, "Cannot run free space command (%s)\n", results); + + if (--timeout > 0) { + Dmsg4(40, "Cannot get free space on device %s. free_space=%s, " + "free_space_errno=%d ERR=%s\n", dev->dev_name, + edit_uint64(dev->free_space, ed1), dev->free_space_errno, + dev->errmsg); + bmicrosleep(1, 0); + continue; + } + + dev->dev_errno = -dev->free_space_errno; + Dmsg4(40, "Cannot get free space on device %s. free_space=%s, " + "free_space_errno=%d ERR=%s\n", + dev->dev_name, edit_uint64(dev->free_space, ed1), + dev->free_space_errno, dev->errmsg); + break; + } + + free_pool_memory(results); + Dmsg2(29, "update_free_space_dev: free_space=%lld, free_space_errno=%d\n", dev->free_space, dev->free_space_errno); + return; +} + +static int write_part(DEVICE *dev) +{ + Dmsg1(29, "write_part: device is %s\n", dev->dev_name); + + if (unmount_dev(dev, 1) < 0) { + Dmsg0(29, "write_part: unable to unmount the device\n"); + } + + POOL_MEM ocmd(PM_FNAME); + POOLMEM *results; + results = get_pool_memory(PM_MESSAGE); + char* icmd; + int status; + int timeout; + + icmd = dev->device->write_part_command; + + edit_device_codes_dev(dev, ocmd.c_str(), icmd); + + /* Wait at most the time a maximum size part is written in DVD 0.5x speed + * FIXME: Minimum speed should be in device configuration + */ + timeout = dev->max_open_wait + (dev->max_part_size/(1350*1024/2)); + + Dmsg2(29, "write_part: cmd=%s timeout=%d\n", ocmd.c_str(), timeout); + + status = run_program_full_output(ocmd.c_str(), timeout, results); + if (status != 0) { + Mmsg1(dev->errmsg, "Error while writing current part to the DVD: %s", results); + dev->dev_errno = EIO; + free_pool_memory(results); + return -1; + } + else { + Dmsg1(29, "write_part: command output=%s\n", results); + POOL_MEM archive_name(PM_FNAME); + get_filename(dev, dev->VolCatInfo.VolCatName, archive_name); + unlink(archive_name.c_str()); + free_pool_memory(results); + return 0; + } +} + +/* Open the next part file. + * - Close the fd + * - Increment part number + * - Reopen the device + */ +int open_next_part(DEVICE *dev) { + int state; + + Dmsg3(29, "open_next_part %s %s %d\n", dev->dev_name, dev->VolCatInfo.VolCatName, dev->openmode); + /* When appending, do not open a new part if the current is empty */ + if (dev->can_append() && (dev->part == dev->num_parts) && + (dev->part_size == 0)) { + Dmsg0(29, "open_next_part exited immediately (dev->part_size == 0).\n"); + return dev->fd; + } + + if (dev->fd >= 0) { + close(dev->fd); + } + + dev->fd = -1; + + state = dev->state; + dev->state &= ~ST_OPENED; + + if (dev->is_dvd() && (dev->part == dev->num_parts) && dev->can_append()) { + if (write_part(dev) < 0) { + return -1; + } + } + + dev->part_start += dev->part_size; + dev->part++; + + if ((dev->num_parts < dev->part) && (dev->state & ST_APPEND)) { + dev->num_parts = dev->part; + + /* Check that the next part file does not exists. + * If it does, move it away... */ + POOL_MEM archive_name(PM_FNAME); + POOL_MEM archive_bkp_name(PM_FNAME); + struct stat buf; + + get_filename(dev, dev->VolCatInfo.VolCatName, archive_name); + + /* Check if the next part exists. */ + if ((stat(archive_name.c_str(), &buf) == 0) || (errno != ENOENT)) { + Dmsg1(29, "open_next_part %s is in the way, moving it away...\n", archive_name.c_str()); + pm_strcpy(archive_bkp_name, archive_name.c_str()); + pm_strcat(archive_bkp_name, ".bak"); + unlink(archive_bkp_name.c_str()); + + /* First try to rename it */ + if (rename(archive_name.c_str(), archive_bkp_name.c_str()) < 0) { + berrno be; + Dmsg3(29, "open_next_part can't rename %s to %s, ERR=%s\n", + archive_name.c_str(), archive_bkp_name.c_str(), be.strerror()); + /* Then try to unlink it */ + if (unlink(archive_name.c_str()) < 0) { + berrno be; + dev->dev_errno = errno; + Mmsg2(&dev->errmsg, _("open_next_part can't unlink existing part %s, ERR=%s\n"), + archive_name.c_str(), be.strerror()); + Emsg0(M_FATAL, 0, dev->errmsg); + return -1; + } + } + } + } + + if (open_dev(dev, dev->VolCatInfo.VolCatName, dev->openmode) < 0) { + return -1; + } else { + dev->state = state; + return dev->fd; + } +} + +/* Open the first part file. + * - Close the fd + * - Reopen the device + */ +int open_first_part(DEVICE *dev) { + int state; + + Dmsg3(29, "open_first_part %s %s %d\n", dev->dev_name, dev->VolCatInfo.VolCatName, dev->openmode); + if (dev->fd >= 0) { + close(dev->fd); + } + + dev->fd = -1; + state = dev->state; + dev->state &= ~ST_OPENED; + + dev->part_start = 0; + dev->part = 0; + + if (open_dev(dev, dev->VolCatInfo.VolCatName, dev->openmode)) { + dev->state = state; + return dev->fd; + } else { + return 0; + } +} + + +/* Protected version of lseek, which opens the right part if necessary */ +off_t lseek_dev(DEVICE *dev, off_t offset, int whence) +{ + int pos, openmode; + + if (dev->num_parts == 0) { /* If there is only one part, simply call lseek. */ + return lseek(dev->fd, offset, whence); + } + + switch(whence) { + case SEEK_SET: + Dmsg1(100, "lseek_dev SEEK_SET called %d\n", offset); + if ((uint64_t)offset >= dev->part_start) { + if ((uint64_t)(offset - dev->part_start) < dev->part_size) { + /* We are staying in the current part, just seek */ + if ((pos = lseek(dev->fd, (off_t)(offset-dev->part_start), SEEK_SET)) < 0) { + return pos; + } else { + return pos + dev->part_start; + } + } else { + /* Load next part, and start again */ + if (open_next_part(dev) < 0) { + Dmsg0(100, "lseek_dev failed while trying to open the next part\n"); + return -1; + } + return lseek_dev(dev, offset, SEEK_SET); + } + } else { + /* pos < dev->part_start : + * We need to access a previous part, + * so just load the first one, and seek again + * until the right one is loaded */ + if (open_first_part(dev) < 0) { + Dmsg0(100, "lseek_dev failed while trying to open the first part\n"); + return -1; + } + return lseek_dev(dev, offset, SEEK_SET); + } + break; + case SEEK_CUR: + Dmsg1(100, "lseek_dev SEEK_CUR called %d\n", offset); + if ((pos = lseek(dev->fd, (off_t)0, SEEK_CUR)) < 0) { + return pos; + } + pos += dev->part_start; + if (offset == 0) { + return pos; + } + else { /* Not used in Bacula, but should work */ + return lseek_dev(dev, pos, SEEK_SET); + } + break; + case SEEK_END: + Dmsg1(100, "lseek_dev SEEK_END called %d\n", offset); + if (offset > 0) { /* Not used by bacula */ + Dmsg1(100, "lseek_dev SEEK_END called with an invalid offset %d\n", offset); + errno = EINVAL; + return -1; + } + + if (dev->part == dev->num_parts) { /* The right part is already loaded */ + if ((pos = lseek(dev->fd, (off_t)0, SEEK_END)) < 0) { + return pos; + } else { + return pos + dev->part_start; + } + } else { + /* Load the first part, then load the next until we reach the last one. + * This is the only way to be sure we compute the right file address. */ + /* Save previous openmode, and open all but last part read-only (useful for DVDs) */ + openmode = dev->openmode; + dev->openmode = OPEN_READ_ONLY; + + /* Works because num_parts > 0. */ + if (open_first_part(dev) < 0) { + Dmsg0(100, "lseek_dev failed while trying to open the first part\n"); + return -1; + } + while (dev->part < (dev->num_parts-1)) { + if (open_next_part(dev) < 0) { + Dmsg0(100, "lseek_dev failed while trying to open the next part\n"); + return -1; + } + } + dev->openmode = openmode; + if (open_next_part(dev) < 0) { + Dmsg0(100, "lseek_dev failed while trying to open the next part\n"); + return -1; + } + return lseek_dev(dev, 0, SEEK_END); + } + break; + default: + errno = EINVAL; + return -1; + } +} + + +/* + * Edit codes into (Un)MountCommand, Write(First)PartCommand + * %% = % + * %a = archive device name + * %m = mount point + * %v = last part name + * + * omsg = edited output message + * imsg = input string containing edit codes (%x) + * + */ +static char *edit_device_codes_dev(DEVICE* dev, char *omsg, const char *imsg) +{ + const char *p; + const char *str; + char add[20]; + + POOL_MEM archive_name(PM_FNAME); + get_filename(dev, dev->VolCatInfo.VolCatName, archive_name); + + *omsg = 0; + Dmsg1(800, "edit_device_codes: %s\n", imsg); + for (p=imsg; *p; p++) { + if (*p == '%') { + switch (*++p) { + case '%': + str = "%"; + break; + case 'n': + bsnprintf(add, sizeof(add), "%d", dev->part); + str = add; + break; + case 'a': + str = dev->dev_name; + break; + case 'm': + str = dev->device->mount_point; + break; + case 'v': + str = archive_name.c_str(); + break; + default: + add[0] = '%'; + add[1] = *p; + add[2] = 0; + str = add; + break; + } + } else { + add[0] = *p; + add[1] = 0; + str = add; + } + Dmsg1(900, "add_str %s\n", str); + pm_strcat(&omsg, (char *)str); + Dmsg1(800, "omsg=%s\n", omsg); + } + return omsg; +} diff --git a/bacula/src/version.h b/bacula/src/version.h index 7246bc8f76..aa58181edd 100644 --- a/bacula/src/version.h +++ b/bacula/src/version.h @@ -21,6 +21,7 @@ /* #define TRACE_JCR_CHAIN 1 */ /* #define TRACE_RES 1 */ /* #define DEBUG_MEMSET 1 */ +/* #define DEBUG_MUTEX 1 */ /* Check if header of tape block is zero before writing */ #define DEBUG_BLOCK_ZEROING 1 @@ -33,7 +34,7 @@ /* #define SEND_DMSG_TO_FILE 1 */ +/* The following are turned on for performance testing */ /* #define NO_ATTRIBUTES_TEST 1 */ /* #define NO_TAPE_WRITE_TEST 1 */ /* #define FD_NO_SEND TEST 1 */ -/* #define DEBUG_MUTEX 1 */ -- 2.39.5