Skip to content

Copying a contentlet with a binary field fails on shared storage: "Cannot set the file time." #37068

Description

@fabrizzio-dotCMS

Problem Statement

Copying a contentlet that has a binary field fails on shared/network storage (NFS/EFS-backed asset volumes, as used by clustered Cloud environments) with:

com.dotmarketing.exception.DotDataException: Error copying binary file: 'example-file_copy.dmg': Cannot set the file time.
	at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.copyContentlet(ESContentletAPIImpl.java:9582)
	at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.copyContentlet(ESContentletAPIImpl.java:9473)
	at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.copyContentlet(ESContentletAPIImpl.java:9823)
	at com.dotmarketing.portlets.contentlet.business.ContentletAPIInterceptor.copyContentlet(ContentletAPIInterceptor.java:2624)
	at com.dotmarketing.portlets.workflows.actionlet.CopyActionlet.performCopy(CopyActionlet.java:98)
	at com.dotmarketing.portlets.workflows.actionlet.CopyActionlet.executePreAction(CopyActionlet.java:65)
	at com.dotmarketing.portlets.workflows.business.WorkflowAPIImpl.fireWorkflowPreCheckin(WorkflowAPIImpl.java:2446)
	...
Caused by: java.io.IOException: Cannot set the file time.
	at org.apache.commons.io.FileUtils.copyFile(FileUtils.java:815)
	at org.apache.commons.io.FileUtils.copyFile(FileUtils.java:838)
	at org.apache.commons.io.FileUtils.copyFile(FileUtils.java:746)
	at com.dotcms.content.elasticsearch.business.ESContentletAPIImpl.copyContentlet(ESContentletAPIImpl.java:9576)

The exception is wrapped into a WorkflowActionFailureException, so the whole Copy workflow action aborts — including bulk copy runs (fireBulkActionTasks), where one bad file kills the task.

Root cause

ESContentletAPIImpl.copyContentlet(...) copies each binary field with the two-argument commons-io helper:

// dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java
destFile = new File(temporalFolder + File.separator + fieldValue);
if (!destFile.exists()) {
    destFile.createNewFile();
}
FileUtils.copyFile(srcFile, destFile);   // <-- preserveFileDate = true

In commons-io 2.14.0 (bom/application/pom.xml), that overload delegates with preserveFileDate = true:

public static void copyFile(final File srcFile, final File destFile) throws IOException {
    copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING);
}

public static void copyFile(final File srcFile, final File destFile,
        final boolean preserveFileDate, final CopyOption... copyOptions) throws IOException {
    ...
    Files.copy(srcFile.toPath(), destFile.toPath(), copyOptions);

    // On Windows, the last modified time is copied by default.
    if (preserveFileDate && !setTimes(srcFile, destFile)) {
        throw new IOException("Cannot set the file time.");   // <-- thrown here
    }
}

setTimes(...) tries BasicFileAttributeView.setTimes(...) and falls back to File.setLastModified(long). On NFS/EFS-style mounts both can fail (attribute cache, ownership, or noatime-like mount semantics), so commons-io throws.

The byte copy has already completed successfully at that point. Only the timestamp-preservation step fails, and the destination is a brand-new file under a throwaway temp folder (getRealAssetPathTmpBinary() + /<uuid>), so its original modification date carries no business meaning. A cosmetic, best-effort operation is aborting a user-facing workflow action.

Why only this call site

This is the only direct use of org.apache.commons.io.FileUtils.copyFile for binary fields in that class. The checkin path (ESContentletAPIImpl ~lines 6778 / 6785) uses dotCMS's own com.liferay.util.FileUtil.copyFile(...) with the CONTENT_VERSION_HARD_LINK option and is unaffected.

The line dates back to 2013 and is unchanged on main; it only surfaces now because it is exercised on shared cluster storage where the timestamp call fails.

Impact

  • Who: any environment whose asset storage is a shared/network volume — i.e. clustered Cloud instances. Reproduced on a customer Cloud cluster.
  • What breaks: Copy workflow action, "Copy" from the content portlet, and bulk copy operations, for any content type with a binary field (File Assets included).
  • Workaround: none at the application level.

Steps to Reproduce

  1. Deploy dotCMS (main) with the assets/temp binary path on a shared network volume (NFS or AWS EFS), such as a Cloud cluster with more than one node.
  2. Upload a File Asset (or any content type with a binary field) — e.g. a large .dmg.
  3. Run the Copy workflow action on that contentlet (single, or via a bulk workflow action on a selection).
  4. The action fails. dotcms.log shows WorkflowActionFailureException: Error copying binary file: '<name>_copy.<ext>': Cannot set the file time.

Local reproduction without a cluster: mount the temp binary directory on a filesystem or bind-mount where the process cannot set file times (e.g. a read-only-attribute NFS export, or a mount owned by another uid), then perform the copy.

Acceptance Criteria

  • Copying a contentlet with a binary field succeeds on storage where setting file modification times is not permitted — the copy completes and the new contentlet has the binary attached.
  • The binary copy no longer requests timestamp preservation, or a timestamp-preservation failure is caught and logged at WARN without failing the copy. (The temp destination is a fresh file in a UUID temp folder, so the source's lastModified is not meaningful.)
  • Byte-for-byte content of the copied binary is identical to the source (size and checksum match).
  • The Copy workflow action and bulk copy (fireBulkActionTasks) complete without WorkflowActionFailureException in this scenario.
  • Existing behavior on local/POSIX filesystems is unchanged — copy still succeeds, file name suffix logic (generateCopyName) for File Assets is untouched.
  • Genuine I/O failures (source unreadable, disk full, destination not writable) still fail loudly with DotDataException — the fix must not swallow real copy errors.
  • An integration test covers the binary-field copy path and asserts the copy succeeds when timestamp preservation is unavailable.
  • Repo audited for other FileUtils.copyFile(src, dest) / copyFileToDirectory call sites that inherit preserveFileDate = true on asset paths; any found on the asset/binary path are fixed in the same PR.

dotCMS Version

main branch. Reproduced on a Cloud clustered environment (shared asset storage). Code path is unchanged since 2013, so all currently supported versions are affected when running on network storage.

Severity

High - Major functionality broken

Links

  • Freshdesk ticket: NA
  • Slack: NA

Metadata

Metadata

Type

Projects

Status
New

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions