Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for "permissions-blindness" before calculating Artifacts git tree hashes #1573

Closed
wants to merge 2 commits into from
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
75 changes: 68 additions & 7 deletions src/Artifacts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ..GitTools
using ..BinaryPlatforms
import ..TOML
import ..Types: parse_toml, write_env_usage, printpkgstyle
import ...Pkg: pkg_server
import ...Pkg: pkg_server, safe_realpath
using ..PlatformEngines
using SHA

Expand Down Expand Up @@ -716,6 +716,65 @@ function unbind_artifact!(artifacts_toml::String, name::String;
return
end

_permissions_blind_filesystem_cache = Dict{String,Bool}()
"""
permissions_blind_filesystem(path::AbstractString)

Determine whether the given directory is a permissions-blind filesystem,
such as a FAT32 volume, or an NFS mount with permissions-modifications.
This method assumes that `path` is a directory and that we have write
permissions on the given `path`.
"""
function permissions_blind_filesystem(path::AbstractString)
global _permissions_blind_filesystem_cache

# Immediately normalize `path`
path = safe_realpath(path)
if !isdir(path)
error("Must provide a directory as `path`")
end

if haskey(_permissions_blind_filesystem_cache, path)
return _permissions_blind_filesystem_cache[path]
end

# Otherwise, check for the ability to give executable permissions
# as well as take them away:

try
blind = mktemp(path) do filepath, io
blind = false

# Set it as executable
chmod(filepath, 0o755)
blind |= Sys.isexecutable(filepath) != true

# Set it as non-executable
chmod(filepath, 0o644)
blind |= Sys.isexecutable(filepath) != false

# Set it back to executable, to be sure, just in case our default files are executable
chmod(filepath, 0o755)
blind |= Sys.isexecutable(filepath) != true

# cache this result
return blind
end

# Cache this result, then return it
_permissions_blind_filesystem_cache[path] = blind
return blind
catch e
if isa(e, InterruptException)
rethrow(e)
end

# In case we hit an error, default to permissions-blind, but don't cache it
# so we will try again in the future.
return true
end
end

"""
download_artifact(tree_hash::SHA1, tarball_url::String, tarball_hash::String;
verbose::Bool = false)
Expand All @@ -739,15 +798,17 @@ function download_artifact(
# Ensure that we're ready to download things
probe_platform_engines!()

if Sys.iswindows()
# Ensure the `artifacts` directory exists in our default depot
artifacts_dir = first(artifacts_dirs())
mkpath(artifacts_dir)
if permissions_blind_filesystem(artifacts_dir)
# The destination directory we're hoping to fill:
dest_dir = artifact_path(tree_hash; honor_overrides=false)

# On Windows, we have some issues around stat() and chmod() that make properly
# determining the git tree hash problematic; for this reason, we use the "unsafe"
# artifact unpacking method, which does not properly verify unpacked git tree
# hash. This will be fixed in a future Julia release which will properly interrogate
# the filesystem ACLs for executable permissions, which git tree hashes care about.
# On permissions-blind filesystems, we are unable to check `isexecutable()` properly.
# This makes determining the git tree hash problematic; for this reason, we use the "unsafe"
# artifact unpacking method, downloading and unpacking directly into the appropriately-named
# `artifacts/<hash>` directory, but not properly verifying the unpacked git tree hash.
try
download_verify_unpack(tarball_url, tarball_hash, dest_dir, ignore_existence=true,
verbose=verbose, quiet_download=quiet_download)
Expand Down
1 change: 1 addition & 0 deletions src/GitTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ function gitmode(path::AbstractString)
elseif isdir(path)
return mode_dir
# We cannot use `Sys.isexecutable()` because on Windows, that simply calls `isfile()`
# This will change in Julia 1.5.
elseif !iszero(filemode(path) & 0o100)
return mode_executable
else
Expand Down
35 changes: 17 additions & 18 deletions test/new.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2297,24 +2297,23 @@ tree_hash(root::AbstractString) = bytes2hex(Pkg.GitTools.tree_hash(root))

@testset "git tree hash computation" begin
mktempdir() do dir
# test "well known" empty tree hash
@test "4b825dc642cb6eb9a060e54bf8d69288fbee4904" == tree_hash(dir)
# create a text file
file = joinpath(dir, "hello.txt")
open(file, write=true) do io
println(io, "Hello, world.")
end
# reference hash generated with command-line git
@test "0a890bd10328d68f6d85efd2535e3a4c588ee8e6" == tree_hash(dir)
# test with various executable bits set
chmod(file, 0o645) # other x bit doesn't matter
@test "0a890bd10328d68f6d85efd2535e3a4c588ee8e6" == tree_hash(dir)
chmod(file, 0o654) # group x bit doesn't matter
@test "0a890bd10328d68f6d85efd2535e3a4c588ee8e6" == tree_hash(dir)
chmod(file, 0o744) # user x bit matters
if Sys.iswindows()
@test_broken "952cfce0fb589c02736482fa75f9f9bb492242f8" == tree_hash(dir)
else
# Skip git tree hashing on permissions-blind filesystems
if !Pkg.Artifacts.permissions_blind_filesystem(dir)
# test "well known" empty tree hash
@test "4b825dc642cb6eb9a060e54bf8d69288fbee4904" == tree_hash(dir)
# create a text file
file = joinpath(dir, "hello.txt")
open(file, write=true) do io
println(io, "Hello, world.")
end
# reference hash generated with command-line git
@test "0a890bd10328d68f6d85efd2535e3a4c588ee8e6" == tree_hash(dir)
# test with various executable bits set
chmod(file, 0o645) # other x bit doesn't matter
@test "0a890bd10328d68f6d85efd2535e3a4c588ee8e6" == tree_hash(dir)
chmod(file, 0o654) # group x bit doesn't matter
@test "0a890bd10328d68f6d85efd2535e3a4c588ee8e6" == tree_hash(dir)
chmod(file, 0o744) # user x bit matters
@test "952cfce0fb589c02736482fa75f9f9bb492242f8" == tree_hash(dir)
end
end
Expand Down