Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Makefile-libglnx.am
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ libglnx_la_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags)
libglnx_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^glnx_" -no-undefined -export-dynamic
libglnx_la_LIBADD = $(libglnx_libs)

TESTS += test-libglnx-xattrs
libglnx_tests = test-libglnx-xattrs test-libglnx-fdio
TESTS += $(libglnx_tests)

check_PROGRAMS += test-libglnx-xattrs
check_PROGRAMS += $(libglnx_tests)
test_libglnx_xattrs_SOURCES = $(libglnx_srcpath)/tests/test-libglnx-xattrs.c
test_libglnx_xattrs_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags)
test_libglnx_xattrs_LDADD = $(libglnx_libs) libglnx.la

test_libglnx_fdio_SOURCES = $(libglnx_srcpath)/tests/test-libglnx-fdio.c
test_libglnx_fdio_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags)
test_libglnx_fdio_LDADD = $(libglnx_libs) libglnx.la
85 changes: 65 additions & 20 deletions glnx-fdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,54 +54,99 @@
sizeof(type) <= 4 ? 10 : \
sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))

static gboolean
rename_file_noreplace_at (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath,
gboolean ignore_eexist,
GError **error)

/* An implementation of renameat2(..., RENAME_NOREPLACE)
* with fallback to a non-atomic version.
*/
int
glnx_renameat2_noreplace (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath)
{
#ifndef ENABLE_WRPSEUDO_COMPAT
if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) < 0)
{
if (errno == EINVAL || errno == ENOSYS)
{
/* Fall through */
;
}
else if (errno == EEXIST && ignore_eexist)
{
(void) unlinkat (olddirfd, oldpath, 0);
return TRUE;
}
else
{
glnx_set_error_from_errno (error);
return FALSE;
return -1;
}
}
else
return TRUE;
#endif

if (linkat (olddirfd, oldpath, newdirfd, newpath, 0) < 0)
return -1;

if (unlinkat (olddirfd, oldpath, 0) < 0)
return -1;

return 0;
}

static gboolean
rename_file_noreplace_at (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath,
gboolean ignore_eexist,
GError **error)
{
if (glnx_renameat2_noreplace (olddirfd, oldpath,
newdirfd, newpath) < 0)
{
if (errno == EEXIST && ignore_eexist)
/* Fall through */
;
{
(void) unlinkat (olddirfd, oldpath, 0);
return TRUE;
}
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}

if (unlinkat (olddirfd, oldpath, 0) < 0)
return TRUE;
}

/* An implementation of renameat2(..., RENAME_EXCHANGE)
* with fallback to a non-atomic version.
*/
int
glnx_renameat2_exchange (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath)
{
#ifndef ENABLE_WRPSEUDO_COMPAT
if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_EXCHANGE) == 0)
return 0;
else
{
glnx_set_error_from_errno (error);
return FALSE;
if (errno == ENOSYS || errno == EINVAL)
{
/* Fall through */
}
else
{
return -1;
}
}
#endif

return TRUE;
/* Fallback */
{ const char *old_tmp_name = glnx_strjoina (oldpath, ".XXXXXX");

/* Move old out of the way */
if (renameat (olddirfd, oldpath, olddirfd, old_tmp_name) < 0)
return -1;
/* Now move new into its place */
if (renameat (newdirfd, newpath, olddirfd, oldpath) < 0)
return -1;
/* And finally old(tmp) into new */
if (renameat (olddirfd, old_tmp_name, newdirfd, newpath) < 0)
return -1;
}
return 0;
}

gboolean
Expand Down
6 changes: 6 additions & 0 deletions glnx-fdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,10 @@ glnx_stream_fstat (GFileDescriptorBased *stream,
struct stat *stbuf,
GError **error);

int glnx_renameat2_noreplace (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
int glnx_renameat2_exchange (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);


G_END_DECLS
3 changes: 3 additions & 0 deletions glnx-missing.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@
#ifndef RENAME_NOREPLACE
#define RENAME_NOREPLACE (1 << 0)
#endif
#ifndef RENAME_EXCHANGE
#define RENAME_EXCHANGE (1 << 1)
#endif

#include "glnx-missing-syscall.h"
155 changes: 155 additions & 0 deletions tests/test-libglnx-fdio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/

#include "config.h"
#include "libglnx.h"
#include <glib.h>
#include <stdlib.h>
#include <gio/gio.h>
#include <err.h>
#include <string.h>

static gboolean
renameat_test_setup (int *out_srcfd, int *out_destfd,
GError **error)
{
glnx_fd_close int srcfd = -1;
glnx_fd_close int destfd = -1;

(void) glnx_shutil_rm_rf_at (AT_FDCWD, "srcdir", NULL, NULL);
if (mkdir ("srcdir", 0755) < 0)
err (1, "mkdir");
if (!glnx_opendirat (AT_FDCWD, "srcdir", TRUE, &srcfd, error))
return FALSE;
(void) glnx_shutil_rm_rf_at (AT_FDCWD, "destdir", NULL, NULL);
if (mkdir ("destdir", 0755) < 0)
err (1, "mkdir");
if (!glnx_opendirat (AT_FDCWD, "destdir", TRUE, &destfd, error))
return FALSE;

if (!glnx_file_replace_contents_at (srcfd, "foo", (guint8*)"foo contents", strlen ("foo contents"),
GLNX_FILE_REPLACE_NODATASYNC, NULL, error))
return FALSE;
if (!glnx_file_replace_contents_at (destfd, "bar", (guint8*)"bar contents", strlen ("bar contents"),
GLNX_FILE_REPLACE_NODATASYNC, NULL, error))
return FALSE;

*out_srcfd = srcfd; srcfd = -1;
*out_destfd = destfd; destfd = -1;
return TRUE;
}

static void
test_renameat2_noreplace (void)
{
g_autoptr(GError) local_error = NULL;
GError **error = &local_error;
glnx_fd_close int srcfd = -1;
glnx_fd_close int destfd = -1;
struct stat stbuf;

if (!renameat_test_setup (&srcfd, &destfd, error))
goto out;

if (glnx_renameat2_noreplace (srcfd, "foo", destfd, "bar") == 0)
g_assert_not_reached ();
else
{
g_assert_cmpint (errno, ==, EEXIST);
}

if (glnx_renameat2_noreplace (srcfd, "foo", destfd, "baz") < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (fstatat (destfd, "bar", &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}

if (fstatat (srcfd, "foo", &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
g_assert_not_reached ();
else
g_assert_cmpint (errno, ==, ENOENT);

out:
g_assert_no_error (local_error);
}

static void
test_renameat2_exchange (void)
{
g_autoptr(GError) local_error = NULL;
GError **error = &local_error;
glnx_fd_close int srcfd = -1;
glnx_fd_close int destfd = -1;
struct stat stbuf;

if (!renameat_test_setup (&srcfd, &destfd, error))
goto out;

if (glnx_renameat2_exchange (AT_FDCWD, "srcdir", AT_FDCWD, "destdir") < 0)
{
glnx_set_error_from_errno (error);
goto out;
}

/* Ensure the dir fds are the same */
if (fstatat (srcfd, "foo", &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (fstatat (destfd, "bar", &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
/* But the dirs should be swapped */
if (fstatat (AT_FDCWD, "destdir/foo", &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (fstatat (AT_FDCWD, "srcdir/bar", &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}

out:
g_assert_no_error (local_error);
}

int main (int argc, char **argv)
{
int ret;

g_test_init (&argc, &argv, NULL);

g_test_add_func ("/renameat2-noreplace", test_renameat2_noreplace);
g_test_add_func ("/renameat2-exchange", test_renameat2_exchange);

ret = g_test_run();

return ret;
}