/*
* Claws Mail -- a GTK based, lightweight, and fast e-mail client
* Copyright (C) 1999-2018 Colin Leroy and the Claws Mail team
*
* 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 3 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, see .
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#include "claws-features.h"
#endif
#include
#ifndef G_OS_WIN32
#include
#else
#define WEXITSTATUS(x) (x)
#endif
#include
#include
#include
#include
#include "defs.h"
#include "codeconv.h"
#include "timing.h"
#include "file-utils.h"
gboolean prefs_common_get_flush_metadata(void);
gboolean prefs_common_get_use_shred(void);
static int safe_fclose(FILE *fp)
{
int r;
START_TIMING("");
if (fflush(fp) != 0) {
return EOF;
}
if (prefs_common_get_flush_metadata() && fsync(fileno(fp)) != 0) {
return EOF;
}
r = fclose(fp);
END_TIMING();
return r;
}
/* Unlock, then safe-close a file pointer
* Safe close is done using fflush + fsync
* if the according preference says so.
*/
int claws_safe_fclose(FILE *fp)
{
#if HAVE_FGETS_UNLOCKED
funlockfile(fp);
#endif
return safe_fclose(fp);
}
#if HAVE_FGETS_UNLOCKED
/* Open a file and locks it once
* so subsequent I/O is faster
*/
FILE *claws_fopen(const char *file, const char *mode)
{
FILE *fp = fopen(file, mode);
if (!fp)
return NULL;
flockfile(fp);
return fp;
}
FILE *claws_fdopen(int fd, const char *mode)
{
FILE *fp = fdopen(fd, mode);
if (!fp)
return NULL;
flockfile(fp);
return fp;
}
/* Unlocks and close a file pointer
*/
int claws_fclose(FILE *fp)
{
funlockfile(fp);
return fclose(fp);
}
#endif
int claws_unlink(const char *filename)
{
GStatBuf s;
static int found_shred = -1;
static const gchar *args[4];
if (filename == NULL)
return 0;
if (prefs_common_get_use_shred()) {
if (found_shred == -1) {
/* init */
args[0] = g_find_program_in_path("shred");
debug_print("found shred: %s\n", args[0]);
found_shred = (args[0] != NULL) ? 1:0;
args[1] = "-f";
args[3] = NULL;
}
if (found_shred == 1) {
if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
if (s.st_nlink == 1) {
gint status=0;
args[2] = filename;
g_spawn_sync(NULL, (gchar **)args, NULL, 0,
NULL, NULL, NULL, NULL, &status, NULL);
debug_print("%s %s exited with status %d\n",
args[0], filename, WEXITSTATUS(status));
if (truncate(filename, 0) < 0)
g_warning("couldn't truncate: %s", filename);
}
}
}
}
return g_unlink(filename);
}
gint file_strip_crs(const gchar *file)
{
FILE *fp = NULL, *outfp = NULL;
gchar buf[4096];
gchar *out = get_tmp_file();
if (file == NULL)
goto freeout;
fp = claws_fopen(file, "rb");
if (!fp)
goto freeout;
outfp = claws_fopen(out, "wb");
if (!outfp) {
claws_fclose(fp);
goto freeout;
}
while (claws_fgets(buf, sizeof (buf), fp) != NULL) {
strcrchomp(buf);
if (claws_fputs(buf, outfp) == EOF) {
claws_fclose(fp);
claws_fclose(outfp);
goto unlinkout;
}
}
claws_fclose(fp);
if (claws_safe_fclose(outfp) == EOF) {
goto unlinkout;
}
if (move_file(out, file, TRUE) < 0)
goto unlinkout;
g_free(out);
return 0;
unlinkout:
claws_unlink(out);
freeout:
g_free(out);
return -1;
}
/*
* Append src file body to the tail of dest file.
* Now keep_backup has no effects.
*/
gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
{
FILE *src_fp, *dest_fp;
gint n_read;
gchar buf[BUFSIZ];
gboolean err = FALSE;
if ((src_fp = claws_fopen(src, "rb")) == NULL) {
FILE_OP_ERROR(src, "claws_fopen");
return -1;
}
if ((dest_fp = claws_fopen(dest, "ab")) == NULL) {
FILE_OP_ERROR(dest, "claws_fopen");
claws_fclose(src_fp);
return -1;
}
if (change_file_mode_rw(dest_fp, dest) < 0) {
FILE_OP_ERROR(dest, "chmod");
g_warning("can't change file mode: %s", dest);
}
while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
if (n_read < sizeof(buf) && claws_ferror(src_fp))
break;
if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
g_warning("writing to %s failed", dest);
claws_fclose(dest_fp);
claws_fclose(src_fp);
claws_unlink(dest);
return -1;
}
}
if (claws_ferror(src_fp)) {
FILE_OP_ERROR(src, "claws_fread");
err = TRUE;
}
claws_fclose(src_fp);
if (claws_fclose(dest_fp) == EOF) {
FILE_OP_ERROR(dest, "claws_fclose");
err = TRUE;
}
if (err) {
claws_unlink(dest);
return -1;
}
return 0;
}
gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
{
FILE *src_fp, *dest_fp;
gint n_read;
gchar buf[BUFSIZ];
gchar *dest_bak = NULL;
gboolean err = FALSE;
if ((src_fp = claws_fopen(src, "rb")) == NULL) {
FILE_OP_ERROR(src, "claws_fopen");
return -1;
}
if (is_file_exist(dest)) {
dest_bak = g_strconcat(dest, ".bak", NULL);
if (rename_force(dest, dest_bak) < 0) {
FILE_OP_ERROR(dest, "rename");
claws_fclose(src_fp);
g_free(dest_bak);
return -1;
}
}
if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
FILE_OP_ERROR(dest, "claws_fopen");
claws_fclose(src_fp);
if (dest_bak) {
if (rename_force(dest_bak, dest) < 0)
FILE_OP_ERROR(dest_bak, "rename");
g_free(dest_bak);
}
return -1;
}
if (change_file_mode_rw(dest_fp, dest) < 0) {
FILE_OP_ERROR(dest, "chmod");
g_warning("can't change file mode: %s", dest);
}
while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
if (n_read < sizeof(buf) && claws_ferror(src_fp))
break;
if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
g_warning("writing to %s failed", dest);
claws_fclose(dest_fp);
claws_fclose(src_fp);
if (claws_unlink(dest) < 0)
FILE_OP_ERROR(dest, "claws_unlink");
if (dest_bak) {
if (rename_force(dest_bak, dest) < 0)
FILE_OP_ERROR(dest_bak, "rename");
g_free(dest_bak);
}
return -1;
}
}
if (claws_ferror(src_fp)) {
FILE_OP_ERROR(src, "claws_fread");
err = TRUE;
}
claws_fclose(src_fp);
if (claws_safe_fclose(dest_fp) == EOF) {
FILE_OP_ERROR(dest, "claws_fclose");
err = TRUE;
}
if (err) {
if (claws_unlink(dest) < 0)
FILE_OP_ERROR(dest, "claws_unlink");
if (dest_bak) {
if (rename_force(dest_bak, dest) < 0)
FILE_OP_ERROR(dest_bak, "rename");
g_free(dest_bak);
}
return -1;
}
if (keep_backup == FALSE && dest_bak)
if (claws_unlink(dest_bak) < 0)
FILE_OP_ERROR(dest_bak, "claws_unlink");
g_free(dest_bak);
return 0;
}
gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
{
if (overwrite == FALSE && is_file_exist(dest)) {
g_warning("move_file(): file %s already exists", dest);
return -1;
}
if (rename_force(src, dest) == 0) return 0;
if (EXDEV != errno) {
FILE_OP_ERROR(src, "rename");
return -1;
}
if (copy_file(src, dest, FALSE) < 0) return -1;
claws_unlink(src);
return 0;
}
gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
{
gint n_read;
gint bytes_left, to_read;
gchar buf[BUFSIZ];
if (fseek(fp, offset, SEEK_SET) < 0) {
perror("fseek");
return -1;
}
bytes_left = length;
to_read = MIN(bytes_left, sizeof(buf));
while ((n_read = claws_fread(buf, sizeof(gchar), to_read, fp)) > 0) {
if (n_read < to_read && claws_ferror(fp))
break;
if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
return -1;
}
bytes_left -= n_read;
if (bytes_left == 0)
break;
to_read = MIN(bytes_left, sizeof(buf));
}
if (claws_ferror(fp)) {
perror("claws_fread");
return -1;
}
return 0;
}
gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
{
FILE *dest_fp;
gboolean err = FALSE;
if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
FILE_OP_ERROR(dest, "claws_fopen");
return -1;
}
if (change_file_mode_rw(dest_fp, dest) < 0) {
FILE_OP_ERROR(dest, "chmod");
g_warning("can't change file mode: %s", dest);
}
if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
err = TRUE;
if (claws_safe_fclose(dest_fp) == EOF) {
FILE_OP_ERROR(dest, "claws_fclose");
err = TRUE;
}
if (err) {
g_warning("writing to %s failed", dest);
claws_unlink(dest);
return -1;
}
return 0;
}
gint canonicalize_file(const gchar *src, const gchar *dest)
{
FILE *src_fp, *dest_fp;
gchar buf[BUFFSIZE];
gint len;
gboolean err = FALSE;
gboolean last_linebreak = FALSE;
if (src == NULL || dest == NULL)
return -1;
if ((src_fp = claws_fopen(src, "rb")) == NULL) {
FILE_OP_ERROR(src, "claws_fopen");
return -1;
}
if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
FILE_OP_ERROR(dest, "claws_fopen");
claws_fclose(src_fp);
return -1;
}
if (change_file_mode_rw(dest_fp, dest) < 0) {
FILE_OP_ERROR(dest, "chmod");
g_warning("can't change file mode: %s", dest);
}
while (claws_fgets(buf, sizeof(buf), src_fp) != NULL) {
gint r = 0;
len = strlen(buf);
if (len == 0) break;
last_linebreak = FALSE;
if (buf[len - 1] != '\n') {
last_linebreak = TRUE;
r = claws_fputs(buf, dest_fp);
} else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
r = claws_fputs(buf, dest_fp);
} else {
if (len > 1) {
r = claws_fwrite(buf, 1, len - 1, dest_fp);
if (r != (len -1))
r = EOF;
}
if (r != EOF)
r = claws_fputs("\r\n", dest_fp);
}
if (r == EOF) {
g_warning("writing to %s failed", dest);
claws_fclose(dest_fp);
claws_fclose(src_fp);
claws_unlink(dest);
return -1;
}
}
if (last_linebreak == TRUE) {
if (claws_fputs("\r\n", dest_fp) == EOF)
err = TRUE;
}
if (claws_ferror(src_fp)) {
FILE_OP_ERROR(src, "claws_fgets");
err = TRUE;
}
claws_fclose(src_fp);
if (claws_safe_fclose(dest_fp) == EOF) {
FILE_OP_ERROR(dest, "claws_fclose");
err = TRUE;
}
if (err) {
claws_unlink(dest);
return -1;
}
return 0;
}
gint canonicalize_file_replace(const gchar *file)
{
gchar *tmp_file;
tmp_file = get_tmp_file();
if (canonicalize_file(file, tmp_file) < 0) {
g_free(tmp_file);
return -1;
}
if (move_file(tmp_file, file, TRUE) < 0) {
g_warning("can't replace file: %s", file);
claws_unlink(tmp_file);
g_free(tmp_file);
return -1;
}
g_free(tmp_file);
return 0;
}
gint str_write_to_file(const gchar *str, const gchar *file, gboolean safe)
{
FILE *fp;
size_t len;
int r;
cm_return_val_if_fail(str != NULL, -1);
cm_return_val_if_fail(file != NULL, -1);
if ((fp = claws_fopen(file, "wb")) == NULL) {
FILE_OP_ERROR(file, "claws_fopen");
return -1;
}
len = strlen(str);
if (len == 0) {
claws_fclose(fp);
return 0;
}
if (claws_fwrite(str, 1, len, fp) != len) {
FILE_OP_ERROR(file, "claws_fwrite");
claws_fclose(fp);
claws_unlink(file);
return -1;
}
if (safe) {
r = claws_safe_fclose(fp);
} else {
r = claws_fclose(fp);
}
if (r == EOF) {
FILE_OP_ERROR(file, "claws_fclose");
claws_unlink(file);
return -1;
}
return 0;
}
static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
{
GByteArray *array;
guchar buf[BUFSIZ];
gint n_read;
gchar *str;
cm_return_val_if_fail(fp != NULL, NULL);
array = g_byte_array_new();
while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
if (n_read < sizeof(buf) && claws_ferror(fp))
break;
g_byte_array_append(array, buf, n_read);
}
if (claws_ferror(fp)) {
FILE_OP_ERROR("file stream", "claws_fread");
g_byte_array_free(array, TRUE);
return NULL;
}
buf[0] = '\0';
g_byte_array_append(array, buf, 1);
str = (gchar *)array->data;
g_byte_array_free(array, FALSE);
if (recode && !g_utf8_validate(str, -1, NULL)) {
const gchar *src_codeset, *dest_codeset;
gchar *tmp = NULL;
src_codeset = conv_get_locale_charset_str();
dest_codeset = CS_UTF_8;
tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
g_free(str);
str = tmp;
}
return str;
}
static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
{
FILE *fp;
gchar *str;
GStatBuf s;
#ifndef G_OS_WIN32
gint fd, err;
struct timeval timeout = {1, 0};
fd_set fds;
int fflags = 0;
#endif
cm_return_val_if_fail(file != NULL, NULL);
if (g_stat(file, &s) != 0) {
FILE_OP_ERROR(file, "stat");
return NULL;
}
if (S_ISDIR(s.st_mode)) {
g_warning("%s: is a directory", file);
return NULL;
}
#ifdef G_OS_WIN32
fp = claws_fopen (file, "rb");
if (fp == NULL) {
FILE_OP_ERROR(file, "open");
return NULL;
}
#else
/* test whether the file is readable without blocking */
fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
if (fd == -1) {
FILE_OP_ERROR(file, "open");
return NULL;
}
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* allow for one second */
err = select(fd+1, &fds, NULL, NULL, &timeout);
if (err <= 0 || !FD_ISSET(fd, &fds)) {
if (err < 0) {
FILE_OP_ERROR(file, "select");
} else {
g_warning("%s: doesn't seem readable", file);
}
close(fd);
return NULL;
}
/* Now clear O_NONBLOCK */
if ((fflags = fcntl(fd, F_GETFL)) < 0) {
FILE_OP_ERROR(file, "fcntl (F_GETFL)");
close(fd);
return NULL;
}
if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
FILE_OP_ERROR(file, "fcntl (F_SETFL)");
close(fd);
return NULL;
}
/* get the FILE pointer */
fp = claws_fdopen(fd, "rb");
if (fp == NULL) {
FILE_OP_ERROR(file, "claws_fdopen");
close(fd); /* if fp isn't NULL, we'll use claws_fclose instead! */
return NULL;
}
#endif
str = file_read_stream_to_str_full(fp, recode);
claws_fclose(fp);
return str;
}
gchar *file_read_to_str(const gchar *file)
{
return file_read_to_str_full(file, TRUE);
}
gchar *file_read_stream_to_str(FILE *fp)
{
return file_read_stream_to_str_full(fp, TRUE);
}
gchar *file_read_to_str_no_recode(const gchar *file)
{
return file_read_to_str_full(file, FALSE);
}
gchar *file_read_stream_to_str_no_recode(FILE *fp)
{
return file_read_stream_to_str_full(fp, FALSE);
}
gint rename_force(const gchar *oldpath, const gchar *newpath)
{
#ifndef G_OS_UNIX
if (!is_file_entry_exist(oldpath)) {
errno = ENOENT;
return -1;
}
if (is_file_exist(newpath)) {
if (claws_unlink(newpath) < 0)
FILE_OP_ERROR(newpath, "unlink");
}
#endif
return g_rename(oldpath, newpath);
}
gint copy_dir(const gchar *src, const gchar *dst)
{
GDir *dir;
const gchar *name;
if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
g_warning("failed to open directory: %s", src);
return -1;
}
if (make_dir(dst) < 0) {
g_dir_close(dir);
return -1;
}
while ((name = g_dir_read_name(dir)) != NULL) {
gchar *old_file, *new_file;
gint r = 0;
old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
debug_print("copying: %s -> %s\n", old_file, new_file);
if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
r = copy_file(old_file, new_file, TRUE);
}
#ifndef G_OS_WIN32
/* Windows has no symlinks. Or well, Vista seems to
have something like this but the semantics might be
different. Thus we don't use it under Windows. */
else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
GError *error = NULL;
gchar *target = g_file_read_link(old_file, &error);
if (error) {
g_warning("couldn't read link: %s", error->message);
g_error_free(error);
}
if (target) {
r = symlink(target, new_file);
g_free(target);
}
}
#endif /*G_OS_WIN32*/
else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
r = copy_dir(old_file, new_file);
}
g_free(old_file);
g_free(new_file);
if (r < 0) {
g_dir_close(dir);
return r;
}
}
g_dir_close(dir);
return 0;
}
gint change_file_mode_rw(FILE *fp, const gchar *file)
{
#if HAVE_FCHMOD
return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
#else
return g_chmod(file, S_IRUSR|S_IWUSR);
#endif
}
FILE *my_tmpfile(void)
{
const gchar suffix[] = ".XXXXXX";
const gchar *tmpdir;
guint tmplen;
const gchar *progname;
guint proglen;
gchar *fname;
gint fd;
FILE *fp;
#ifndef G_OS_WIN32
gchar buf[2]="\0";
#endif
tmpdir = get_tmp_dir();
tmplen = strlen(tmpdir);
progname = g_get_prgname();
if (progname == NULL)
progname = "claws-mail";
proglen = strlen(progname);
Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
return tmpfile());
memcpy(fname, tmpdir, tmplen);
fname[tmplen] = G_DIR_SEPARATOR;
memcpy(fname + tmplen + 1, progname, proglen);
memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
fd = g_mkstemp(fname);
if (fd < 0)
return tmpfile();
#ifndef G_OS_WIN32
claws_unlink(fname);
/* verify that we can write in the file after unlinking */
if (write(fd, buf, 1) < 0) {
close(fd);
return tmpfile();
}
#endif
fp = claws_fdopen(fd, "w+b");
if (!fp)
close(fd);
else {
rewind(fp);
return fp;
}
return tmpfile();
}
FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
{
int fd;
*filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
fd = g_mkstemp(*filename);
if (fd < 0)
return NULL;
return claws_fdopen(fd, "w+");
}
FILE *str_open_as_stream(const gchar *str)
{
FILE *fp;
size_t len;
cm_return_val_if_fail(str != NULL, NULL);
fp = my_tmpfile();
if (!fp) {
FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
return NULL;
}
len = strlen(str);
if (len == 0) return fp;
if (claws_fwrite(str, 1, len, fp) != len) {
FILE_OP_ERROR("str_open_as_stream", "claws_fwrite");
claws_fclose(fp);
return NULL;
}
rewind(fp);
return fp;
}