* Author: AMANDA core development group.
*/
/*
- * $Id: file.c,v 1.14.4.6.4.2.2.5.2.1 2005/09/20 21:31:52 jrjackson Exp $
+ * $Id: file.c,v 1.40 2006/07/19 17:41:15 martinea Exp $
*
* file and directory bashing routines
*/
#include "amanda.h"
#include "util.h"
+#include "timestamp.h"
+#include "arglist.h"
+#include "file.h"
-uid_t client_uid = (uid_t) -1;
-gid_t client_gid = (gid_t) -1;
-
-/* Make a directory (internal function).
-** If the directory already exists then we pretend we created it.
-** XXX - I'm not sure about the use of the chown() stuff. On most systems
-** it will do nothing - only root is permitted to change the owner
-** of a file.
-*/
-int mk1dir(dir, mode, uid, gid)
-char *dir; /* directory to create */
-int mode; /* mode for new directory */
-uid_t uid; /* uid for new directory */
-gid_t gid; /* gid for new directory */
-{
- int rc; /* return code */
-
- rc = 0; /* assume the best */
-
- if(mkdir(dir, mode) == 0) {
- chmod(dir, mode); /* mkdir() is affected by the umask */
- chown(dir, uid, gid); /* XXX - no-op on most systems? */
- } else { /* maybe someone beat us to it */
- int serrno;
-
- serrno = errno;
- if(access(dir, F_OK) != 0) rc = -1;
- errno = serrno; /* pass back the real error */
- }
-
- return rc;
-}
-
+static void areads_getbuf(const char *s, int l, int fd);
+static char *original_cwd = NULL;
/*
* Make a directory hierarchy given an entry to be created (by the caller)
* the last element, but not the last element. So a (potential) file name
* may be passed to mkpdir and all the parents of that file will be created.
*/
-int mkpdir(file, mode, uid, gid)
-char *file; /* file to create parent directories for */
-int mode; /* mode for new directories */
-uid_t uid; /* uid for new directories */
-gid_t gid; /* gid for new directories */
+int
+mkpdir(
+ char * file, /* file to create parent directories for */
+ mode_t mode, /* mode for new directories */
+ uid_t uid, /* uid for new directories */
+ gid_t gid) /* gid for new directories */
{
- char *dir = NULL, *p;
+ char *dir;
+ char *p;
int rc; /* return code */
rc = 0;
- dir = stralloc(file); /* make a copy we can play with */
-
+ /* Remove last member of file, put the result in dir */
+ dir = stralloc(file); /* make a copy we can play with */
p = strrchr(dir, '/');
- if(p != dir && p != NULL) { /* got a '/' or a simple name */
- *p = '\0';
+ *p = '\0';
+
+ rc = mkdir(dir, mode);
+ if (rc != 0) {
+ if (errno == ENOENT) { /* create parent directory */
+ rc = mkpdir(dir, mode, uid, gid);
+ if (rc != 0)
+ return rc;
+ rc = mkdir(dir, mode);
+ }
+ if (rc != 0 && errno == EEXIST) {
+ amfree(dir);
+ return 0;
+ }
+ }
+
+ /* mkdir succeded, set permission and ownership */
+ if (rc == 0) {
+ /* mkdir is affected by umask, so set the mode bits manually */
+ rc = chmod(dir, mode);
- if(access(dir, F_OK) != 0) { /* doesn't exist */
- if(mkpdir(dir, mode, uid, gid) != 0 ||
- mk1dir(dir, mode, uid, gid) != 0) rc = -1; /* create failed */
+ if (rc == 0 && geteuid() == 0) {
+ rc = chown(dir, uid, gid);
}
}
** - stops deleting before topdir, ie: topdir will not be removed
** - if file is not under topdir this routine will not notice
*/
-int rmpdir(file, topdir)
-char *file; /* directory hierarchy to remove */
-char *topdir; /* where to stop removing */
+int
+rmpdir(
+ char * file, /* directory hierarchy to remove */
+ char * topdir) /* where to stop removing */
{
int rc;
- char *p, *dir = NULL;
+ char *p, *dir;
if(strcmp(file, topdir) == 0) return 0; /* all done */
dir = stralloc(file);
p = strrchr(dir, '/');
- if(p == dir) rc = 0; /* no /'s */
- else {
+ if (p == NULL || p == dir) {
+ rc = 0;
+ } else {
*p = '\0';
-
rc = rmpdir(dir, topdir);
}
}
-/*
- *=====================================================================
- * Do Amanda setup for all programs.
- *
- * void amanda_setup (int argc, char **argv, int setup_flags)
- *
- * entry: setup_flags (see AMANDA_SETUP_FLAG_xxx)
- * exit: none
- *=====================================================================
- */
-
-void
-amanda_setup (argc, argv, setup_flags)
- int argc;
- char **argv;
- int setup_flags;
-{
-}
-
/*
*=====================================================================
* Change directory to a "safe" location and set some base environment.
*
* void safe_cd (void)
*
- * entry: client_uid and client_gid set to CLIENT_LOGIN information
- * exit: none
- *
* Set a default umask of 0077.
*
* Create the Amada debug directory (if defined) and the Amanda temp
*/
void
-safe_cd()
+safe_cd(void)
{
int cd_ok = 0;
struct stat sbuf;
- struct passwd *pwent;
char *d;
-
- if(client_uid == (uid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) {
- client_uid = pwent->pw_uid;
- client_gid = pwent->pw_gid;
- endpwent();
- }
+ uid_t client_uid = get_client_uid();
+ gid_t client_gid = get_client_gid();
(void) umask(0077);
+ /* stash away the current directory for later reference */
+ if (original_cwd == NULL) {
+ original_cwd = g_get_current_dir();
+ }
+
if (client_uid != (uid_t) -1) {
#if defined(AMANDA_DBGDIR)
d = stralloc2(AMANDA_DBGDIR, "/.");
- (void) mkpdir(d, 02700, client_uid, client_gid);
+ (void) mkpdir(d, (mode_t)0700, client_uid, client_gid);
amfree(d);
#endif
d = stralloc2(AMANDA_TMPDIR, "/.");
- (void) mkpdir(d, 02700, client_uid, client_gid);
+ (void) mkpdir(d, (mode_t)0700, client_uid, client_gid);
amfree(d);
}
if(cd_ok) {
save_core(); /* save any old core file */
} else {
- (void) chdir("/"); /* assume this works */
+ if ((cd_ok = chdir("/")) == -1) {
+ (void)cd_ok; /* Quiet compiler warning if DEBUG disabled */
+ }
}
}
*/
void
-safe_fd(fd_start, fd_count)
- int fd_start;
- int fd_count;
+safe_fd(
+ int fd_start,
+ int fd_count)
{
int fd;
- for(fd = 0; fd < FD_SETSIZE; fd++) {
+ for(fd = 0; fd < (int)FD_SETSIZE; fd++) {
if (fd < 3) {
/*
- * Open three file descriptors. If stdin, stdout and stderr
- * are normal, i.e. open, we will open descriptor numbers
- * 3 or higher which the rest of this loop will turn right
- * around and close. If one of the standard descriptors
- * is not open it will end up pointing to /dev/null and the
- * rest of the loop will leave it alone.
+ * Open three file descriptors. If one of the standard
+ * descriptors is not open it will be pointed to /dev/null...
*
* This avoids, for instance, someone running us with stderr
* closed so that when we open some other file, messages
* sent to stderr do not accidentally get written to the
* wrong file.
*/
- (void) open("/dev/null", O_RDWR);
+ if (fcntl(fd, F_GETFD) == -1) {
+ if (open("/dev/null", O_RDWR) == -1) {
+ g_fprintf(stderr, _("/dev/null is inaccessable: %s\n"),
+ strerror(errno));
+ exit(1);
+ }
+ }
} else {
/*
* Make sure nobody spoofs us with a lot of extra open files
}
}
}
-
}
/*
*/
void
-save_core()
+save_core(void)
{
struct stat sbuf;
char suffix[2];
char *old, *new;
- ts = construct_datestamp((time_t *)&sbuf.st_mtime);
+ ts = get_datestamp_from_time(sbuf.st_mtime);
suffix[0] = 'z';
suffix[1] = '\0';
old = vstralloc("core", ts, suffix, NULL);
/*
** Sanitise a file name.
**
-** Convert all funny characters to '_' so that we can use,
+** Convert all '/', ':', and '\' characters to '_' so that we can use,
** for example, disk names as part of file names.
** Notes:
** - there is a many-to-one mapping between input and output
-** XXX - We only look for '/' and ' ' at the moment. May
-** XXX - be we should also do all unprintables.
+** - Only / and '\0' are disallowed in filenames by POSIX, but Windows
+** disallows ':' and '\' as well. Furthermore, we use ':' as a
+** delimiter at other points in Amanda.
*/
-char *sanitise_filename(inp)
-char *inp;
+char *
+sanitise_filename(
+ char * inp)
{
char *buf;
- int buf_size;
+ size_t buf_size;
char *s, *d;
int ch;
- buf_size = 2 * strlen(inp) + 1; /* worst case */
+ buf_size = strlen(inp) + 1; /* worst case */
buf = alloc(buf_size);
d = buf;
s = inp;
while((ch = *s++) != '\0') {
- if(ch == '_') {
- if(d >= buf + buf_size) {
- return NULL; /* cannot happen */
- }
- *d++ = '_'; /* convert _ to __ to try */
- /* and ensure unique output */
- } else if(ch == '/' || isspace(ch)) {
+ if((ch == '/') || (ch == ':') || (ch == '\\')) {
ch = '_'; /* convert "bad" to "_" */
}
- if(d >= buf + buf_size) {
- return NULL; /* cannot happen */
- }
- *d++ = ch;
+ *d++ = (char)ch;
}
- if(d >= buf + buf_size) {
- return NULL; /* cannot happen */
+ assert(d < buf + buf_size);
+ *d = '\0';
+
+ return buf;
+}
+
+/* duplicate '_' */
+char *
+old_sanitise_filename(
+ char * inp)
+{
+ char *buf;
+ size_t buf_size;
+ char *s, *d;
+ int ch;
+
+ buf_size = 2*strlen(inp) + 1; /* worst case */
+ buf = alloc(buf_size);
+ d = buf;
+ s = inp;
+ while((ch = *s++) != '\0') {
+ if(ch == '_') {
+ *d++ = (char)ch;
+ }
+ if(ch == '/') {
+ ch = '_'; /* convert "bad" to "_" */
+ }
+ *d++ = (char)ch;
}
+ assert(d < buf + buf_size);
*d = '\0';
return buf;
}
+void
+canonicalize_pathname(char *pathname, char *result_buf)
+{
+#ifdef __CYGWIN__
+ cygwin_conv_to_full_posix_path(pathname, result_buf);
+#else
+ strncpy(result_buf, pathname, PATH_MAX-1);
+ result_buf[PATH_MAX-1] = '\0';
+#endif
+}
+
/*
*=====================================================================
* Get the next line of input from a stdio file.
*
- * char *agets (FILE *f)
+ * char *agets (FILE *stream)
*
- * entry: f = stdio stream to read
- * exit: returns a pointer to an alloc'd string or NULL at EOF
- * or error (errno will be zero on EOF).
+ * entry: stream - stream to read
+ * exit: returns a pointer to an alloc'd string or NULL
+ * at EOF or error. The functions ferror(stream) and
+ * feof(stream) should be checked by caller to determine
+ * stream status.
*
- * Notes: the newline, if read, is removed from the string
+ * Notes: the newline at the end of a line, if read, is removed from
+ * the string. Quoted newlines are left intact.
* the caller is responsible for free'ing the string
+ *
*=====================================================================
*/
+#define AGETS_LINE_INCR 128
+
char *
-debug_agets(s, l, file)
- char *s;
- int l;
- FILE *file;
+debug_agets(
+ const char *sourcefile,
+ int lineno,
+ FILE * stream)
{
- char *line = NULL, *line_ptr;
- size_t line_size, size_save;
- int line_free, line_len;
- char *cp;
- char *f;
-
- malloc_enter(dbmalloc_caller_loc(s, l));
+ int ch;
+ char *line = alloc(AGETS_LINE_INCR);
+ size_t line_size = 0;
+ size_t loffset = 0;
+ int inquote = 0;
+ int escape = 0;
+
+ (void)sourcefile; /* Quiet unused parameter warning if not debugging */
+ (void)lineno; /* Quiet unused parameter warning if not debugging */
+
+ while ((ch = fgetc(stream)) != EOF) {
+ if (ch == '\n') {
+ if (!inquote) {
+ if (escape) {
+ escape = 0;
+ loffset--; /* Consume escape in buffer */
+ continue;
+ }
+ /* Reached end of line so exit without passing on LF */
+ break;
+ }
+ }
-#define AGETS_LINE_INCR 128
+ if (ch == '\\') {
+ escape = !escape;
+ } else {
+ if (ch == '"') {
+ if (!escape)
+ inquote = !inquote;
+ }
+ escape = 0;
+ }
- line_size = AGETS_LINE_INCR;
- line = debug_alloc (s, l, line_size);
- line_free = line_size;
- line_ptr = line;
- line_len = 0;
+ if ((loffset + 1) >= line_size) {
+ char *tmpline;
- while ((f = fgets(line_ptr, line_free, file)) != NULL) {
- /*
- * Note that we only have to search what we just read, not
- * the whole buffer.
- */
- if ((cp = strchr (line_ptr, '\n')) != NULL) {
- line_len += cp - line_ptr;
- *cp = '\0'; /* zap the newline */
- break; /* got to end of line */
- }
- line_len += line_free - 1; /* bytes read minus '\0' */
- size_save = line_size;
- if (line_size < 256 * AGETS_LINE_INCR) {
- line_size *= 2;
- } else {
- line_size += 256 * AGETS_LINE_INCR;
+ /*
+ * Reallocate input line.
+ * alloc() never return NULL pointer.
+ */
+ tmpline = alloc(line_size + AGETS_LINE_INCR);
+ memcpy(tmpline, line, line_size);
+ amfree(line);
+ line = tmpline;
+ line_size = line_size + AGETS_LINE_INCR;
}
- cp = debug_alloc (s, l, line_size); /* get more space */
- memcpy (cp, line, size_save); /* copy old to new */
- free (line); /* and release the old */
- line = cp;
- line_ptr = line + size_save - 1; /* start at the null byte */
- line_free = line_size - line_len; /* and we get to use it */
+ line[loffset++] = (char)ch;
}
+
+ if ((ch == EOF) && (loffset == 0)) {
+ amfree(line); /* amfree zeros line... */
+ } else {
+ line[loffset] = '\0';
+ }
+
/*
- * Return what we got even if there was not a newline. Only
- * report done (NULL) when no data was processed.
+ * Return what we got even if there was not a newline.
+ * Only report done (NULL) when no data was processed.
*/
- if (f == NULL && line_len == 0) {
- amfree (line);
- line = NULL; /* redundant, but clear */
- if(!ferror(file)) {
- errno = 0; /* flag EOF vs error */
- }
- }
- malloc_leave(dbmalloc_caller_loc(s, l));
return line;
}
+
/*
*=====================================================================
* Find/create a buffer for a particular file descriptor for use with
* areads().
*
- * void areads_getbuf (char *file, int line, int fd)
+ * void areads_getbuf (const char *file, size_t line, int fd)
*
* entry: file, line = caller source location
* fd = file descriptor to look up
static struct areads_buffer {
char *buffer;
char *endptr;
- ssize_t bufsize;
+ size_t bufsize;
} *areads_buffer = NULL;
static int areads_bufcount = 0;
-static ssize_t areads_bufsize = BUFSIZ; /* for the test program */
+static size_t areads_bufsize = BUFSIZ; /* for the test program */
static void
-areads_getbuf(s, l, fd)
- char *s;
- int l;
- int fd;
+areads_getbuf(
+ const char *s,
+ int l,
+ int fd)
{
struct areads_buffer *new;
- ssize_t size;
+ size_t size;
assert(fd >= 0);
if(fd >= areads_bufcount) {
- size = (fd + 1) * sizeof(*areads_buffer);
+ size = (size_t)(fd + 1) * SIZEOF(*areads_buffer);
new = (struct areads_buffer *) debug_alloc(s, l, size);
memset((char *)new, 0, size);
if(areads_buffer) {
- size = areads_bufcount * sizeof(*areads_buffer);
+ size = areads_bufcount * SIZEOF(*areads_buffer);
memcpy(new, areads_buffer, size);
}
amfree(areads_buffer);
*/
ssize_t
-areads_dataready(fd)
- int fd;
+areads_dataready(
+ int fd)
{
ssize_t r = 0;
*/
void
-areads_relbuf(fd)
- int fd;
+areads_relbuf(
+ int fd)
{
if(fd >= 0 && fd < areads_bufcount) {
amfree(areads_buffer[fd].buffer);
*/
char *
-debug_areads (s, l, fd)
- char *s;
- int l;
- int fd;
+debug_areads (
+ const char *s,
+ int l,
+ int fd)
{
char *nl;
char *line;
char *buffer;
char *endptr;
char *newbuf;
- ssize_t buflen;
- ssize_t size;
+ size_t buflen;
+ size_t size;
ssize_t r;
- malloc_enter(dbmalloc_caller_loc(s, l));
-
if(fd < 0) {
errno = EBADF;
return NULL;
areads_getbuf(s, l, fd);
buffer = areads_buffer[fd].buffer;
endptr = areads_buffer[fd].endptr;
- buflen = areads_buffer[fd].bufsize - (endptr - buffer);
+ buflen = areads_buffer[fd].bufsize - (size_t)(endptr - buffer);
while((nl = strchr(buffer, '\n')) == NULL) {
/*
* No newline yet, so get more data.
newbuf = debug_alloc(s, l, size + 1);
memcpy (newbuf, buffer, areads_buffer[fd].bufsize + 1);
amfree(areads_buffer[fd].buffer);
- buffer = NULL;
areads_buffer[fd].buffer = newbuf;
areads_buffer[fd].endptr = newbuf + areads_buffer[fd].bufsize;
areads_buffer[fd].bufsize = size;
buffer = areads_buffer[fd].buffer;
endptr = areads_buffer[fd].endptr;
- buflen = areads_buffer[fd].bufsize - (endptr - buffer);
+ buflen = areads_buffer[fd].bufsize - (size_t)(endptr - buffer);
}
if ((r = read(fd, endptr, buflen)) <= 0) {
if(r == 0) {
errno = 0; /* flag EOF instead of error */
}
- malloc_leave(dbmalloc_caller_loc(s, l));
return NULL;
}
endptr[r] = '\0'; /* we always leave room for this */
}
*nl++ = '\0';
line = stralloc(buffer);
- size = endptr - nl; /* data still left in buffer */
+ size = (size_t)(endptr - nl); /* data still left in buffer */
memmove(buffer, nl, size);
areads_buffer[fd].endptr = buffer + size;
areads_buffer[fd].endptr[0] = '\0';
- malloc_leave(dbmalloc_caller_loc(s, l));
return line;
}
+int robust_open(const char * pathname, int flags, mode_t mode) {
+ int result = -1;
+ int e_busy_count = 0;
+
+ for (;;) {
+ if (flags & O_CREAT) {
+ result = open(pathname, flags, mode);
+ } else {
+ result = open(pathname, flags);
+ }
+
+ if (result < 0) {
+#ifdef EBUSY
+ /* EBUSY is a tricky one; sometimes it is synonymous with
+ EINTR, but sometimes it means the device is open
+ elsewhere (e.g., with a tape drive on Linux). We take
+ the middle path and retry, but with limited
+ patience. */
+ if (errno == EBUSY && e_busy_count < 10) {
+ e_busy_count ++;
+ continue;
+ } else
+#endif
+ if (0
+ /* Always retry on EINTR; if the caller did
+ not specify non-blocking mode, then also retry on
+ EAGAIN or EWOULDBLOCK. */
+#ifdef EINTR
+ || errno == EINTR
+#endif
+ || ( 1
+#ifdef O_NONBLOCK
+ && !(flags & O_NONBLOCK)
+#endif
+ && ( 0
+#ifdef EAGAIN
+ || errno == EAGAIN
+#endif
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ ) ) ) {
+ /* Try again */
+ continue;
+ } else {
+ /* Failure. */
+ return result;
+ }
+ } else {
+ break;
+ }
+ }
+
+#ifdef F_SETFD
+ if (result >= 0) {
+ fcntl(result, F_SETFD, 1); /* Throw away result. */
+ }
+#endif
+
+ return result;
+}
+
+int robust_close(int fd) {
+ for (;;) {
+ int result;
+
+ result = close(fd);
+ if (result != 0 && (0
+#ifdef EINTR
+ || errno == EINTR
+#endif
+#ifdef EBUSY
+ || errno == EBUSY
+#endif
+#ifdef EAGAIN
+ || errno == EAGAIN
+#endif
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ )) {
+ continue;
+ } else {
+ return result;
+ }
+ }
+}
+
+uid_t
+get_client_uid(void)
+{
+ static uid_t client_uid = (uid_t) -1;
+ struct passwd *pwent;
+
+ if(client_uid == (uid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) {
+ client_uid = pwent->pw_uid;
+ endpwent();
+ }
+
+ return client_uid;
+}
+
+gid_t
+get_client_gid(void)
+{
+ static gid_t client_gid = (gid_t) -1;
+ struct passwd *pwent;
+
+ if(client_gid == (gid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) {
+ client_gid = pwent->pw_gid;
+ endpwent();
+ }
+
+ return client_gid;
+}
+
+char *
+get_original_cwd(void)
+{
+ if (original_cwd == NULL) {
+ original_cwd = g_get_current_dir();
+ }
+
+ return original_cwd;
+}
+
#ifdef TEST
-int main(argc, argv)
- int argc;
- char **argv;
+int
+main(
+ int argc,
+ char ** argv)
{
int rc;
int fd;
char *file;
char *line;
+ /*
+ * Configure program for internationalization:
+ * 1) Only set the message locale for now.
+ * 2) Set textdomain for all amanda related programs to "amanda"
+ * We don't want to be forced to support dozens of message catalogs
+ */
+ setlocale(LC_MESSAGES, "C");
+ textdomain("amanda");
+
safe_fd(-1, 0);
set_pname("file test");
+ dbopen(NULL);
+
+ /* Don't die when child closes pipe */
+ signal(SIGPIPE, SIG_IGN);
+
name = "/tmp/a/b/c/d/e";
if (argc > 2 && argv[1][0] != '\0') {
name = argv[1];
name = argv[3];
}
- fprintf(stderr, "Create parent directories of %s ...", name);
- rc = mkpdir(name, 02777, (uid_t)-1, (gid_t)-1);
+ g_fprintf(stderr, _("Create parent directories of %s ..."), name);
+ rc = mkpdir(name, (mode_t)02777, (uid_t)-1, (gid_t)-1);
if (rc == 0)
- fprintf(stderr, " done\n");
+ g_fprintf(stderr, " done\n");
else {
- perror("failed");
+ perror(_("failed"));
return rc;
}
- fprintf(stderr, "Delete %s back to %s ...", name, top);
+ g_fprintf(stderr, _("Delete %s back to %s ..."), name, top);
rc = rmpdir(name, top);
if (rc == 0)
- fprintf(stderr, " done\n");
+ g_fprintf(stderr, _(" done\n"));
else {
- perror("failed");
+ perror(_("failed"));
return rc;
}
- fprintf(stderr, "areads dump of %s ...", file);
+ g_fprintf(stderr, _("areads dump of %s ..."), file);
if ((fd = open (file, 0)) < 0) {
perror(file);
return 1;
amfree(line);
}
aclose(fd);
- fprintf(stderr, " done.\n");
+ g_fprintf(stderr, _(" done.\n"));
- fprintf(stderr, "Finished.\n");
+ g_fprintf(stderr, _("Finished.\n"));
+
+ dbclose();
return 0;
}
#endif
+