mirror of
https://github.com/ostreedev/ostree.git
synced 2024-12-22 17:35:55 +03:00
checkout: Support a "pure addition" mode
I plan to use this for `rpm-ostree livefs`. https://github.com/projectatomic/rpm-ostree/issues/639 Closes: #714 Approved by: jlebon
This commit is contained in:
parent
ff34810097
commit
94948e3522
@ -89,6 +89,14 @@ Boston, MA 02111-1307, USA.
|
|||||||
</para></listitem>
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--union-add</option></term>
|
||||||
|
|
||||||
|
<listitem><para>
|
||||||
|
Keep existing directories and files.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--allow-noent</option></term>
|
<term><option>--allow-noent</option></term>
|
||||||
|
|
||||||
|
@ -201,8 +201,16 @@ checkout_file_from_input_at (OstreeRepo *self,
|
|||||||
while (G_UNLIKELY (res == -1 && errno == EINTR));
|
while (G_UNLIKELY (res == -1 && errno == EINTR));
|
||||||
if (res == -1)
|
if (res == -1)
|
||||||
{
|
{
|
||||||
glnx_set_error_from_errno (error);
|
if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES)
|
||||||
goto out;
|
{
|
||||||
|
ret = TRUE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glnx_set_error_from_errno (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
|
if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
|
||||||
@ -240,6 +248,11 @@ checkout_file_from_input_at (OstreeRepo *self,
|
|||||||
while (G_UNLIKELY (fd == -1 && errno == EINTR));
|
while (G_UNLIKELY (fd == -1 && errno == EINTR));
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
{
|
{
|
||||||
|
if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES)
|
||||||
|
{
|
||||||
|
ret = TRUE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
glnx_set_error_from_errno (error);
|
glnx_set_error_from_errno (error);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@ -332,6 +345,12 @@ checkout_file_unioning_from_input_at (OstreeRepo *repo,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HARDLINK_RESULT_NOT_SUPPORTED,
|
||||||
|
HARDLINK_RESULT_SKIP_EXISTED,
|
||||||
|
HARDLINK_RESULT_LINKED
|
||||||
|
} HardlinkResult;
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
checkout_file_hardlink (OstreeRepo *self,
|
checkout_file_hardlink (OstreeRepo *self,
|
||||||
OstreeRepoCheckoutAtOptions *options,
|
OstreeRepoCheckoutAtOptions *options,
|
||||||
@ -339,28 +358,32 @@ checkout_file_hardlink (OstreeRepo *self,
|
|||||||
int destination_dfd,
|
int destination_dfd,
|
||||||
const char *destination_name,
|
const char *destination_name,
|
||||||
gboolean allow_noent,
|
gboolean allow_noent,
|
||||||
gboolean *out_was_supported,
|
HardlinkResult *out_result,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
gboolean ret = FALSE;
|
HardlinkResult ret_result = HARDLINK_RESULT_NOT_SUPPORTED;
|
||||||
gboolean ret_was_supported = FALSE;
|
|
||||||
int srcfd = (self->mode == OSTREE_REPO_MODE_BARE || self->mode == OSTREE_REPO_MODE_BARE_USER) ?
|
int srcfd = (self->mode == OSTREE_REPO_MODE_BARE || self->mode == OSTREE_REPO_MODE_BARE_USER) ?
|
||||||
self->objects_dir_fd : self->uncompressed_objects_dir_fd;
|
self->objects_dir_fd : self->uncompressed_objects_dir_fd;
|
||||||
|
|
||||||
again:
|
again:
|
||||||
if (linkat (srcfd, loose_path, destination_dfd, destination_name, 0) != -1)
|
if (linkat (srcfd, loose_path, destination_dfd, destination_name, 0) == 0)
|
||||||
ret_was_supported = TRUE;
|
ret_result = HARDLINK_RESULT_LINKED;
|
||||||
else if (!options->no_copy_fallback && (errno == EMLINK || errno == EXDEV || errno == EPERM))
|
else if (!options->no_copy_fallback && (errno == EMLINK || errno == EXDEV || errno == EPERM))
|
||||||
{
|
{
|
||||||
/* EMLINK, EXDEV and EPERM shouldn't be fatal; we just can't do the
|
/* EMLINK, EXDEV and EPERM shouldn't be fatal; we just can't do the
|
||||||
* optimization of hardlinking instead of copying.
|
* optimization of hardlinking instead of copying.
|
||||||
*/
|
*/
|
||||||
ret_was_supported = FALSE;
|
|
||||||
}
|
}
|
||||||
else if (allow_noent && errno == ENOENT)
|
else if (allow_noent && errno == ENOENT)
|
||||||
{
|
{
|
||||||
ret_was_supported = FALSE;
|
}
|
||||||
|
else if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES)
|
||||||
|
{
|
||||||
|
/* In this mode, we keep existing content. Distinguish this case though to
|
||||||
|
* avoid inserting into the devino cache.
|
||||||
|
*/
|
||||||
|
ret_result = HARDLINK_RESULT_SKIP_EXISTED;
|
||||||
}
|
}
|
||||||
else if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
|
else if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
|
||||||
{
|
{
|
||||||
@ -370,7 +393,7 @@ checkout_file_hardlink (OstreeRepo *self,
|
|||||||
* the same file, then rename() does nothing, and returns a
|
* the same file, then rename() does nothing, and returns a
|
||||||
* success status."
|
* success status."
|
||||||
*
|
*
|
||||||
* So we can't make this atomic.
|
* So we can't make this atomic.
|
||||||
*/
|
*/
|
||||||
(void) unlinkat (destination_dfd, destination_name, 0);
|
(void) unlinkat (destination_dfd, destination_name, 0);
|
||||||
goto again;
|
goto again;
|
||||||
@ -379,14 +402,12 @@ checkout_file_hardlink (OstreeRepo *self,
|
|||||||
{
|
{
|
||||||
g_prefix_error (error, "Hardlinking %s to %s: ", loose_path, destination_name);
|
g_prefix_error (error, "Hardlinking %s to %s: ", loose_path, destination_name);
|
||||||
glnx_set_error_from_errno (error);
|
glnx_set_error_from_errno (error);
|
||||||
goto out;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = TRUE;
|
if (out_result)
|
||||||
if (out_was_supported)
|
*out_result = ret_result;
|
||||||
*out_was_supported = ret_was_supported;
|
return TRUE;
|
||||||
out:
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -439,7 +460,7 @@ checkout_one_file_at (OstreeRepo *repo,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gboolean did_hardlink = FALSE;
|
HardlinkResult hardlink_res = HARDLINK_RESULT_NOT_SUPPORTED;
|
||||||
/* Try to do a hardlink first, if it's a regular file. This also
|
/* Try to do a hardlink first, if it's a regular file. This also
|
||||||
* traverses all parent repos.
|
* traverses all parent repos.
|
||||||
*/
|
*/
|
||||||
@ -469,11 +490,11 @@ checkout_one_file_at (OstreeRepo *repo,
|
|||||||
options,
|
options,
|
||||||
loose_path_buf,
|
loose_path_buf,
|
||||||
destination_dfd, destination_name,
|
destination_dfd, destination_name,
|
||||||
TRUE, &did_hardlink,
|
TRUE, &hardlink_res,
|
||||||
cancellable, error))
|
cancellable, error))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
if (did_hardlink && options->devino_to_csum_cache)
|
if (hardlink_res == HARDLINK_RESULT_LINKED && options->devino_to_csum_cache)
|
||||||
{
|
{
|
||||||
struct stat stbuf;
|
struct stat stbuf;
|
||||||
OstreeDevIno *key;
|
OstreeDevIno *key;
|
||||||
@ -492,13 +513,13 @@ checkout_one_file_at (OstreeRepo *repo,
|
|||||||
g_hash_table_add ((GHashTable*)options->devino_to_csum_cache, key);
|
g_hash_table_add ((GHashTable*)options->devino_to_csum_cache, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (did_hardlink)
|
if (hardlink_res != HARDLINK_RESULT_NOT_SUPPORTED)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current_repo = current_repo->parent_repo;
|
current_repo = current_repo->parent_repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
need_copy = !did_hardlink;
|
need_copy = (hardlink_res == HARDLINK_RESULT_NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
can_cache = (options->enable_uncompressed_cache
|
can_cache = (options->enable_uncompressed_cache
|
||||||
@ -514,7 +535,7 @@ checkout_one_file_at (OstreeRepo *repo,
|
|||||||
&& repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
|
&& repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
|
||||||
&& options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
|
&& options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
|
||||||
{
|
{
|
||||||
gboolean did_hardlink;
|
HardlinkResult hardlink_res = HARDLINK_RESULT_NOT_SUPPORTED;
|
||||||
|
|
||||||
if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL,
|
if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL,
|
||||||
cancellable, error))
|
cancellable, error))
|
||||||
@ -560,19 +581,20 @@ checkout_one_file_at (OstreeRepo *repo,
|
|||||||
|
|
||||||
if (!checkout_file_hardlink (repo, options, loose_path_buf,
|
if (!checkout_file_hardlink (repo, options, loose_path_buf,
|
||||||
destination_dfd, destination_name,
|
destination_dfd, destination_name,
|
||||||
FALSE, &did_hardlink,
|
FALSE, &hardlink_res,
|
||||||
cancellable, error))
|
cancellable, error))
|
||||||
{
|
{
|
||||||
g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name);
|
g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
need_copy = !did_hardlink;
|
need_copy = (hardlink_res == HARDLINK_RESULT_NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fall back to copy if we couldn't hardlink */
|
/* Fall back to copy if we couldn't hardlink */
|
||||||
if (need_copy)
|
if (need_copy)
|
||||||
{
|
{
|
||||||
|
g_assert (!options->no_copy_fallback);
|
||||||
if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
|
if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
|
||||||
cancellable, error))
|
cancellable, error))
|
||||||
goto out;
|
goto out;
|
||||||
@ -655,7 +677,9 @@ checkout_tree_at (OstreeRepo *self,
|
|||||||
while (G_UNLIKELY (res == -1 && errno == EINTR));
|
while (G_UNLIKELY (res == -1 && errno == EINTR));
|
||||||
if (res == -1)
|
if (res == -1)
|
||||||
{
|
{
|
||||||
if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
|
if (errno == EEXIST &&
|
||||||
|
(options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES
|
||||||
|
|| options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES))
|
||||||
did_exist = TRUE;
|
did_exist = TRUE;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -722,11 +722,13 @@ typedef enum {
|
|||||||
/**
|
/**
|
||||||
* OstreeRepoCheckoutOverwriteMode:
|
* OstreeRepoCheckoutOverwriteMode:
|
||||||
* @OSTREE_REPO_CHECKOUT_OVERWRITE_NONE: No special options
|
* @OSTREE_REPO_CHECKOUT_OVERWRITE_NONE: No special options
|
||||||
* @OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES: When layering checkouts, overwrite earlier files, but keep earlier directories
|
* @OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES: When layering checkouts, unlink() and replace existing files, but do not modify existing directories
|
||||||
|
* @OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES: Only add new files/directories
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
OSTREE_REPO_CHECKOUT_OVERWRITE_NONE = 0,
|
OSTREE_REPO_CHECKOUT_OVERWRITE_NONE = 0,
|
||||||
OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES = 1
|
OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES = 1,
|
||||||
|
OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES = 2, /* Since: 2017.3 */
|
||||||
} OstreeRepoCheckoutOverwriteMode;
|
} OstreeRepoCheckoutOverwriteMode;
|
||||||
|
|
||||||
_OSTREE_PUBLIC
|
_OSTREE_PUBLIC
|
||||||
|
@ -36,6 +36,7 @@ static gboolean opt_allow_noent;
|
|||||||
static gboolean opt_disable_cache;
|
static gboolean opt_disable_cache;
|
||||||
static char *opt_subpath;
|
static char *opt_subpath;
|
||||||
static gboolean opt_union;
|
static gboolean opt_union;
|
||||||
|
static gboolean opt_union_add;
|
||||||
static gboolean opt_whiteouts;
|
static gboolean opt_whiteouts;
|
||||||
static gboolean opt_from_stdin;
|
static gboolean opt_from_stdin;
|
||||||
static char *opt_from_file;
|
static char *opt_from_file;
|
||||||
@ -63,6 +64,7 @@ static GOptionEntry options[] = {
|
|||||||
{ "disable-cache", 0, 0, G_OPTION_ARG_NONE, &opt_disable_cache, "Do not update or use the internal repository uncompressed object cache", NULL },
|
{ "disable-cache", 0, 0, G_OPTION_ARG_NONE, &opt_disable_cache, "Do not update or use the internal repository uncompressed object cache", NULL },
|
||||||
{ "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Checkout sub-directory PATH", "PATH" },
|
{ "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Checkout sub-directory PATH", "PATH" },
|
||||||
{ "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL },
|
{ "union", 0, 0, G_OPTION_ARG_NONE, &opt_union, "Keep existing directories, overwrite existing files", NULL },
|
||||||
|
{ "union-add", 0, 0, G_OPTION_ARG_NONE, &opt_union_add, "Keep existing files/directories, only add new", NULL },
|
||||||
{ "whiteouts", 0, 0, G_OPTION_ARG_NONE, &opt_whiteouts, "Process 'whiteout' (Docker style) entries", NULL },
|
{ "whiteouts", 0, 0, G_OPTION_ARG_NONE, &opt_whiteouts, "Process 'whiteout' (Docker style) entries", NULL },
|
||||||
{ "allow-noent", 0, 0, G_OPTION_ARG_NONE, &opt_allow_noent, "Do nothing if specified path does not exist", NULL },
|
{ "allow-noent", 0, 0, G_OPTION_ARG_NONE, &opt_allow_noent, "Do nothing if specified path does not exist", NULL },
|
||||||
{ "from-stdin", 0, 0, G_OPTION_ARG_NONE, &opt_from_stdin, "Process many checkouts from standard input", NULL },
|
{ "from-stdin", 0, 0, G_OPTION_ARG_NONE, &opt_from_stdin, "Process many checkouts from standard input", NULL },
|
||||||
@ -87,14 +89,23 @@ process_one_checkout (OstreeRepo *repo,
|
|||||||
* `ostree_repo_checkout_at` until such time as we have a more
|
* `ostree_repo_checkout_at` until such time as we have a more
|
||||||
* convenient infrastructure for testing C APIs with data.
|
* convenient infrastructure for testing C APIs with data.
|
||||||
*/
|
*/
|
||||||
if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks)
|
if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks || opt_union_add)
|
||||||
{
|
{
|
||||||
OstreeRepoCheckoutAtOptions options = { 0, };
|
OstreeRepoCheckoutAtOptions options = { 0, };
|
||||||
|
|
||||||
if (opt_user_mode)
|
if (opt_user_mode)
|
||||||
options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
||||||
if (opt_union)
|
/* Can't union these */
|
||||||
|
if (opt_union && opt_union_add)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"Cannot specify both --union and --union-add");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else if (opt_union)
|
||||||
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
|
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
|
||||||
|
else if (opt_union_add)
|
||||||
|
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES;
|
||||||
if (opt_whiteouts)
|
if (opt_whiteouts)
|
||||||
options.process_whiteouts = TRUE;
|
options.process_whiteouts = TRUE;
|
||||||
if (subpath)
|
if (subpath)
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "1..62"
|
echo "1..63"
|
||||||
|
|
||||||
$CMD_PREFIX ostree --version > version.yaml
|
$CMD_PREFIX ostree --version > version.yaml
|
||||||
python -c 'import yaml; yaml.safe_load(open("version.yaml"))'
|
python -c 'import yaml; yaml.safe_load(open("version.yaml"))'
|
||||||
@ -279,6 +279,24 @@ cd checkout-test2-union
|
|||||||
assert_file_has_content ./yet/another/tree/green "leaf"
|
assert_file_has_content ./yet/another/tree/green "leaf"
|
||||||
echo "ok checkout union 1"
|
echo "ok checkout union 1"
|
||||||
|
|
||||||
|
cd ${test_tmpdir}
|
||||||
|
$OSTREE commit -b test-union-add --tree=ref=test2
|
||||||
|
$OSTREE checkout test-union-add checkout-test-union-add
|
||||||
|
echo 'file for union add testing' > checkout-test-union-add/union-add-test
|
||||||
|
echo 'another file for union add testing' > checkout-test-union-add/union-add-test2
|
||||||
|
$OSTREE commit -b test-union-add --tree=dir=checkout-test-union-add
|
||||||
|
rm checkout-test-union-add -rf
|
||||||
|
# Check out previous
|
||||||
|
$OSTREE checkout test-union-add^ checkout-test-union-add
|
||||||
|
assert_not_has_file checkout-test-union-add/union-add-test
|
||||||
|
assert_not_has_file checkout-test-union-add/union-add-test2
|
||||||
|
# Now create a file we don't want overwritten
|
||||||
|
echo 'existing file for union add' > checkout-test-union-add/union-add-test
|
||||||
|
$OSTREE checkout --union-add test-union-add checkout-test-union-add
|
||||||
|
assert_file_has_content checkout-test-union-add/union-add-test 'existing file for union add'
|
||||||
|
assert_file_has_content checkout-test-union-add/union-add-test2 'another file for union add testing'
|
||||||
|
echo "ok checkout union add"
|
||||||
|
|
||||||
cd ${test_tmpdir}
|
cd ${test_tmpdir}
|
||||||
rm -rf shadow-repo
|
rm -rf shadow-repo
|
||||||
mkdir shadow-repo
|
mkdir shadow-repo
|
||||||
|
Loading…
Reference in New Issue
Block a user