mirror of
https://github.com/irssi/irssi.git
synced 2024-12-04 14:46:39 -05:00
Make config_write more atomic to prevent truncation when out of space
This is modeled after glib's g_file_set_contents. It doesn't use that function directly because the writing is done with GIOChannel streaming-like writes and g_file_set_contents expects the whole thing to be in-memory. Main differences with g_file_set_contents: - complete lack of win32 special casing (cygwin/WSL should work though) - no fallocate() (linux only, but we don't know the size upfront, anyway) - always calls fsync (glib skips it on btrfs or when not overwriting) Other than that, it's the same old mkstemp + fsync + rename.
This commit is contained in:
parent
b2b1ac6708
commit
8deb6182c6
@ -301,30 +301,67 @@ int config_write(CONFIG_REC *rec, const char *fname, int create_mode)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
int fd;
|
int fd;
|
||||||
|
int save_errno;
|
||||||
|
char *tmp_name;
|
||||||
|
const char *dest_name;
|
||||||
|
|
||||||
g_return_val_if_fail(rec != NULL, -1);
|
g_return_val_if_fail(rec != NULL, -1);
|
||||||
g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1);
|
g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1);
|
||||||
g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1);
|
g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1);
|
||||||
|
|
||||||
fd = open(fname != NULL ? fname : rec->fname,
|
dest_name = fname != NULL ? fname : rec->fname;
|
||||||
|
tmp_name = g_strdup_printf("%s.XXXXXX", dest_name);
|
||||||
|
|
||||||
|
fd = g_mkstemp_full(tmp_name,
|
||||||
O_WRONLY | O_TRUNC | O_CREAT,
|
O_WRONLY | O_TRUNC | O_CREAT,
|
||||||
create_mode != -1 ? create_mode : rec->create_mode);
|
create_mode != -1 ? create_mode : rec->create_mode);
|
||||||
if (fd == -1)
|
if (fd == -1) {
|
||||||
return config_error(rec, g_strerror(errno));
|
config_error(rec, g_strerror(errno));
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
rec->handle = g_io_channel_unix_new(fd);
|
rec->handle = g_io_channel_unix_new(fd);
|
||||||
g_io_channel_set_encoding(rec->handle, NULL, NULL);
|
g_io_channel_set_encoding(rec->handle, NULL, NULL);
|
||||||
g_io_channel_set_close_on_unref(rec->handle, TRUE);
|
g_io_channel_set_close_on_unref(rec->handle, TRUE);
|
||||||
|
|
||||||
rec->tmp_indent_level = 0;
|
rec->tmp_indent_level = 0;
|
||||||
rec->tmp_last_lf = TRUE;
|
rec->tmp_last_lf = TRUE;
|
||||||
ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
|
ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
|
||||||
|
save_errno = errno;
|
||||||
|
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
/* write error */
|
/* write error */
|
||||||
config_error(rec, errno == 0 ? "bug" : g_strerror(errno));
|
unlink(tmp_name);
|
||||||
|
config_error(rec, save_errno == 0 ? "bug" : g_strerror(save_errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = fsync(fd);
|
||||||
|
save_errno = errno;
|
||||||
|
|
||||||
|
if (ret == -1) {
|
||||||
|
unlink(tmp_name);
|
||||||
|
config_error(rec, g_strerror(errno));
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_io_channel_unref(rec->handle);
|
g_io_channel_unref(rec->handle);
|
||||||
rec->handle = NULL;
|
rec->handle = NULL;
|
||||||
|
|
||||||
|
if (rename(tmp_name, dest_name) == -1) {
|
||||||
|
unlink(tmp_name);
|
||||||
|
config_error(rec, g_strerror(errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (rec->handle) {
|
||||||
|
g_io_channel_unref(rec->handle);
|
||||||
|
rec->handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(tmp_name);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user