Skip to content

Commit c079d8b

Browse files
committed
Store sym links as the referenced file by default
1 parent a1be755 commit c079d8b

File tree

2 files changed

+95
-38
lines changed

2 files changed

+95
-38
lines changed

os/src/ZipOps.scala

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package os
33
import os.{shaded_org_apache_tools_zip => apache}
44

55
import java.net.URI
6-
import java.nio.file.{FileSystem, FileSystems, Files}
7-
import java.nio.file.attribute.{BasicFileAttributeView, FileTime, PosixFilePermissions}
6+
import java.nio.file.{FileSystem, FileSystems, Files, LinkOption}
7+
import java.nio.file.attribute.{
8+
BasicFileAttributes,
9+
BasicFileAttributeView,
10+
FileTime,
11+
PosixFilePermissions
12+
}
813
import java.util.zip.{ZipEntry, ZipFile, ZipInputStream, ZipOutputStream}
914
import scala.collection.JavaConverters._
1015
import scala.util.matching.Regex
@@ -45,7 +50,8 @@ object zip {
4550
includePatterns: Seq[Regex] = List(),
4651
preserveMtimes: Boolean = false,
4752
deletePatterns: Seq[Regex] = List(),
48-
compressionLevel: Int = java.util.zip.Deflater.DEFAULT_COMPRESSION
53+
compressionLevel: Int = java.util.zip.Deflater.DEFAULT_COMPRESSION,
54+
preserveLinks: Boolean = false
4955
): os.Path = {
5056
checker.value.onWrite(dest)
5157
// check read preemptively in case "dest" is created
@@ -84,6 +90,7 @@ object zip {
8490
includePatterns,
8591
preserveMtimes,
8692
compressionLevel,
93+
preserveLinks,
8794
f
8895
)
8996
finally f.close()
@@ -115,6 +122,7 @@ object zip {
115122
includePatterns: Seq[Regex],
116123
preserveMtimes: Boolean,
117124
compressionLevel: Int,
125+
preserveLinks: Boolean,
118126
out: java.io.OutputStream
119127
): Unit = {
120128
val zipOut = new apache.ZipOutputStream(out)
@@ -125,7 +133,7 @@ object zip {
125133
sources,
126134
excludePatterns,
127135
includePatterns,
128-
(path, sub) => makeZipEntry(path, sub, preserveMtimes, zipOut)
136+
(path, sub) => makeZipEntry(path, sub, preserveMtimes, preserveLinks, zipOut)
129137
)
130138
zipOut.finish()
131139
} finally {
@@ -146,17 +154,25 @@ object zip {
146154
!isExcluded && isIncluded
147155
}
148156

149-
private def toFileType(file: os.Path): apache.PermissionUtils.FileType = {
150-
if (os.isLink(file)) apache.PermissionUtils.FileType.SYMLINK
151-
else if (os.isFile(file)) apache.PermissionUtils.FileType.REGULAR_FILE
152-
else if (os.isDir(file)) apache.PermissionUtils.FileType.DIR
157+
private def toFileType(
158+
file: os.Path,
159+
followLinks: Boolean = false
160+
): apache.PermissionUtils.FileType = {
161+
val attrs = if (followLinks)
162+
Files.readAttributes(file.toNIO, classOf[BasicFileAttributes])
163+
else Files.readAttributes(file.toNIO, classOf[BasicFileAttributes], LinkOption.NOFOLLOW_LINKS)
164+
165+
if (attrs.isSymbolicLink()) apache.PermissionUtils.FileType.SYMLINK
166+
else if (attrs.isRegularFile()) apache.PermissionUtils.FileType.REGULAR_FILE
167+
else if (attrs.isDirectory()) apache.PermissionUtils.FileType.DIR
153168
else apache.PermissionUtils.FileType.OTHER
154169
}
155170

156171
private def makeZipEntry(
157172
file: os.Path,
158173
sub: os.SubPath,
159174
preserveMtimes: Boolean,
175+
preserveLinks: Boolean,
160176
zipOut: apache.ZipOutputStream
161177
) = {
162178
val zipEntry = new apache.ZipEntry(sub.toString)
@@ -166,14 +182,14 @@ object zip {
166182

167183
if (!scala.util.Properties.isWin) {
168184
val mode = apache.PermissionUtils.modeFromPermissions(
169-
os.perms(file, followLinks = false).toSet(),
170-
toFileType(file)
185+
os.perms(file, followLinks = !preserveLinks).toSet(),
186+
toFileType(file, followLinks = !preserveLinks)
171187
)
172188
zipEntry.setUnixMode(mode)
173189
}
174190

175191
val fis =
176-
if (!scala.util.Properties.isWin && os.isLink(file))
192+
if (preserveLinks && !scala.util.Properties.isWin && os.isLink(file))
177193
Some(new java.io.ByteArrayInputStream(os.readLink(file).toString().getBytes()))
178194
else if (os.isFile(file)) Some(os.read.inputStream(file))
179195
else None
@@ -201,7 +217,8 @@ object zip {
201217
excludePatterns: Seq[Regex] = List(),
202218
includePatterns: Seq[Regex] = List(),
203219
preserveMtimes: Boolean = false,
204-
compressionLevel: Int = java.util.zip.Deflater.DEFAULT_COMPRESSION
220+
compressionLevel: Int = java.util.zip.Deflater.DEFAULT_COMPRESSION,
221+
preserveLinks: Boolean = false
205222
): geny.Writable = {
206223
(outputStream: java.io.OutputStream) =>
207224
{
@@ -211,6 +228,7 @@ object zip {
211228
includePatterns,
212229
preserveMtimes,
213230
compressionLevel,
231+
preserveLinks,
214232
outputStream
215233
)
216234
}

os/test/src/ZipOpTests.scala

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -191,26 +191,29 @@ object ZipOpTests extends TestSuite {
191191
def prepare(
192192
wd: os.Path,
193193
zipStream: Boolean = false,
194-
unzipStream: Boolean = false
194+
unzipStream: Boolean = false,
195+
preserveLinks: Boolean = false
195196
) = {
196197
val zipFileName = "zipped.zip"
197198
val source = wd / "folder2"
199+
val link = os.rel / "nestedA" / "link.txt"
198200
if (!scala.util.Properties.isWin) {
199201
os.perms.set(source / "nestedA" / "a.txt", os.PermSet.fromString("rw-rw-rw-"))
200-
os.symlink(source / "nestedA" / "link.txt", os.rel / "a.txt")
202+
os.symlink(source / link, os.rel / "a.txt")
201203
}
202204

203205
val zipped =
204206
if (zipStream) {
205207
os.write(
206208
wd / zipFileName,
207-
os.zip.stream(sources = List(source))
209+
os.zip.stream(sources = List(source), preserveLinks = preserveLinks)
208210
)
209211
wd / zipFileName
210212
} else {
211213
os.zip(
212214
dest = wd / zipFileName,
213-
sources = List(source)
215+
sources = List(source),
216+
preserveLinks = preserveLinks
214217
)
215218
}
216219

@@ -228,56 +231,92 @@ object ZipOpTests extends TestSuite {
228231
)
229232
}
230233

231-
(source, unzipped)
234+
(source, unzipped, link)
232235
}
233236

234237
def walkRel(p: os.Path) = os.walk(p).map(_.relativeTo(p))
235238

236239
test("zip") - prep { wd =>
237240
if (!scala.util.Properties.isWin) {
238-
val (source, unzipped) = prepare(wd)
241+
val (source, unzipped, link) = prepare(wd, preserveLinks = false)
239242

240243
assert(walkRel(source).toSet == walkRel(unzipped).toSet)
241244
assert(os.walk.stream(source)
242245
.filter(!os.isLink(_))
243246
.forall(p => os.perms(p) == os.perms(unzipped / p.relativeTo(source))))
244-
assert(
245-
os.walk.stream(source)
246-
.filter(os.isLink(_))
247-
.forall { p =>
248-
val unzippedLink = unzipped / p.relativeTo(source)
249-
os.isLink(unzippedLink) &&
250-
os.readLink(p) == os.readLink(unzippedLink)
251-
}
252-
)
247+
248+
val unzippedLink = unzipped / link
249+
assert(os.isFile(unzippedLink))
250+
assert(os.read(os.readLink.absolute(source / link)) == os.read(unzippedLink))
251+
}
252+
}
253+
254+
test("zipPreserveLinks") - prep { wd =>
255+
if (!scala.util.Properties.isWin) {
256+
val (source, unzipped, link) = prepare(wd, preserveLinks = true)
257+
258+
assert(walkRel(source).toSet == walkRel(unzipped).toSet)
259+
assert(os.walk.stream(source)
260+
.filter(!os.isLink(_))
261+
.forall(p => os.perms(p) == os.perms(unzipped / p.relativeTo(source))))
262+
263+
val unzippedLink = unzipped / link
264+
assert(os.isLink(unzippedLink))
265+
assert(os.readLink(source / link) == os.readLink(unzippedLink))
253266
}
254267
}
255268

256269
test("zipStream") - prep { wd =>
257270
if (!scala.util.Properties.isWin) {
258-
val (source, unzipped) = prepare(wd, zipStream = true)
271+
val (source, unzipped, link) = prepare(wd, zipStream = true, preserveLinks = false)
272+
273+
assert(walkRel(source).toSet == walkRel(unzipped).toSet)
274+
assert(os.walk.stream(source)
275+
.filter(!os.isLink(_))
276+
.forall(p => os.perms(p) == os.perms(unzipped / p.relativeTo(source))))
277+
278+
val unzippedLink = unzipped / link
279+
assert(os.isFile(unzippedLink))
280+
assert(os.read(os.readLink.absolute(source / link)) == os.read(unzippedLink))
281+
}
282+
}
283+
284+
test("zipStreamPreserveLinks") - prep { wd =>
285+
if (!scala.util.Properties.isWin) {
286+
val (source, unzipped, link) = prepare(wd, zipStream = true, preserveLinks = true)
259287

260288
assert(walkRel(source).toSet == walkRel(unzipped).toSet)
261289
assert(os.walk.stream(source)
262290
.filter(!os.isLink(_))
263291
.forall(p => os.perms(p) == os.perms(unzipped / p.relativeTo(source))))
264-
assert(
265-
os.walk.stream(source)
266-
.filter(os.isLink(_))
267-
.forall { p =>
268-
val unzippedLink = unzipped / p.relativeTo(source)
269-
os.isLink(unzippedLink) &&
270-
os.readLink(p) == os.readLink(unzippedLink)
271-
}
272-
)
292+
293+
val unzippedLink = unzipped / link
294+
assert(os.isLink(unzippedLink))
295+
assert(os.readLink(source / link) == os.readLink(unzippedLink))
296+
}
297+
}
298+
299+
test("unzipStreamWithLinks") - prep { wd =>
300+
if (!scala.util.Properties.isWin) {
301+
val (source, unzipped, link) = prepare(wd, unzipStream = true, preserveLinks = true)
302+
303+
assert(walkRel(source).toSet == walkRel(unzipped).toSet)
304+
305+
val unzippedLink = unzipped / link
306+
assert(os.isFile(unzippedLink))
307+
assert(os.readLink(source / link).toString == os.read(unzippedLink))
273308
}
274309
}
275310

276311
test("unzipStream") - prep { wd =>
277312
if (!scala.util.Properties.isWin) {
278-
val (source, unzipped) = prepare(wd, unzipStream = true)
313+
val (source, unzipped, link) = prepare(wd, unzipStream = true, preserveLinks = false)
279314

280315
assert(walkRel(source).toSet == walkRel(unzipped).toSet)
316+
317+
val unzippedLink = unzipped / link
318+
assert(os.isFile(unzippedLink))
319+
assert(os.read(os.readLink.absolute(source / link)) == os.read(unzippedLink))
281320
}
282321
}
283322
}

0 commit comments

Comments
 (0)