/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. * * This file is part of LVM2. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/misc/lib.h" #include "lib/misc/lvm-file.h" #include #include #include #include #include /* * Creates a temporary filename, and opens a descriptor to the * file. Both the filename and descriptor are needed so we can * rename the file after successfully writing it. Grab * NFS-supported exclusive fcntl discretionary lock. */ int create_temp_name(const char *dir, char *buffer, size_t len, int *fd, unsigned *seed) { const struct flock lock = { .l_type = F_WRLCK }; int i, num; pid_t pid; char hostname[255]; char *p; num = rand_r(seed); pid = getpid(); if (gethostname(hostname, sizeof(hostname)) < 0) { log_sys_error("gethostname", ""); dm_strncpy(hostname, "nohostname", sizeof(hostname)); } else { /* Replace any '/' with '?' found in the hostname. */ p = hostname; while ((p = strchr(p, '/'))) *p = '?'; } for (i = 0; i < 20; i++, num++) { if (dm_snprintf(buffer, len, "%s/.lvm_%s_%d_%d", dir, hostname, pid, num) == -1) { log_error("Not enough space to build temporary file " "string."); return 0; } *fd = open(buffer, O_CREAT | O_EXCL | O_WRONLY | O_APPEND, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH); if (*fd < 0) continue; if (!fcntl(*fd, F_SETLK, &lock)) return 1; if (close(*fd)) log_sys_error("close", buffer); } return 0; } /* * NFS-safe rename of a temporary file to a common name, designed * to avoid race conditions and not overwrite the destination if * it exists. * * Try to create the new filename as a hard link to the original. * Check the link count of the original file to see if it worked. * (Assumes nothing else touches our temporary file!) If it * worked, unlink the old filename. */ int lvm_rename(const char *old, const char *new) { struct stat buf; if (link(old, new)) { log_error("%s: rename to %s failed: %s", old, new, strerror(errno)); return 0; } if (stat(old, &buf)) { log_sys_error("stat", old); return 0; } if (buf.st_nlink != 2) { log_error("%s: rename to %s failed", old, new); return 0; } if (unlink(old) && (errno != ENOENT)) { log_sys_error("unlink", old); return 0; } return 1; } int path_exists(const char *path) { struct stat info; if (!*path) return 0; if (stat(path, &info) < 0) return 0; return 1; } int dir_exists(const char *path) { struct stat info; if (!*path) return 0; if (stat(path, &info) < 0) return 0; if (!S_ISDIR(info.st_mode)) return 0; return 1; } int dir_create(const char *path, int mode) { int r; log_debug("Creating directory %s.", path); dm_prepare_selinux_context(path, S_IFDIR); r = mkdir(path, mode); dm_prepare_selinux_context(NULL, 0); if (r == 0) return 1; if (errno == EEXIST) { if (dir_exists(path)) return 1; log_error("Path %s is not a directory.", path); } else log_sys_error("mkdir", path); return 0; } int dir_create_recursive(const char *path, int mode) { int r = 0; char *orig, *s; orig = s = strdup(path); if (!s) { log_error("Failed to duplicate directory path %s.", path); return 0; } while ((s = strchr(s, '/')) != NULL) { *s = '\0'; if (*orig && !dir_exists(orig) && !dir_create(orig, mode)) goto_out; *s++ = '/'; } if (!dir_exists(path) && !dir_create(path, mode)) goto_out; r = 1; out: free(orig); return r; } void sync_dir(const char *file) { int fd; char *dir, *c; if (!(dir = strdup(file))) { log_error("sync_dir failed in strdup"); return; } if (!dir_exists(dir)) { c = dir + strlen(dir); while (*c != '/' && c > dir) c--; if (c == dir) *c++ = '.'; *c = '\0'; } if ((fd = open(dir, O_RDONLY)) == -1) { log_sys_error("open", dir); goto out; } if (fsync(fd) && (errno != EROFS) && (errno != EINVAL)) log_sys_error("fsync", dir); if (close(fd)) log_sys_error("close", dir); out: free(dir); } /* * Attempt to obtain fcntl lock on a file, if necessary creating file first * or waiting. * Returns file descriptor on success, else -1. * mode is F_WRLCK or F_RDLCK */ int fcntl_lock_file(const char *file, short lock_type, int warn_if_read_only) { const struct flock lock = { .l_type = lock_type }; int lockfd; char *dir; char *c; if (!(dir = strdup(file))) { log_error("fcntl_lock_file failed in strdup."); return -1; } if ((c = strrchr(dir, '/'))) *c = '\0'; if (!dm_create_dir(dir)) { free(dir); return -1; } free(dir); log_very_verbose("Locking %s (%s, %hd)", file, (lock_type == F_WRLCK) ? "F_WRLCK" : "F_RDLCK", lock_type); if ((lockfd = open(file, O_RDWR | O_CREAT, 0777)) < 0) { /* EACCES has been reported on NFS */ if (warn_if_read_only || (errno != EROFS && errno != EACCES)) log_sys_error("open", file); else stack; return -1; } if (fcntl(lockfd, F_SETLKW, &lock)) { log_sys_error("fcntl", file); if (close(lockfd)) log_sys_error("close", file); return -1; } return lockfd; } void fcntl_unlock_file(int lockfd) { const struct flock lock = { .l_type = F_UNLCK }; log_very_verbose("Unlocking fd %d", lockfd); if (fcntl(lockfd, F_SETLK, &lock) == -1) log_sys_error("fcntl", ""); if (close(lockfd)) log_sys_error("close",""); } int lvm_fclose(FILE *fp, const char *filename) { if (!dm_fclose(fp)) return 0; if (errno == 0) log_error("%s: write error", filename); else log_sys_error("write error", filename); return EOF; } void lvm_stat_ctim(struct timespec *ctim, const struct stat *buf) { #ifdef HAVE_STAT_ST_CTIM *ctim = buf->st_ctim; #else ctim->tv_sec = buf->st_ctime; ctim->tv_nsec = 0; #endif }