/* This file is part of Idest.
Copyright (C) 2009-2011 Sergey Poznyakoff
Idest 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 3, or (at your option)
any later version.
Idest 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 Idest. If not, see . */
#include "idest.h"
#include
#include
#include
#include "dirname.h"
#include "backupfile.h"
#define MKDIR_PERMISSIONS 0777
#define CREAT_PERMISSIONS 0666
/* Concatenate BASE directory name, a single directory separator (/) and
NAME. Return in PBASELEN the length of BASE, not counting eventual trailing
slash. */
char *
concat_dir(const char *base, const char *name, size_t *pbaselen)
{
size_t len = strlen(base);
size_t size;
char *dir;
while (len > 0 && base[len - 1] == '/')
len--;
size = len + 1 + strlen(name);
dir = xmalloc(size + 1);
memcpy(dir, base, len);
dir[len++] = '/';
strcpy(dir + len, name);
if (pbaselen)
*pbaselen = len;
return dir;
}
/* Create the directory DIR, eventually creating all intermediate directories
starting from DIR + BASELEN. */
int
create_hierarchy(char *dir, size_t baselen)
{
int rc;
struct stat st;
char *p;
if (stat(dir, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
error(0, 0, "component %s is not a directory", dir);
return 1;
}
return 0;
} else if (errno != ENOENT) {
error(0, errno, "cannot stat file %s", dir);
return 1;
}
p = strrchr(dir, '/');
if (p) {
if (p - dir + 1 < baselen) {
error(0, 0, "base directory %s does not exist", dir);
return 1;
}
*p = 0;
}
rc = create_hierarchy(dir, baselen);
if (rc == 0) {
if (p)
*p = '/';
if (mkdir(dir, MKDIR_PERMISSIONS)) {
error(0, errno, "cannot create directory %s", dir);
rc = 1;
}
}
return rc;
}
/* Create a directory BASE/NAME (with eventual intermediate directories in
NAME). */
char *
create_directory(const char *base, const char *name)
{
size_t baselen;
char *dir = concat_dir(base, name, &baselen);
if (create_hierarchy(dir, baselen)) {
free(dir);
dir = NULL;
}
return dir;
}
/* Copy FILE to DST_FILE. */
int
copy_file(const char *file, const char *dst_file)
{
int in_fd, out_fd;
struct stat st;
char *buf = NULL;
size_t bufsize;
size_t fsize;
int rc;
in_fd = open(file, O_RDONLY);
if (in_fd == -1) {
error(0, errno,
"cannot open source file %s for reading", file);
return 1;
}
if (fstat(in_fd, &st)) {
error(0, errno, "cannot stat source file %s", file);
close(in_fd);
return 1;
}
out_fd = creat(dst_file, CREAT_PERMISSIONS);
if (out_fd == -1) {
error(0, errno, "cannot create destination file %s", file);
close(in_fd);
return 1;
}
for (bufsize = fsize;
bufsize > 0 && (buf = malloc(bufsize)) == NULL;
bufsize /= 2);
if (bufsize == 0)
xalloc_die();
rc = 0;
fsize = st.st_size;
while (fsize > 0) {
size_t rest;
size_t rdbytes;
rest = fsize > bufsize ? bufsize : fsize;
rdbytes = read(in_fd, buf, rest);
if (rdbytes == -1) {
error(0, errno, "unexpected error reading %s", file);
rc = 1;
break;
}
rest = write(out_fd, buf, rdbytes);
if (rest == -1) {
error(0, errno, "unexpected error writing to %s",
dst_file);
rc = 1;
break;
} else if (rest != rdbytes) {
error(0, 0, "short write on %s", dst_file);
rc = 1;
}
fsize -= rdbytes;
}
free(buf);
close(in_fd);
close(out_fd);
if (rc) {
unlink(dst_file);
return 1;
}
return 0;
}
/* Backup a file */
int
backup_file(const char *file)
{
int rc = 0;
char *new_name;
if (backup_dir) {
if (file[0] == '/')
new_name = concat_dir(backup_dir,
last_component(file), NULL);
else
new_name = concat_dir(backup_dir, file, NULL);
if (access(new_name, F_OK) == 0) {
if (backup_type == no_backups) {
if (unlink(new_name))
error(0, errno,
"cannot unlink backup file %s",
new_name);
/* Try to continue anyway */
} else {
char *bfn = find_backup_file_name(new_name,
backup_type);
copy_file(new_name, bfn);
/* Continue no matter what the result of
copy_file was */
}
}
} else if (backup_type == no_backups)
return 0;
else
new_name = find_backup_file_name(file, backup_type);
rc = copy_file(file, new_name);
free(new_name);
return rc;
}