Skip to content

Commit 6894798

Browse files
authored
Fix symlink behavior (#343)
1 parent ef87eaf commit 6894798

File tree

5 files changed

+79
-16
lines changed

5 files changed

+79
-16
lines changed

tests/base.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def check_strings(self, command, output, expected_present, expected_absent):
186186
print_in_box(error_message)
187187
self.stop(error_message)
188188

189-
def setupDirs(self, test_name):
189+
def setupDirs(self, test_name, follow_symlinks=False):
190190
"""
191191
Set up directories for testing.
192192
"""
@@ -223,7 +223,9 @@ def setupDirs(self, test_name):
223223
os.symlink("file0.txt", "{}/file0_soft.txt".format(self.test_dir))
224224

225225
# Bad symbolic (soft) link (points to a file name which points to an inode)
226-
if not os.path.lexists("{}/file0_soft_bad.txt".format(self.test_dir)):
226+
if (not follow_symlinks) and (
227+
not os.path.lexists("{}/file0_soft_bad.txt".format(self.test_dir))
228+
):
227229
# Create symbolic link pointing to test_dir/file0_that_doesnt_exist.txt
228230
# named test_dir/file0_soft_bad.txt
229231
os.symlink(
@@ -250,6 +252,7 @@ def create(
250252
use_hpss,
251253
zstash_path,
252254
keep=False,
255+
follow_symlinks=False,
253256
cache=None,
254257
verbose=False,
255258
no_tars_md5=False,
@@ -268,11 +271,16 @@ def create(
268271
cache_option = " --cache={}".format(cache)
269272
else:
270273
cache_option = ""
274+
if follow_symlinks:
275+
follow_symlinks_option = " --follow-symlinks"
276+
else:
277+
follow_symlinks_option = ""
271278
v_option = " -v" if verbose else ""
272279
no_tars_md5_option = " --no_tars_md5" if no_tars_md5 else ""
273-
cmd = "{}zstash create{}{}{}{} --hpss={} {}".format(
280+
cmd = "{}zstash create{}{}{}{}{} --hpss={} {}".format(
274281
zstash_path,
275282
keep_option,
283+
follow_symlinks_option,
276284
cache_option,
277285
v_option,
278286
no_tars_md5_option,

tests/scripts/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Scripts for debugging / manually testing zstash

tests/scripts/symlinks.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Test symlinks
2+
# Adjusted from https://github.com/E3SM-Project/zstash/issues/341
3+
4+
follow_symlinks=true
5+
6+
rm -rf workdir workdir2 workdir3
7+
mkdir workdir workdir2 workdir3
8+
cd workdir
9+
mkdir -p src/d1 src/d2
10+
touch src/d1/large_file.txt
11+
12+
# This creates a symlink in d2 that links to a file in d1
13+
# Notice absolute path is used for source
14+
ln -s /home/ac.forsyth2/ez/zstash/tests/scripts/workdir/src/d1/large_file.txt src/d2/large_file.txt
15+
16+
echo ""
17+
echo "ls -l src/d2"
18+
ls -l src/d2
19+
# symlink
20+
21+
echo ""
22+
if [[ "${follow_symlinks,,}" == "true" ]]; then
23+
echo "zstash create --hpss=none --follow-symlinks --cache /home/ac.forsyth2/ez/zstash/tests/scripts/workdir2 src/d2"
24+
zstash create --hpss=none --follow-symlinks --cache /home/ac.forsyth2/ez/zstash/tests/scripts/workdir2 src/d2
25+
else
26+
echo "zstash create --hpss=none --cache /home/ac.forsyth2/ez/zstash/tests/scripts/workdir2 src/d2"
27+
zstash create --hpss=none --cache /home/ac.forsyth2/ez/zstash/tests/scripts/workdir2 src/d2
28+
fi
29+
30+
echo ""
31+
echo "ls -l src/d2"
32+
ls -l src/d2
33+
# symlink (src is unaffected)
34+
35+
cd ../workdir3
36+
echo ""
37+
echo "zstash extract --hpss=none --cache /home/ac.forsyth2/ez/zstash/tests/scripts/workdir2"
38+
zstash extract --hpss=none --cache /home/ac.forsyth2/ez/zstash/tests/scripts/workdir2
39+
40+
cd ..
41+
echo ""
42+
echo "ls workdir3"
43+
ls workdir3
44+
# large_file.txt

tests/test_create.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ class TestCreate(TestZstash):
1919
"""
2020

2121
# x = on, no mark = off, b = both on and off tested
22-
# option | CreateVerbose | CreateIncludeDir | CreateIncludeFile | CreateExcludeDir | CreateExcludeFile | CreateKeep | CreateCache | TestZstash.create (used in multiple tests) | TestCheckParallel.testKeepTarsWithPreviouslySetHPSS |
23-
# --exclude | | | |x|x| | | | |
24-
# --include | |x|x| | | | | | |
25-
# --maxsize | | | | | | | | |x|
26-
# --keep | | | | | |x| |b| |
27-
# --cache | | | | | | |x| | |
28-
# -v |x| | | | | | | | |
22+
# option | CreateVerbose | CreateIncludeDir | CreateIncludeFile | CreateExcludeDir | CreateExcludeFile | CreateKeep | CreateCache | CreateFollowSymlinks | TestZstash.create (used in multiple tests) | TestCheckParallel.testKeepTarsWithPreviouslySetHPSS |
23+
# --exclude | | | |x|x| | | | | |
24+
# --follow-symlinks | | | | | | | |x| | |
25+
# --include | |x|x| | | | | | | |
26+
# --maxsize | | | | | | | | | |x|
27+
# --keep | | | | | |x| | |b| |
28+
# --cache | | | | | | |x|x| | |
29+
# -v |x| | | | | | | | | |
2930

3031
def helperCreateVerbose(self, test_name, hpss_path: str, zstash_path=ZSTASH_PATH):
3132
"""
@@ -209,6 +210,17 @@ def helperCreateCache(self, test_name, hpss_path, zstash_path=ZSTASH_PATH):
209210
)
210211
self.stop(error_message)
211212

213+
def helperCreateFollowSymlinks(self, test_name, zstash_path=ZSTASH_PATH):
214+
"""
215+
Test `zstash create --hpss=none --follow-symlinks --cache=my_cache`
216+
"""
217+
self.hpss_path = "none"
218+
self.cache = "my_cache"
219+
use_hpss = self.setupDirs(test_name, follow_symlinks=True)
220+
self.create(use_hpss, zstash_path, follow_symlinks=True, cache=self.cache)
221+
# Test that the link in the src directory remains a link (i.e., is not a copied file)
222+
self.assertTrue(os.path.islink(f"{self.test_dir}/file0_soft.txt"))
223+
212224
def testCreateVerbose(self):
213225
self.helperCreateVerbose("testCreateVerbose", "none")
214226

@@ -252,6 +264,9 @@ def testCreateCacheHPSS(self):
252264
self.conditional_hpss_skip()
253265
self.helperCreateCache("testCreateCacheHPSS", HPSS_ARCHIVE)
254266

267+
def testCreateFollowSymlinks(self):
268+
self.helperCreateFollowSymlinks("testCreateFollowSymlinks")
269+
255270

256271
if __name__ == "__main__":
257272
unittest.main()

zstash/hpss_utils.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import hashlib
44
import os
55
import os.path
6-
import shutil
76
import sqlite3
87
import tarfile
98
import traceback
@@ -98,7 +97,7 @@ def add_files(
9897
do_hash = False
9998
tarFileObject = HashIO(os.path.join(cache, tfname), "wb", do_hash)
10099
# FIXME: error: Argument "fileobj" to "open" has incompatible type "HashIO"; expected "Optional[IO[bytes]]"
101-
tar = tarfile.open(mode="w", fileobj=tarFileObject) # type: ignore
100+
tar = tarfile.open(mode="w", fileobj=tarFileObject, dereference=follow_symlinks) # type: ignore
102101

103102
# Add current file to tar archive
104103
current_file: str = files[i]
@@ -179,10 +178,6 @@ def add_file(
179178

180179
# FIXME: error: "TarFile" has no attribute "offset"
181180
offset: int = tar.offset # type: ignore
182-
if follow_symlinks and os.path.islink(file_name):
183-
linked_file_name = os.path.realpath(file_name)
184-
os.remove(file_name) # Remove symbolic link and create a hard copy
185-
shutil.copy(linked_file_name, file_name)
186181
tarinfo: tarfile.TarInfo = tar.gettarinfo(file_name)
187182
# Change the size of any hardlinks from 0 to the size of the actual file
188183
if tarinfo.islnk():

0 commit comments

Comments
 (0)