diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv
index a1c4c4c8da91e1088fcb82bef7d3ba139e2ffe52..e2fe974b4304e594e58ce62599b36d389885ddf7 100644
--- a/conf/core/jobs.csv
+++ b/conf/core/jobs.csv
@@ -77,6 +77,17 @@
 0,9,UPDATE,CheckTargetPathValid,ERROR
 0,9,UPDATE,SetImpToFix,ERROR
 
+## link rules
+0,10,INSERT,CheckNamePresent,WARN
+0,10,INSERT,MatchFileProp,ERROR
+0,10,INSERT,CheckTargetPathValid,ERROR
+0,10,INSERT,SetImpToFix,ERROR
+
+0,10,UPDATE,CheckNamePresent,WARN
+0,10,UPDATE,MatchFileProp,ERROR
+0,10,UPDATE,CheckTargetPathValid,ERROR
+0,10,UPDATE,SetImpToFix,ERROR
+
 ## property rules
 0,4,INSERT,CheckDatatypePresent,ERROR
 0,4,UPDATE,CheckDatatypePresent,ERROR
diff --git a/src/main/java/org/caosdb/server/database/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/DatabaseUtils.java
index ad8810418015deebc81a587f30ce904ac3401a4a..d31f10fc0e29b55b223b454c6848b1ea6c7ba0b7 100644
--- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java
+++ b/src/main/java/org/caosdb/server/database/DatabaseUtils.java
@@ -224,7 +224,7 @@ public class DatabaseUtils {
       throws SQLException {
     final String key = bytes2UTF8(rs.getBytes("FileKey"));
     if (key != null) {
-      // ret.fileParentId = rs.getInt("FileParentID");
+      ret.fileParentId = rs.getInt("FileParentID");
       ret.filePath = new Path(bytes2UTF8(rs.getBytes("FilePath")));
       ret.fileSize = rs.getLong("FileSize");
       if (rs.wasNull()) {
@@ -245,6 +245,10 @@ public class DatabaseUtils {
       }
       ret.fileStorageId = bytes2UTF8(rs.getBytes("FileStorageID"));
       ret.fileKey = key;
+
+      if (ret.fileType == ObjectType.LINK) {
+        ret.linkTarget = rs.getInt("LinkTarget");
+      }
     }
   }
 
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java
index 71b0ba1e99789e9fd08d7d1ca5abd5639baf2939..21f8f0990a34c084e6d30f59e709cf2dfdece90d 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java
@@ -17,9 +17,7 @@ public class MySQLGetVirtualFSO extends MySQLTransaction implements GetVirtualFS
     super(access);
   }
 
-  public static final String STMT_GET_FSO =
-      "SELECT 'SHA-512' AS FileHashAlgo, file_id AS FileId, "
-          + /*parent_directory AS FileParentID,*/ " path AS FilePath, size AS FileSize, hex(hash) AS FileHash, checked_timestamp AS FileHashChecked, mimetype AS FileMimeType, file_storage_id AS FileStorageID, file_key AS FileKey FROM files WHERE file_storage_id = ? AND file_key = ?";
+  public static final String STMT_GET_FSO = "call retrieveFSODescriptor(?, ?)";
 
   @Override
   public VirtualFSODescriptorInterface execute(String fileStorageId, String key) {
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java
index 305bb6c41a41ab1719c55f1eaaac30089e240e68..3c38279e6ff6896c92b7956cf4386b017cd10fe3 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java
@@ -17,7 +17,7 @@ public class MySQLInsertFSODescriptor extends MySQLTransaction implements Insert
   }
 
   public static final String STMT_INSERT_FILE_PROPERTIES =
-      "call insertFSODescriptor(?,?,?, ?,?,?, ?,?,?, ?)";
+      "call insertFSODescriptor(?,?,?, ?,?,?, ?,?,?, ?,?)";
 
   @Override
   public void insertFSODescriptor(final SparseEntity entity) {
@@ -56,6 +56,11 @@ public class MySQLInsertFSODescriptor extends MySQLTransaction implements Insert
         }
         insertFilePropsStmt.setString(9, entity.fileStorageId);
         insertFilePropsStmt.setString(10, entity.fileKey);
+        if (entity.linkTarget != null) {
+          insertFilePropsStmt.setInt(11, entity.linkTarget);
+        } else {
+          insertFilePropsStmt.setNull(11, Types.INTEGER);
+        }
         insertFilePropsStmt.execute();
       }
     } catch (final SQLIntegrityConstraintViolationException exc) {
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/CheckTargetPath.java b/src/main/java/org/caosdb/server/database/backend/transaction/CheckTargetPath.java
index e72b4556c16b1e5411d71763f95674e080f189c9..274dd0df43f2d1ab7807c10251ba99befbf3644f 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/CheckTargetPath.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/CheckTargetPath.java
@@ -2,9 +2,11 @@ package org.caosdb.server.database.backend.transaction;
 
 import org.caosdb.server.database.BackendTransaction;
 import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
+import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.container.TransactionContainer;
+import org.caosdb.server.filesystem.FileSystem.ObjectType;
 import org.caosdb.server.filesystem.Path;
 import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 import org.caosdb.server.utils.ServerMessages;
@@ -42,6 +44,29 @@ public class CheckTargetPath extends BackendTransaction {
     checkNastyPath(entity.getFSODescriptor().getPath());
     checkOwner(entity);
     checkParentExists(entity, container);
+    checkLinkTarget(entity, container);
+  }
+
+  private void checkLinkTarget(EntityInterface entity, TransactionContainer container) {
+    if (entity.getFSODescriptor().getObjectType() != ObjectType.LINK) {
+      return;
+    }
+
+    EntityID linkTarget = entity.getFSODescriptor().getLinkTarget();
+
+    EntityInterface linkTargetEntity = container.getEntityById(linkTarget);
+    if (linkTargetEntity == null) {
+      RetrieveSparseEntity t = new RetrieveSparseEntity(linkTarget, null);
+      linkTargetEntity = execute(t).getEntity();
+    }
+
+    if (linkTargetEntity.getRole().hasFSODescriptor()) {
+      linkTarget.link(linkTargetEntity.getId());
+      entity.getFSODescriptor().setLinkTargetPath(linkTargetEntity.getFSODescriptor().getPath());
+
+    } else {
+      // TODO(tf) throw something
+    }
   }
 
   private void checkParentExists(EntityInterface entity, TransactionContainer container)
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/ListDirectory.java b/src/main/java/org/caosdb/server/database/backend/transaction/ListDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..4050d55a90b3a2c028d934f7c0629513eaefd477
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/ListDirectory.java
@@ -0,0 +1,34 @@
+package org.caosdb.server.database.backend.transaction;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.caosdb.server.database.BackendTransaction;
+import org.caosdb.server.database.backend.interfaces.GetFilesInDirectoryImpl;
+import org.caosdb.server.database.proto.SparseEntity;
+import org.caosdb.server.entity.EntityID;
+import org.caosdb.server.filesystem.FSODescriptor;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
+
+public class ListDirectory extends BackendTransaction {
+
+  private EntityID id;
+  private List<VirtualFSODescriptorInterface> children = new LinkedList<>();
+
+  public ListDirectory(EntityID id) {
+    this.id = id;
+  }
+
+  @Override
+  protected void execute() {
+    GetFilesInDirectoryImpl t = getImplementation(GetFilesInDirectoryImpl.class);
+    List<SparseEntity> spes = t.execute(id);
+
+    for (SparseEntity spe : spes) {
+      children.add(new FSODescriptor(spe));
+    }
+  }
+
+  public List<VirtualFSODescriptorInterface> getChildren() {
+    return children;
+  }
+}
diff --git a/src/main/java/org/caosdb/server/database/proto/SparseEntity.java b/src/main/java/org/caosdb/server/database/proto/SparseEntity.java
index e99cdd8096bfeb47c838ed365c8c7bda563e7b28..4cedd8765b65a5c45166da473e8eb680f828ee59 100644
--- a/src/main/java/org/caosdb/server/database/proto/SparseEntity.java
+++ b/src/main/java/org/caosdb/server/database/proto/SparseEntity.java
@@ -50,6 +50,7 @@ public class SparseEntity extends VerySparseEntity {
   public String versionId = null;
   public Long versionSeconds = null;
   public Integer versionNanos = null;
+  public Integer linkTarget = null;
 
   @Override
   public String toString() {
@@ -68,6 +69,7 @@ public class SparseEntity extends VerySparseEntity {
         .append(this.fileMimeType)
         .append(this.fileType)
         .append(this.fileParentId)
+        .append(this.linkTarget)
         .append(this.versionId)
         .append(this.versionSeconds)
         .append(this.versionNanos)
diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java
index 317aea4dfc9e15e2115817621a455f083ff3d5b5..04237c31cef8e319a4a6207ee24d919836f2bd86 100644
--- a/src/main/java/org/caosdb/server/entity/Entity.java
+++ b/src/main/java/org/caosdb/server/entity/Entity.java
@@ -56,6 +56,7 @@ import org.caosdb.server.entity.xml.ToElementStrategy;
 import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.filesystem.FSODescriptor;
 import org.caosdb.server.filesystem.FileSystem;
+import org.caosdb.server.filesystem.FileSystem.ObjectType;
 import org.caosdb.server.filesystem.Hash;
 import org.caosdb.server.filesystem.Hasher;
 import org.caosdb.server.filesystem.Path;
@@ -385,6 +386,9 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
       if (getFSODescriptor().getParentDirectoryID() != null) {
         ret.fileParentId = getFSODescriptor().getParentDirectoryID().toInteger();
       }
+      if (ret.fileType == ObjectType.LINK && getFSODescriptor().getLinkTarget() != null) {
+        ret.linkTarget = getFSODescriptor().getLinkTarget().toInteger();
+      }
     }
     return ret;
   }
@@ -805,12 +809,23 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa
       tmpIdentifier = tmpIdentifier.substring(0, tmpIdentifier.length() - 1);
     }
 
+    // Parse LINKTARGET
+    Integer linkTarget = null;
+    if (element.getAttribute("linktarget") != null
+        && !element.getAttributeValue("linktarget").isBlank()) {
+      linkTarget = Integer.parseInt(element.getAttributeValue("linktarget"));
+    }
+
     // Store PATH, HASH, SIZE, TMPIDENTIFYER
     if (tmpIdentifier != null || checksum != null || path != null || size != null) {
       // legacy clients (which use this api) always use sha512.
       Hash hash = Hash.create(checksum, 0, Hasher.Default);
       if (getRole() == Role.Directory) {
         setFSODescriptor(FSODescriptor.createDir(FileSystem.DEFAULT_BACKEND, new Path(path)));
+      } else if (getRole() == Role.Link) {
+        setFSODescriptor(
+            FSODescriptor.createLink(
+                FileSystem.DEFAULT_BACKEND, new Path(path), new EntityID(linkTarget)));
       } else {
         setFSODescriptor(
             new FSODescriptor(
diff --git a/src/main/java/org/caosdb/server/entity/Role.java b/src/main/java/org/caosdb/server/entity/Role.java
index 992ddc6e6bbc8c8cfbc702667afce13a0a0ba93b..0e98e2c72c810dff337a476516ef8baddae9778c 100644
--- a/src/main/java/org/caosdb/server/entity/Role.java
+++ b/src/main/java/org/caosdb/server/entity/Role.java
@@ -83,6 +83,6 @@ public enum Role {
   }
 
   public boolean hasFSODescriptor() {
-    return this == File || this == Directory;
+    return this == File || this == Directory || this == Link;
   }
 }
diff --git a/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java b/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java
index a8c3e6a832848903884edda270e003a0c0dd6feb..397af673f438beba0620c3fa412ed3fd613f524b 100644
--- a/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java
+++ b/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.entity.container;
 
-import java.util.HashMap;
+import java.util.Map;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.entity.EntityID;
 
@@ -33,7 +33,7 @@ public abstract class EntityByIdContainer extends TransactionContainer {
       final Subject user,
       final Long timestamp,
       final String srid,
-      final HashMap<String, String> flags) {
+      final Map<String, String> flags) {
     super(user, timestamp, srid, flags);
   }
 
diff --git a/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java b/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java
index bc0da7c1de2bb876551d29ad7daffb50494f9720..24a2617865697c73ef5d8710f201930b7cac9f4e 100644
--- a/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java
+++ b/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java
@@ -22,7 +22,7 @@
  */
 package org.caosdb.server.entity.container;
 
-import java.util.HashMap;
+import java.util.Map;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.RetrieveEntity;
@@ -35,7 +35,7 @@ public class RetrieveContainer extends EntityByIdContainer {
       final Subject user,
       final Long timestamp,
       final String srid,
-      final HashMap<String, String> flags) {
+      final Map<String, String> flags) {
     super(user, timestamp, srid, flags);
   }
 
diff --git a/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java b/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java
index 52fd675f4816f2df16ef68722bef572a807a5348..89ff91d4e56574fe137968ddd344b547df442817 100644
--- a/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java
+++ b/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java
@@ -25,6 +25,7 @@ package org.caosdb.server.entity.container;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.database.misc.TransactionBenchmark;
@@ -60,7 +61,7 @@ public class TransactionContainer extends Container<EntityInterface>
       final Subject user,
       final Long timestamp,
       final String srid,
-      final HashMap<String, String> flags) {
+      final Map<String, String> flags) {
     this(user, timestamp, srid);
     setFlags(flags);
   }
@@ -91,9 +92,9 @@ public class TransactionContainer extends Container<EntityInterface>
     this.messages.add(m);
   }
 
-  private HashMap<String, String> flags = null;
+  private Map<String, String> flags = null;
 
-  public HashMap<String, String> getFlags() {
+  public Map<String, String> getFlags() {
     return this.flags;
   }
 
@@ -156,7 +157,7 @@ public class TransactionContainer extends Container<EntityInterface>
     return null;
   }
 
-  public void setFlags(final HashMap<String, String> flags) {
+  public void setFlags(final Map<String, String> flags) {
     this.flags = flags;
   }
 
diff --git a/src/main/java/org/caosdb/server/entity/container/WritableContainer.java b/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
index dc88de5731c6948314f7594c14ae3e2d621814fd..bb193a8105b690b5f195d2f9fd694c2ab614518b 100644
--- a/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
+++ b/src/main/java/org/caosdb/server/entity/container/WritableContainer.java
@@ -21,7 +21,7 @@
  */
 package org.caosdb.server.entity.container;
 
-import java.util.HashMap;
+import java.util.Map;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.subject.Subject;
 
@@ -32,7 +32,7 @@ public class WritableContainer extends TransactionContainer {
       final Subject user,
       final Long timestamp,
       final String srid,
-      final HashMap<String, String> flags) {
+      final Map<String, String> flags) {
     super(user, timestamp, srid, flags);
   }
 
diff --git a/src/main/java/org/caosdb/server/entity/xml/FileToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/FileToElementStrategy.java
index 26122ba414133da7a5bfa5981f6c68f684028997..92779f2b2dd7f04d5a6fd2ee5d03db685996c303 100644
--- a/src/main/java/org/caosdb/server/entity/xml/FileToElementStrategy.java
+++ b/src/main/java/org/caosdb/server/entity/xml/FileToElementStrategy.java
@@ -23,6 +23,7 @@
 package org.caosdb.server.entity.xml;
 
 import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.filesystem.FileSystem.ObjectType;
 import org.jdom2.Attribute;
 import org.jdom2.Element;
 
@@ -48,6 +49,11 @@ public class FileToElementStrategy extends EntityToElementStrategy {
         element.setAttribute(
             new Attribute("size", Long.toString(entity.getFSODescriptor().getSize())));
       }
+      if (serializeFieldStrategy.isToBeSet("linktarget")
+          && entity.getFSODescriptor().getObjectType() == ObjectType.LINK
+          && entity.getFSODescriptor().getLinkTarget() != null) {
+        element.setAttribute("linktarget", entity.getFSODescriptor().getLinkTarget().toString());
+      }
     }
     return element;
   }
diff --git a/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java b/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java
index 5290f8698e828cd5da4a6b953690a9710834847f..19b1c829accfc0a4fd881024f437a5ac9e3af4eb 100644
--- a/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java
+++ b/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java
@@ -33,6 +33,7 @@ public class FSODescriptor implements VirtualFSODescriptorInterface {
   protected EntityStatus status = null;
   protected Hash hash = null;
   protected Path path = null;
+  protected Path linkTargetPath = null;
   protected Long size = null;
   protected String tmpIdentifier = null;
   protected EntityID entityId = new EntityID();
@@ -41,6 +42,7 @@ public class FSODescriptor implements VirtualFSODescriptorInterface {
   protected EntityID parentDirectory = null;
   protected List<VirtualFSODescriptorInterface> children;
   private RealFSODescriptorInterface twin;
+  private EntityID linkTarget = null;
 
   public FSODescriptor(
       final String fileStorageId,
@@ -80,6 +82,7 @@ public class FSODescriptor implements VirtualFSODescriptorInterface {
     this.type = spe.fileType;
     this.mimeType = spe.fileMimeType;
     this.parentDirectory = new EntityID(spe.fileParentId);
+    this.linkTarget = new EntityID(spe.linkTarget);
   }
 
   public FSODescriptor(final Path path, final Long size) {
@@ -240,8 +243,7 @@ public class FSODescriptor implements VirtualFSODescriptorInterface {
 
   @Override
   public EntityID getLinkTarget() {
-    // TODO Auto-generated method stub
-    return null;
+    return linkTarget;
   }
 
   @Override
@@ -258,4 +260,27 @@ public class FSODescriptor implements VirtualFSODescriptorInterface {
   public void setKey(String key) {
     this.key = key;
   }
+
+  @Override
+  public Path getLinkTargetPath() {
+    return linkTargetPath;
+  }
+
+  public void setLinkTarget(EntityID linkTarget) {
+    this.linkTarget = linkTarget;
+  }
+
+  @Override
+  public void setLinkTargetPath(Path linkTargetPath) {
+    this.linkTargetPath = linkTargetPath;
+  }
+
+  public static FSODescriptor createLink(
+      String fileStorageId, final Path path, final EntityID target) {
+    FSODescriptor result = new FSODescriptor(path);
+    result.fileStorageId = fileStorageId;
+    result.linkTarget = target;
+    result.type = ObjectType.LINK;
+    return result;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java b/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java
index 54fb698029048939851aa89719c7d6a8f2f2a10c..40836edbf5fd8ac9cd310a28385f3300801f1196 100644
--- a/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java
+++ b/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java
@@ -317,6 +317,30 @@ public abstract class LocalFileStorage implements FileStorageInterface {
         @Override
         public void cleanUp() {}
       };
+    } else if (target.getObjectType() == ObjectType.LINK) {
+      final String newTargetPath = target.getLinkTargetPath().toString();
+      final File newLink = targetPath.toFile();
+      createSymlink(targetPath.toFile(), getRoot().resolve(newTargetPath).toFile());
+      return new Undoable() {
+
+        @Override
+        public void undo() {
+          // remove newly created directories
+          if (newLink != null) {
+            try {
+              FileUtils.forceDelete(newLink);
+            } catch (final Exception e) {
+              logger.error(
+                  "Undo was not successfull. Could not delete link " + newLink.getAbsolutePath(),
+                  e);
+            }
+          }
+        }
+
+        @Override
+        public void cleanUp() {}
+      };
+
     } else {
       return rename(file.getFile(), targetPath.toFile());
     }
diff --git a/src/main/java/org/caosdb/server/filesystem/VirtualFSODescriptorInterface.java b/src/main/java/org/caosdb/server/filesystem/VirtualFSODescriptorInterface.java
index fa5cf556311ab41d6cd60527dfbfbf53d0c3b9fb..f76c997ae1ee1396f99b5815000177851071548c 100644
--- a/src/main/java/org/caosdb/server/filesystem/VirtualFSODescriptorInterface.java
+++ b/src/main/java/org/caosdb/server/filesystem/VirtualFSODescriptorInterface.java
@@ -122,4 +122,8 @@ public interface VirtualFSODescriptorInterface extends FSODescriptorInterface {
   public abstract List<? extends VirtualFSODescriptorInterface> listChildren();
 
   public abstract void setKey(String key);
+
+  public abstract Path getLinkTargetPath();
+
+  public abstract void setLinkTargetPath(Path target);
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/ListDirectory.java b/src/main/java/org/caosdb/server/jobs/core/ListDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd4bdd12c06b380a71fde753023aba62d2d9d3a2
--- /dev/null
+++ b/src/main/java/org/caosdb/server/jobs/core/ListDirectory.java
@@ -0,0 +1,33 @@
+package org.caosdb.server.jobs.core;
+
+import java.util.List;
+import org.caosdb.server.entity.EntityInterface;
+import org.caosdb.server.filesystem.FileSystem.ObjectType;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
+import org.caosdb.server.jobs.EntityFlagJob;
+import org.caosdb.server.jobs.JobAnnotation;
+import org.caosdb.server.jobs.TransactionStage;
+
+@JobAnnotation(
+    defaultValue = "false",
+    loadOnDefault = false,
+    flag = "listDirectory",
+    values = {"true", "false"},
+    stage = TransactionStage.POST_TRANSACTION)
+public class ListDirectory extends EntityFlagJob {
+
+  @Override
+  protected void job(String value) {
+    if ("true".equals(value)
+        && getEntity().hasFSODescriptor()
+        && getEntity().getFSODescriptor().getObjectType() == ObjectType.DIRECTORY) {
+      getEntity().getFSODescriptor().setChildren(listChildren(getEntity()));
+    }
+  }
+
+  private List<VirtualFSODescriptorInterface> listChildren(EntityInterface entity) {
+    org.caosdb.server.database.backend.transaction.ListDirectory t =
+        new org.caosdb.server.database.backend.transaction.ListDirectory(entity.getId());
+    return t.getChildren();
+  }
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
index a07862c0a8ced52a8caa1af2e845497eb64badd7..201a725fa17f99f187130b7ae7f8290499902cf3 100644
--- a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
+++ b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java
@@ -23,7 +23,9 @@
 package org.caosdb.server.jobs.core;
 
 import java.util.Map.Entry;
+import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.jobs.ContainerJob;
+import org.caosdb.server.jobs.EntityFlagJob;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.Job;
 import org.caosdb.server.jobs.JobAnnotation;
@@ -42,6 +44,10 @@ public class LoadContainerFlagJobs extends ContainerJob {
             ((FlagJob) j).setValue(flag.getValue());
           }
           getTransaction().getSchedule().add(j);
+        } else if (j != null && j instanceof EntityFlagJob) {
+          for (EntityInterface e : getContainer()) {
+            e.setFlag(flag.getKey(), flag.getValue());
+          }
         }
       }
     }
diff --git a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
index 44ebfa22d08c975031f5bf5bfca325d6b706af24..bf5ccce8de2cc6d2d50fe563591c69cca91293c8 100644
--- a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
+++ b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
@@ -34,6 +34,7 @@ import java.sql.SQLException;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.logging.Level;
 import org.apache.commons.fileupload.FileUploadException;
@@ -71,7 +72,7 @@ import org.restlet.util.Series;
  */
 public abstract class AbstractCaosDBServerResource extends ServerResource {
 
-  private final HashMap<String, String> flags = new HashMap<String, String>();
+  private final Map<String, String> flags = new HashMap<String, String>();
   private Long timestamp = null;
   private static final XMLParser xmlparser = new XMLParser();
   protected String sRID = null; // Server side request ID
@@ -415,7 +416,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
     return this.requestedItems;
   }
 
-  public HashMap<String, String> getFlags() {
+  public Map<String, String> getFlags() {
     return this.flags;
   }
 
diff --git a/src/main/java/org/caosdb/server/resource/FileSystemResource.java b/src/main/java/org/caosdb/server/resource/FileSystemResource.java
index 49a072f0035c8c3eaf9a5d85279b962687154767..407d6aac0ff86c3c38030dd7ceb485d314e0c5cb 100644
--- a/src/main/java/org/caosdb/server/resource/FileSystemResource.java
+++ b/src/main/java/org/caosdb/server/resource/FileSystemResource.java
@@ -25,18 +25,23 @@ package org.caosdb.server.resource;
 import static java.net.URLDecoder.decode;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
 import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
 import org.caosdb.server.database.misc.TransactionBenchmark;
+import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.entity.EntityInterface;
 import org.caosdb.server.entity.Message;
 import org.caosdb.server.entity.Message.MessageType;
+import org.caosdb.server.entity.container.RetrieveContainer;
 import org.caosdb.server.filesystem.FileSystem;
 import org.caosdb.server.filesystem.FileSystem.ObjectType;
 import org.caosdb.server.filesystem.Path;
 import org.caosdb.server.filesystem.RealFSODescriptorInterface;
 import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 import org.caosdb.server.permissions.EntityPermission;
+import org.caosdb.server.transaction.Retrieve;
 import org.caosdb.server.transaction.RetrieveSparseEntityByPath;
 import org.caosdb.server.utils.ServerMessages;
 import org.jdom2.Document;
@@ -72,24 +77,21 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
   protected final Representation httpGetInChildClass() throws Exception {
     try {
       final long t1 = System.currentTimeMillis();
-      final Path path =
-          new Path(
-              decode(
-                  getRequest().getAttributes().containsKey("path")
-                      ? (String) getRequest().getAttributes().get("path")
-                      : "",
-                  "UTF-8"));
+      final Path path = getPath();
 
       final Element rootElem = generateRootElement();
       final Document doc = new Document(rootElem);
       EntityInterface entity = null;
       VirtualFSODescriptorInterface file = null;
 
-      entity = getEntity(path);
+      entity = getEntityByPath(path);
+      if (entity.getFSODescriptor().getObjectType() == ObjectType.LINK) {
+        entity = getEntityById(entity.getFSODescriptor().getLinkTarget());
+      }
       file = entity.getFSODescriptor();
 
       if (file.getObjectType() == ObjectType.DIRECTORY) {
-        String referenceString = getReference().toString();
+        String referenceString = getReferenceString(file);
         if (!referenceString.endsWith(Path.DEFAULT_PATH_SEPARATOR)) {
           referenceString = referenceString + Path.DEFAULT_PATH_SEPARATOR;
         }
@@ -108,9 +110,10 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
                   "url", referenceString + child.getName() + Path.DEFAULT_PATH_SEPARATOR);
               break;
             case LINK:
-              // TODO
-              throw new UnsupportedOperationException(
-                  "Unknown FileSystem.ObjectType: " + child.getObjectType().toString());
+              celem = new Element("link");
+              celem.setAttribute("name", child.getName());
+              celem.setAttribute("url", referenceString + child.getName());
+              break;
             case FILE:
               celem = new Element("file");
               celem.setAttribute("name", child.getName());
@@ -158,6 +161,20 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
     }
   }
 
+  private Path getPath() throws UnsupportedEncodingException {
+    return new Path(
+        decode(
+            getRequest().getAttributes().containsKey("path")
+                ? (String) getRequest().getAttributes().get("path")
+                : "",
+            "UTF-8"));
+  }
+
+  private String getReferenceString(VirtualFSODescriptorInterface file)
+      throws UnsupportedEncodingException {
+    return getReference().toString().replaceFirst(getPath().toString(), file.getPath().toString());
+  }
+
   private Element toElement(final VirtualFSODescriptorInterface file) {
     // TODO
     return new Element("FileDescriptor");
@@ -167,9 +184,17 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
     return TransactionBenchmark.getRootInstance().getBenchmark(getClass());
   }
 
-  protected String getEntityID(final Path path) throws Exception {
-    final EntityInterface fileEnt = getEntity(path);
-    return fileEnt.getId().toString();
+  private EntityInterface getEntityById(EntityID id) throws Exception {
+    final long t1 = System.currentTimeMillis();
+    Map<String, String> flags = getFlags();
+    flags.put("listDirectory", "true");
+    RetrieveContainer c = new RetrieveContainer(getUser(), getTimestamp(), getSRID(), flags);
+    c.add(id);
+    Retrieve t = new Retrieve(c);
+    t.execute();
+    final long t2 = System.currentTimeMillis();
+    getBenchmark().addMeasurement(this.getClass().getSimpleName() + ".getEntityById", t2 - t1);
+    return t.getContainer().get(0);
   }
 
   /**
@@ -179,12 +204,12 @@ public class FileSystemResource extends AbstractCaosDBServerResource {
    * @return
    * @throws Exception
    */
-  private EntityInterface getEntity(final Path path) throws Exception {
+  private EntityInterface getEntityByPath(final Path path) throws Exception {
     final long t1 = System.currentTimeMillis();
     final RetrieveSparseEntityByPath t = new RetrieveSparseEntityByPath(path);
     t.execute();
     final long t2 = System.currentTimeMillis();
-    getBenchmark().addMeasurement(this.getClass().getSimpleName() + ".getEntity", t2 - t1);
+    getBenchmark().addMeasurement(this.getClass().getSimpleName() + ".getEntityByPath", t2 - t1);
     return t.getEntity();
   }