diff --git a/NEWS.md b/NEWS.md index 53cd1632c5aec..534f1e8b6887e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,7 @@ Build system changes New library functions --------------------- +* `hardlink(src, dst)` can be used to create hard links. ([#41639]) New library features -------------------- diff --git a/base/exports.jl b/base/exports.jl index 88933dad882ca..0a8e473055b4b 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -895,6 +895,7 @@ export filemode, filesize, gperm, + hardlink, isblockdev, ischardev, isdir, diff --git a/base/file.jl b/base/file.jl index 3a038863107bc..f9deb5b9fbe48 100644 --- a/base/file.jl +++ b/base/file.jl @@ -8,6 +8,7 @@ export chown, cp, cptree, + hardlink, mkdir, mkpath, mktemp, @@ -998,6 +999,26 @@ if Sys.iswindows() const UV__EPERM = -4048 end +""" + hardlink(src::AbstractString, dst::AbstractString) + +Creates a hard link to an existing source file `src` with the name `dst`. The +destination, `dst`, must not exist. + +See also: [`symlink`](@ref). + +!!! compat "Julia 1.8" + This method was added in Julia 1.8. +""" +function hardlink(src::AbstractString, dst::AbstractString) + err = ccall(:jl_fs_hardlink, Int32, (Cstring, Cstring), src, dst) + if err < 0 + msg = "hardlink($(repr(src)), $(repr(dst)))" + uv_error(msg, err) + end + return nothing +end + """ symlink(target::AbstractString, link::AbstractString; dir_target = false) @@ -1020,6 +1041,8 @@ a junction point will be used. Best practice for creating symlinks on Windows is to create them only after the files/directories they reference are already created. +See also: [`hardlink`](@ref). + !!! note This function raises an error under operating systems that do not support soft symbolic links, such as Windows XP. diff --git a/doc/src/base/file.md b/doc/src/base/file.md index 93b5be617ad4b..40d1cc2ca7ef0 100644 --- a/doc/src/base/file.md +++ b/doc/src/base/file.md @@ -8,6 +8,7 @@ Base.Filesystem.readdir Base.Filesystem.walkdir Base.Filesystem.mkdir Base.Filesystem.mkpath +Base.Filesystem.hardlink Base.Filesystem.symlink Base.Filesystem.readlink Base.Filesystem.chmod diff --git a/src/jl_uv.c b/src/jl_uv.c index 18733926e8c54..719d3bf9c6010 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -369,6 +369,14 @@ JL_DLLEXPORT int jl_fs_sendfile(uv_os_fd_t src_fd, uv_os_fd_t dst_fd, return ret; } +JL_DLLEXPORT int jl_fs_hardlink(char *path, char *new_path) +{ + uv_fs_t req; + int ret = uv_fs_link(unused_uv_loop_arg, &req, path, new_path, NULL); + uv_fs_req_cleanup(&req); + return ret; +} + JL_DLLEXPORT int jl_fs_symlink(char *path, char *new_path, int flags) { uv_fs_t req; diff --git a/test/file.jl b/test/file.jl index 6c48cedb0ebb0..57389a40025e7 100644 --- a/test/file.jl +++ b/test/file.jl @@ -63,17 +63,32 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER end if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER - link = joinpath(dir, "afilelink.txt") + link = joinpath(dir, "afilesymlink.txt") symlink(file, link) @test stat(file) == stat(link) # relative link - rellink = joinpath(subdir, "rel_afilelink.txt") + rellink = joinpath(subdir, "rel_afilesymlink.txt") relfile = joinpath("..", "afile.txt") symlink(relfile, rellink) @test stat(rellink) == stat(file) end +@testset "hardlink" begin + link = joinpath(dir, "afilehardlink.txt") + hardlink(file, link) + @test stat(file) == stat(link) + + # when the destination exists + @test_throws Base.IOError hardlink(file, link) + + rm(link) + + # the source file does not exist + missing_file = joinpath(dir, "for-sure-missing-file.txt") + @test_throws Base.IOError hardlink(missing_file, link) +end + using Random @testset "that temp names are actually unique" begin