Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/build/adapter_filesystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ auto BuildAdapterFilesystem::write_dependencies(
this->refresh(path);
const auto deps_path{this->dependencies_path(path)};
std::filesystem::create_directories(deps_path.parent_path());
// To reset the inode and correctly handle hard links
std::filesystem::remove(deps_path);
std::ofstream deps_stream{deps_path};
assert(!deps_stream.fail());
for (const auto &dependency : dependencies) {
Expand Down
2 changes: 2 additions & 0 deletions src/index/generators.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ struct GENERATE_URITEMPLATE_ROUTES {
const sourcemeta::one::BuildDynamicCallback<std::filesystem::path> &,
const Context &router) -> void {
std::filesystem::create_directories(destination.parent_path());
// To reset the inode and correctly handle hard links
std::filesystem::remove(destination);
sourcemeta::core::URITemplateRouterView::save(router, destination);
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/index/output.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class Output {
-> const std::filesystem::path & {
assert(path.is_absolute());
std::filesystem::create_directories(path.parent_path());
// To reset the inode and correctly handle hard links
std::filesystem::remove(path);
std::ofstream stream{path};
assert(!stream.fail());
sourcemeta::core::stringify(document, stream);
Expand Down
2 changes: 2 additions & 0 deletions src/shared/metapack.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ auto write_stream(const std::filesystem::path &path,
metadata.assign("extension", extension);
}

// To reset the inode and correctly handle hard links
std::filesystem::remove(path);
std::ofstream output{path};
assert(!output.fail());
sourcemeta::core::stringify(metadata, output);
Expand Down
1 change: 1 addition & 0 deletions test/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ if(ONE_INDEX)
sourcemeta_one_test_cli(common index non-existent-collection-directory)
sourcemeta_one_test_cli(common index collection-path-is-file)
sourcemeta_one_test_cli(common index deps-contents)
sourcemeta_one_test_cli(common index inode-reset-on-rebuild)

if(ONE_ENTERPRISE)
sourcemeta_one_test_cli(enterprise index no-options)
Expand Down
86 changes: 86 additions & 0 deletions test/cli/index/common/inode-reset-on-rebuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << EOF > "$TMP/one.json"
{
"url": "https://sourcemeta1.com/",
"contents": {
"schemas": {
"baseUri": "https://example.com/",
"path": "./schemas"
}
}
}
EOF

mkdir "$TMP/schemas"

cat << 'EOF' > "$TMP/schemas/test.json"
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/test"
}
EOF

collect_inodes() {
find "$1" -type f -exec ls -i {} \; \
| awk '{print $2, $1}' | sort > "$2"
# Confirm the file is not empty
test "$(wc -l < "$2" | tr -d ' ')" -gt 0
# For debugging
cat "$2" 1>&2
}

"$1" "$TMP/one.json" "$TMP/output"
collect_inodes "$TMP/output" "$TMP/inodes_before.txt"

# Pin the original inodes by hard-linking every file into a mirror
# directory. This prevents the filesystem from reusing freed inode
# numbers when the indexer removes and recreates files.
cd "$TMP/output"
find . -type d | while read -r directory
do
mkdir -p "$TMP/mirror/$directory"
done
find . -type f | while read -r file
do
ln "$file" "$TMP/mirror/$file"
done
cd -

# Change the registry URL and modify the schema to force all targets to rebuild
cat << EOF > "$TMP/one.json"
{
"url": "https://sourcemeta2.com/",
"contents": {
"schemas": {
"baseUri": "https://example.com/",
"path": "./schemas"
}
}
}
EOF

cat << 'EOF' > "$TMP/schemas/test.json"
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/test",
"type": "string"
}
EOF

"$1" "$TMP/one.json" "$TMP/output"
collect_inodes "$TMP/output" "$TMP/inodes_after.txt"

# Exactly 1 shared inode is expected: the version mark. All other files
# must have been removed and recreated with fresh inodes.
SHARED="$(grep --fixed-strings --line-regexp \
--file="$TMP/inodes_before.txt" "$TMP/inodes_after.txt" || true)"
test "$(echo "$SHARED" | wc -l | tr -d ' ')" = "1"
echo "$SHARED" | grep --quiet "version.json"