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
- 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.
- Upload a File Asset (or any content type with a binary field) — e.g. a large
.dmg.
- Run the Copy workflow action on that contentlet (single, or via a bulk workflow action on a selection).
- 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
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
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:
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:In commons-io 2.14.0 (
bom/application/pom.xml), that overload delegates withpreserveFileDate = true:setTimes(...)triesBasicFileAttributeView.setTimes(...)and falls back toFile.setLastModified(long). On NFS/EFS-style mounts both can fail (attribute cache, ownership, ornoatime-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.copyFilefor binary fields in that class. The checkin path (ESContentletAPIImpl~lines 6778 / 6785) uses dotCMS's owncom.liferay.util.FileUtil.copyFile(...)with theCONTENT_VERSION_HARD_LINKoption 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
Steps to Reproduce
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..dmg.dotcms.logshowsWorkflowActionFailureException: Error copying binary file: '<name>_copy.<ext>': Cannot set the file time.Acceptance Criteria
lastModifiedis not meaningful.)fireBulkActionTasks) complete withoutWorkflowActionFailureExceptionin this scenario.generateCopyName) for File Assets is untouched.DotDataException— the fix must not swallow real copy errors.FileUtils.copyFile(src, dest)/copyFileToDirectorycall sites that inheritpreserveFileDate = trueon asset paths; any found on the asset/binary path are fixed in the same PR.dotCMS Version
mainbranch. 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