Skip to content

Commit 31d3466

Browse files
cmortyjjohnstn
authored andcommitted
Bug 566705 Docker: readContainerDirectory: Fix reading symlinks
"ls -L" reads info of linked file, not the link itself -> Links are never marked as such -> Removed The link target is now taken from ls and not normalized. The previous solution also broke when links contained absolute paths. Normalizing links also broke the PLA as, for example, extracting a Tar also does not normalize symlinks. File names and links with blanks in their names are supported now by tokenizing properly. ContainerFileProxy is updated to return the actual link via getLink() and to return the actual target via getTarget() CopyFromContainerCommandHandler is updated to create symbolic links for relative links if copying a folder, otherwise, for individual files and absolute links, the link is followed and the file/dir is copied. Up to 10 links are followed to avoid a possible circular link situation. ContainerLauncher copy from container logic is updated similarly. Fix API issue in DockerException caused by removal of getMessage(). Change-Id: Ie740e30ff1fbbf170e46d54eab134a82dc36c38b Reviewed-on: https://git.eclipse.org/r/c/linuxtools/org.eclipse.linuxtools/+/173149 Tested-by: Linux Tools Bot <[email protected]> Tested-by: Jeff Johnston <[email protected]> Reviewed-by: Jeff Johnston <[email protected]> Reviewed-on: https://git.eclipse.org/r/c/linuxtools/org.eclipse.linuxtools/+/168426
1 parent d41e5f4 commit 31d3466

File tree

6 files changed

+276
-126
lines changed

6 files changed

+276
-126
lines changed

containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/docker/core/DockerException.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Copyright (c) 2014, 2020 Red Hat and others.
3-
*
3+
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
66
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -33,6 +33,11 @@ public DockerException(final Throwable cause) {
3333
super(calculateMessage(null, cause), cause);
3434
}
3535

36+
@Override
37+
public String getMessage() {
38+
return super.getMessage();
39+
}
40+
3641
static private String calculateMessage(final String message, final Throwable cause) {
3742
Throwable dre = cause;
3843
// Search for DockerRequestException

containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/ContainerFileProxy.java

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
2-
* Copyright (c) 2016, 2018 Red Hat.
3-
*
2+
* Copyright (c) 2016, 2020 Red Hat.
3+
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
66
* which is available at https://www.eclipse.org/legal/epl-2.0/
@@ -12,11 +12,14 @@
1212
*******************************************************************************/
1313
package org.eclipse.linuxtools.internal.docker.core;
1414

15+
import org.eclipse.core.runtime.Path;
16+
1517
public class ContainerFileProxy {
1618

1719
private final String path;
1820
private final String name;
1921
private final String link;
22+
private final String target;
2023
private final boolean isFolder;
2124
private final boolean isLink;
2225

@@ -26,7 +29,8 @@ public ContainerFileProxy(String directory, String name,
2629
this.name = name;
2730
this.isFolder = isFolder;
2831
this.isLink = false;
29-
this.link = this.path;
32+
this.link = null;
33+
this.target = this.path;
3034
}
3135

3236
public ContainerFileProxy(String directory, String name, boolean isFolder,
@@ -35,7 +39,10 @@ public ContainerFileProxy(String directory, String name, boolean isFolder,
3539
this.name = name;
3640
this.isFolder = isFolder;
3741
this.isLink = isLink;
38-
this.link = (link == null ? this.path : link);
42+
this.link = link;
43+
this.target = (link.startsWith("/") ? link
44+
: new Path(this.path).removeLastSegments(1).append(link)
45+
.toPortableString());
3946
}
4047

4148
public String getFullPath() {
@@ -60,6 +67,10 @@ public String getLink() {
6067
return link;
6168
}
6269

70+
public String getTarget() {
71+
return target;
72+
}
73+
6374
public String getName() {
6475
return name;
6576
}

containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/internal/docker/core/DockerConnection.java

+31-17
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@
3535
import java.util.Map.Entry;
3636
import java.util.Set;
3737
import java.util.concurrent.CopyOnWriteArrayList;
38+
import java.util.regex.Matcher;
39+
import java.util.regex.Pattern;
3840

3941
import javax.ws.rs.ProcessingException;
4042

4143
import org.eclipse.core.runtime.IPath;
4244
import org.eclipse.core.runtime.IProgressMonitor;
4345
import org.eclipse.core.runtime.IStatus;
4446
import org.eclipse.core.runtime.ListenerList;
45-
import org.eclipse.core.runtime.Path;
4647
import org.eclipse.core.runtime.Status;
4748
import org.eclipse.core.runtime.jobs.Job;
4849
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -396,10 +397,9 @@ public void ping() throws DockerException {
396397
}
397398

398399
@Override
399-
protected void finalize() throws Throwable {
400+
protected void finalize() {
400401
this.finalizing = true;
401402
close();
402-
super.finalize();
403403
}
404404

405405
@Override
@@ -2263,7 +2263,7 @@ public List<ContainerFileProxy> readContainerDirectory(final String id,
22632263
try {
22642264
DockerClient copyClient = getClientCopy();
22652265
final ExecCreation execCreation = copyClient.execCreate(id,
2266-
new String[] { "/bin/sh", "-c", "ls -l -F -L -Q " + path }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
2266+
new String[] { "/bin/sh", "-c", "ls -l -F -Q " + path }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
22672267
ExecCreateParam.attachStdout(),
22682268
ExecCreateParam.attachStderr());
22692269
final String execId = execCreation.id();
@@ -2306,33 +2306,47 @@ public List<ContainerFileProxy> readContainerDirectory(final String id,
23062306
return childList;
23072307
}
23082308

2309+
private String[] tokenize(String line) {
2310+
String regex = "(\"[^\"]*\"/?)|(\\S+)";
2311+
List<String> result = new ArrayList<>();
2312+
2313+
Matcher m = Pattern.compile(regex).matcher(line);
2314+
while (m.find()) {
2315+
if (m.group(1) != null) {
2316+
result.add(m.group(1));
2317+
} else {
2318+
result.add(m.group(2));
2319+
}
2320+
}
2321+
return result.toArray(new String[0]);
2322+
}
2323+
23092324
private void processDirectoryLine(String line, String path,
23102325
List<ContainerFileProxy> childList) {
23112326
if (line.trim().startsWith("total")) //$NON-NLS-1$
23122327
return; // ignore the total line
2313-
String[] token = line.split("\\s+"); //$NON-NLS-1$
2328+
String[] token = tokenize(line);
23142329
boolean isDirectory = token[0].startsWith("d"); //$NON-NLS-1$
23152330
boolean isLink = token[0].startsWith("l"); //$NON-NLS-1$
23162331
if (token.length > 8) {
2317-
// last token depends on whether we have a link or not
2318-
String link = null;
2332+
// Non-Link ends with "filename"
2333+
// Link ends with "filename" -> "Target"
2334+
String name = token[token.length - (isLink ? 3 : 1)];
2335+
// remove quotes and any indicator char
2336+
name = name.substring(1,
2337+
name.length() - (name.endsWith("\"") ? 1 : 2));
2338+
23192339
if (isLink) {
23202340
String linkname = token[token.length - 1];
23212341
if (linkname.endsWith("/")) { //$NON-NLS-1$
2322-
linkname = linkname.substring(0, linkname.length() - 1);
23232342
isDirectory = true;
23242343
}
2325-
IPath linkPath = new Path(path);
2326-
linkPath = linkPath.append(linkname);
2327-
link = linkPath.toString();
2328-
String name = token[token.length - 3];
2344+
linkname = linkname.substring(1,
2345+
linkname.length() - (linkname.endsWith("\"") ? 1 : 2));
2346+
23292347
childList.add(new ContainerFileProxy(path, name, isDirectory,
2330-
isLink, link));
2348+
isLink, linkname));
23312349
} else {
2332-
String name = token[token.length - 1];
2333-
// remove quotes and any indicator char
2334-
name = name.substring(1,
2335-
name.length() - (name.endsWith("\"") ? 1 : 2));
23362350
childList.add(new ContainerFileProxy(path, name, isDirectory));
23372351
}
23382352
}

containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/docker/ui/launch/ContainerLauncher.java

+102-44
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,28 @@ protected IStatus run(final IProgressMonitor monitor) {
451451
path = path.append(te.getName());
452452
File f = new File(path.toOSString());
453453
int mode = te.getMode();
454+
if (te.isSymbolicLink()) {
455+
IPath linkPath = new Path(te.getLinkName());
456+
if (!linkPath.isAbsolute()) {
457+
if (!isWin) {
458+
java.nio.file.Path p = Paths.get(path.toPortableString());
459+
java.nio.file.Path link = Paths.get(te.getLinkName());
460+
if (f.exists()) {
461+
f.delete();
462+
}
463+
Files.createSymbolicLink(p, link);
464+
} else {
465+
linkPath = new Path(volume).append(te.getLinkName());
466+
links.put(path.toPortableString(), linkPath.toPortableString());
467+
}
468+
} else {
469+
links.put(path.toPortableString(), linkPath.toPortableString());
470+
}
471+
continue;
472+
}
454473
if (te.isDirectory()) {
455474
f.mkdir();
456-
if (!isWin && !te.isSymbolicLink()) {
475+
if (!isWin) {
457476
Files.setPosixFilePermissions(Paths.get(path.toOSString()), toPerms(mode));
458477
}
459478
continue;
@@ -462,30 +481,22 @@ protected IStatus run(final IProgressMonitor monitor) {
462481
continue;
463482
}
464483
f.createNewFile();
465-
if (!isWin && !te.isSymbolicLink()) {
484+
if (!isWin) {
466485
Files.setPosixFilePermissions(Paths.get(path.toOSString()), toPerms(mode));
467486
}
468487
}
469488
try (FileOutputStream os = new FileOutputStream(f)) {
470489
int bufferSize = ((int) size > 4096 ? 4096 : (int) size);
471490
byte[] barray = new byte[bufferSize];
472491
int result = -1;
473-
if (size == 0 && te.isSymbolicLink()) {
474-
IPath linkPath = new Path(te.getLinkName());
475-
if (!linkPath.isAbsolute()) {
476-
linkPath = new Path(volume).append(te.getLinkName());
477-
}
478-
links.put(te.getName(), linkPath.toPortableString());
479-
} else {
480-
while ((result = k.read(barray, 0, bufferSize)) > -1) {
481-
if (monitor.isCanceled()) {
482-
monitor.done();
483-
k.close();
484-
os.close();
485-
return Status.CANCEL_STATUS;
486-
}
487-
os.write(barray, 0, result);
492+
while ((result = k.read(barray, 0, bufferSize)) > -1) {
493+
if (monitor.isCanceled()) {
494+
monitor.done();
495+
k.close();
496+
os.close();
497+
return Status.CANCEL_STATUS;
488498
}
499+
os.write(barray, 0, result);
489500
}
490501
}
491502
}
@@ -495,36 +506,83 @@ protected IStatus run(final IProgressMonitor monitor) {
495506
// is fully copied
496507
for (String name : links.keySet()) {
497508
String link = links.get(name);
498-
InputStream in2 = ((DockerConnection) connection).copyContainer(token, containerId,
499-
link);
500-
/*
501-
* The input stream from copyContainer might be incomplete or non-blocking so we
502-
* should wrap it in a stream that is guaranteed to block until data is
503-
* available.
504-
*/
505-
try (TarArchiveInputStream k = new TarArchiveInputStream(
506-
new BlockingInputStream(in2))) {
507-
TarArchiveEntry te = k.getNextTarEntry();
508-
if (te != null) {
509-
long size = te.getSize();
510-
IPath currDir = target.append(volume).removeLastSegments(1);
511-
IPath path = currDir.append(name);
512-
File f = new File(path.toOSString());
513-
f.createNewFile();
514-
try (FileOutputStream os = new FileOutputStream(f)) {
515-
int bufferSize = ((int) size > 4096 ? 4096 : (int) size);
516-
byte[] barray = new byte[bufferSize];
517-
int result = -1;
518-
while ((result = k.read(barray, 0, bufferSize)) > -1) {
519-
if (monitor.isCanceled()) {
520-
monitor.done();
521-
k.close();
522-
os.close();
523-
return Status.CANCEL_STATUS;
509+
boolean resolved = false;
510+
int i = 0; // prevent infinite loop if there is a circular link
511+
while (!resolved && ++i < 10) {
512+
InputStream in2 = ((DockerConnection) connection).copyContainer(token, containerId,
513+
link);
514+
/*
515+
* The input stream from copyContainer might be incomplete or non-blocking so we
516+
* should wrap it in a stream that is guaranteed to block until data is
517+
* available.
518+
*/
519+
try (TarArchiveInputStream k = new TarArchiveInputStream(
520+
new BlockingInputStream(in2))) {
521+
TarArchiveEntry te = k.getNextTarEntry();
522+
if (te != null && te.isSymbolicLink()) {
523+
IPath linkPath = new Path(te.getLinkName());
524+
if (!linkPath.isAbsolute()) {
525+
linkPath = new Path(link).removeLastSegments(1)
526+
.append(te.getLinkName());
527+
}
528+
link = linkPath.toPortableString();
529+
continue;
530+
}
531+
while (te != null) {
532+
long size = te.getSize();
533+
IPath currDir = target.append(volume).removeLastSegments(1);
534+
IPath path = currDir.append(name);
535+
File f = new File(path.toOSString());
536+
if (te.isSymbolicLink()) {
537+
IPath linkPath = new Path(te.getLinkName());
538+
if (!linkPath.isAbsolute()) {
539+
if (!isWin) {
540+
java.nio.file.Path p = Paths.get(path.toPortableString());
541+
java.nio.file.Path linkp = Paths.get(te.getLinkName());
542+
if (f.exists()) {
543+
f.delete();
544+
}
545+
Files.createSymbolicLink(p, linkp);
546+
} else {
547+
// we don't follow nested links
548+
}
549+
} else {
550+
// we don't follow nested absolute links
551+
}
552+
te = k.getNextTarEntry();
553+
continue;
554+
}
555+
int mode = te.getMode();
556+
if (te.isDirectory()) {
557+
f.mkdir();
558+
if (!isWin) {
559+
Files.setPosixFilePermissions(Paths.get(path.toOSString()),
560+
toPerms(mode));
561+
}
562+
te = k.getNextTarEntry();
563+
continue;
564+
}
565+
f.createNewFile();
566+
if (!isWin) {
567+
Files.setPosixFilePermissions(Paths.get(path.toOSString()),
568+
toPerms(mode));
569+
}
570+
try (FileOutputStream os = new FileOutputStream(f)) {
571+
int bufferSize = ((int) size > 4096 ? 4096 : (int) size);
572+
byte[] barray = new byte[bufferSize];
573+
int result = -1;
574+
while ((result = k.read(barray, 0, bufferSize)) > -1) {
575+
if (monitor.isCanceled()) {
576+
monitor.done();
577+
k.close();
578+
os.close();
579+
return Status.CANCEL_STATUS;
580+
}
581+
os.write(barray, 0, result);
524582
}
525-
os.write(barray, 0, result);
526583
}
527584
}
585+
resolved = true;
528586
}
529587
}
530588
}

containers/org.eclipse.linuxtools.docker.ui/src/org/eclipse/linuxtools/internal/docker/ui/commands/CommandMessages.properties

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ missing_connection=Missing connection
1616
command.pullImage.failure.no_connection=Unable to pull an image: no connection was found in the selection.
1717

1818
command.copyfromcontainer.failure.no_connection=Unable to copy from container: no connection was found.
19+
command.copyfromcontainer.failure.possible_circular_links=More than 9 links followed: possible circular links.
1920
command.copyfromcontainer.job.title=Copying files from {0}
2021
command.copyfromcontainer.job.task=Copying files
2122
command.copyfromcontainer.job.subtask=Copying {0}

0 commit comments

Comments
 (0)