Skip to content
Open
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
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_eclipse_core_internal_filesystem_local_l

/* Follow symlink and update stat for further processing. */
if (fstatat(directoryFd, entry->d_name, &st, 0) != 0) {
memset(&st, 0, sizeof(st));
/* Keep lstat data from the symlink itself for dangling links. */
statErrno = errno;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*******************************************************************************/
package org.eclipse.core.internal.filesystem.local;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -533,6 +534,12 @@ public InputStream openInputStream(int options, IProgressMonitor monitor) throws
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
IFileInfo info = fetchInfo();
if (!info.exists() && info.getAttribute(EFS.ATTRIBUTE_SYMLINK) && info.getStringAttribute(EFS.ATTRIBUTE_LINK_TARGET) != null) {
// If the file is a symbolic link and the target is set but does not exist, return an empty
// stream instead of throwing an exception
return new ByteArrayInputStream(new byte[0]);
}
handleReadIOException(e);
return null;
}
Expand All @@ -544,6 +551,12 @@ public byte[] readAllBytes(int options, IProgressMonitor monitor) throws CoreExc
try {
return Files.readAllBytes(file.toPath());
} catch (IOException e) {
IFileInfo info = fetchInfo();
if (!info.exists() && info.getAttribute(EFS.ATTRIBUTE_SYMLINK) && info.getStringAttribute(EFS.ATTRIBUTE_LINK_TARGET) != null) {
// If the file is a symbolic link and the target is set but does not exist, return an empty
// stream instead of throwing an exception
return new byte[0];
}
handleReadIOException(e);
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,15 @@ public static FileInfo fetchFileInfo(String fileName) {
info = new FileInfo();
} else if ((stat.st_mode & S_IFMT) == S_IFLNK) { // it's a link!
LinuxStructStat targetStat = new LinuxStructStat();
if (stat(name, targetStat) == 0) { // get the information about the file the link points to
if (stat(name, targetStat) == 0 && targetStat.errno == 0) { // get the information about the file the link points to
info = targetStat.toFileInfo(); // store the target file stats in info
} else { // invalid link target
info = new FileInfo();
// Keep symlink metadata (permissions/timestamp) from lstat for dangling links.
info = stat.toFileInfo();
if (targetStat.errno == ENOENT) {
// Preserve historical "broken target" semantics while still providing link attributes.
info.setExists(false);
}
if (targetStat.errno != ENOENT) {
info.setError(IFileInfo.IO_ERROR);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ public IResource[] allResourcesFor(URI location, boolean files, int memberFlags)
public ResourceAttributes attributes(IResource resource) {
IFileStore store = getStore(resource);
IFileInfo fileInfo = store.fetchInfo();
if (!fileInfo.exists()) {
if (!fileInfo.exists() && !fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK)) {
return null;
}
return FileUtil.fileInfoToAttributes(fileInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package org.eclipse.core.internal.localstore;

import java.util.Iterator;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.internal.resources.Resource;
import org.eclipse.core.resources.IResource;

Expand All @@ -41,7 +43,7 @@ public UnifiedTreeNode(UnifiedTree tree, IResource resource, IFileStore store, I
}

public boolean existsInFileSystem() {
return fileInfo != null && fileInfo.exists();
return fileInfo != null && (fileInfo.exists() || fileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,35 @@
package org.eclipse.core.internal.resources;

import java.net.URI;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.IFileSystem;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.internal.localstore.FileSystemResourceManager;
import org.eclipse.core.internal.properties.IPropertyManager;
import org.eclipse.core.internal.utils.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.internal.utils.BitMask;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.team.IResourceTree;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.osgi.util.NLS;

Expand Down Expand Up @@ -289,7 +310,8 @@ private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monit
// If the file doesn't exist on disk then signal to the workspace to delete the
// file and return.
IFileStore fileStore = localManager.getStore(file);
boolean localExists = fileStore.fetchInfo().exists();
IFileInfo localFileInfo = fileStore.fetchInfo();
boolean localExists = localFileInfo.exists() || localFileInfo.getAttribute(EFS.ATTRIBUTE_SYMLINK);
if (!localExists) {
deletedFile(file);
// Indicate that the delete was successful.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFileAttributes;
import java.util.function.Consumer;
import java.util.function.Function;
import org.eclipse.core.filesystem.EFS;
Expand All @@ -50,14 +54,28 @@
import org.junit.jupiter.api.extension.RegisterExtension;

public class SymlinkTest {

public static final boolean SUPPORT_BROKEN_SYMLINKS = Platform.OS.isLinux()
&& Platform.ARCH_X86_64.equals(Platform.getOSArch());

/**
* Tolerance in milliseconds applied when comparing a file's last modified time against a
* timestamp captured via {@link System#currentTimeMillis()}. The file system last modified
* time may be provided by a finer-resolution native (JNI) source, so rounding/truncation can
* make it appear a few milliseconds earlier than the wall-clock value.
*/
private static final long TIMESTAMP_TOLERANCE_MS = 1000;

/**
* Symbolic links on Windows behave differently compared to Unix-based systems. Symbolic links
* on Windows have their own set of attributes independent from the attributes of the link's
* target. The {@link java.io.File#exists() File.exists()} method on Windows checks for
* existence of the symbolic link itself, not its target.
*/
private static final boolean SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES = Platform.OS_WIN32
.equals(Platform.getOS()) ? true : false;
.equals(Platform.getOS());


private static String specialCharName = "äöüß ÄÖÜ àÀâ µ²³úá"; //$NON-NLS-1$

protected IFileStore aDir, aFile; //actual Dir, File
Expand Down Expand Up @@ -109,7 +127,11 @@ protected void mkLink(IFileStore dir, String src, String tgt, boolean isDir) thr

@Test
public void testBrokenSymlinkAttributes() throws Exception {
long testStartTime = System.currentTimeMillis();
// Use a tolerance because getLastModified() may originate from a finer-resolution
// native (JNI) timer than System.currentTimeMillis(). Truncation/rounding can make
// the reported modification time appear a few milliseconds before testStartTime even
// though the file was created afterwards.
long testStartTime = System.currentTimeMillis() - TIMESTAMP_TOLERANCE_MS;
makeLinkStructure();
//break links by removing actual dir and file
ensureDoesNotExist(aDir);
Expand All @@ -124,22 +146,39 @@ public void testBrokenSymlinkAttributes() throws Exception {
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES, ilDir.isDirectory());
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES, illDir.exists());
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES, illDir.isDirectory());
if (SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES) {
if (SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES || SUPPORT_BROKEN_SYMLINKS) {
// Symlinks on Windows have their own modification time.
assertTrue(ilFile.getLastModified() >= testStartTime);
assertTrue(ilDir.getLastModified() >= testStartTime);
assertTrue(illFile.getLastModified() >= testStartTime);
assertTrue(illDir.getLastModified() >= testStartTime);
assertTrue(ilFile.getLastModified() >= testStartTime,
"getLastModified()=" + ilFile.getLastModified() + " testStartTime=" + testStartTime);
assertTrue(ilDir.getLastModified() >= testStartTime,
"getLastModified()=" + ilDir.getLastModified() + " testStartTime=" + testStartTime);
assertTrue(illFile.getLastModified() >= testStartTime,
"getLastModified()=" + illFile.getLastModified() + " testStartTime=" + testStartTime);
assertTrue(illDir.getLastModified() >= testStartTime,
"getLastModified()=" + illDir.getLastModified() + " testStartTime=" + testStartTime);
} else {
assertEquals(0, ilFile.getLastModified());
assertEquals(0, ilDir.getLastModified());
assertEquals(0, illFile.getLastModified());
assertEquals(0, illDir.getLastModified());
}
assertEquals(0, ilFile.getLength());
assertEquals(0, ilDir.getLength());
assertEquals(0, illFile.getLength());
assertEquals(0, illDir.getLength());

if (SUPPORT_BROKEN_SYMLINKS) {
PosixFileAttributes ilFileAttrs = posixAttributess(lFile);
PosixFileAttributes illFileAttrs = posixAttributess(llFile);
PosixFileAttributes ilDirAttrs = posixAttributess(lDir);
PosixFileAttributes illDirAttrs = posixAttributess(llDir);

assertEquals(ilFileAttrs.size(), ilFile.getLength());
assertEquals(ilDirAttrs.size(), ilDir.getLength());
assertEquals(illFileAttrs.size(), illFile.getLength());
assertEquals(illDirAttrs.size(), illDir.getLength());
} else {
assertEquals(0, ilFile.getLength());
assertEquals(0, ilDir.getLength());
assertEquals(0, illFile.getLength());
assertEquals(0, illDir.getLength());
}

assertTrue(ilFile.getAttribute(EFS.ATTRIBUTE_SYMLINK));
assertEquals(ilFile.getStringAttribute(EFS.ATTRIBUTE_LINK_TARGET), "aFile");
Expand All @@ -151,6 +190,11 @@ public void testBrokenSymlinkAttributes() throws Exception {
assertEquals(illDir.getStringAttribute(EFS.ATTRIBUTE_LINK_TARGET), "lDir");
}

private static PosixFileAttributes posixAttributess(IFileStore store) throws IOException {
return Files.readAttributes(Paths.get(store.toURI()), PosixFileAttributes.class,
LinkOption.NOFOLLOW_LINKS);
}

// Moving a broken symlink is possible.
@Test
public void testBrokenSymlinkMove() throws Exception {
Expand Down Expand Up @@ -213,7 +257,7 @@ public void testRecursiveSymlink() throws Exception {
mkLink(baseStore, "l2", "l1", false);
IFileStore l1 = baseStore.getChild("l1");
IFileInfo i1 = l1.fetchInfo();
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES, i1.exists());
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES || SUPPORT_BROKEN_SYMLINKS, i1.exists());
assertFalse(i1.isDirectory());

assertTrue(i1.getAttribute(EFS.ATTRIBUTE_SYMLINK));
Expand All @@ -235,7 +279,7 @@ public void testRecursiveSymlink() throws Exception {
assertTrue(exceptionThrown);
assertTrue(i1.getAttribute(EFS.ATTRIBUTE_READ_ONLY));
}
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES, i1.exists());
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES || SUPPORT_BROKEN_SYMLINKS, i1.exists());

i1.setLastModified(12345);
exceptionThrown = false;
Expand All @@ -248,7 +292,7 @@ public void testRecursiveSymlink() throws Exception {
//FIXME bug: putInfo neither sets attributes nor throws an exception for broken symbolic links
//assertTrue(exceptionThrown);
//assertEquals(i1.getLastModified(), 12345);
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES, i1.exists());
assertEquals(SYMLINKS_ARE_FIRST_CLASS_FILES_OR_DIRECTORIES || SUPPORT_BROKEN_SYMLINKS, i1.exists());

l1.delete(EFS.NONE, getMonitor());
infos = baseStore.childInfos(EFS.NONE, getMonitor());
Expand Down
Loading
Loading