Federico Mariani created CAMEL-24089:
----------------------------------------

             Summary: camel-file: file-to-file copy with readLock=fileLock 
silently truncates files larger than 2 GB on Linux (single transferTo, return 
value ignored)
                 Key: CAMEL-24089
                 URL: https://issues.apache.org/jira/browse/CAMEL-24089
             Project: Camel
          Issue Type: Bug
          Components: camel-file
    Affects Versions: 4.21.0
            Reporter: Federico Mariani
         Attachments: FileConsumerFileLockHugeFileTruncationIssueTest.java

h3. Problem
Route {{from("file:in?readLock=fileLock").to("file:out")}} keeps the read-lock 
{{FileChannel}} in an exchange property; {{FileOperations.writeFileByFile}} 
(FileOperations.java:515-532) then copies via:
{code:java}
channel.transferTo(0, channel.size(), out);   // single call, return value 
discarded
{code}
{{FileChannel.transferTo}} is documented to possibly transfer fewer bytes than 
requested, and the JDK caps each call at {{Integer.MAX_VALUE}} bytes (~2 147 
479 552 with sendfile on Linux). Any source file larger than ~2 GB produces a 
silently truncated target while the producer reports success - the done file is 
still written and the commit deletes/moves the source.

*Platform note:* the truncation manifests on Linux, where the JDK takes the 
direct sendfile/copy_file_range path capped at {{Integer.MAX_VALUE}}-ish bytes 
per call ({{sun.nio.ch.FileChannelImpl.transferTo}}: {{int icount = (int) 
Math.min(count, nd.maxDirectTransferSize())}}). On macOS the direct transfer is 
unsupported for file-to-file targets and the JDK falls back to 
{{transferToTrustedChannel}}, an internal loop that copies the full count - 
verified empirically (a single {{transferTo}} of a 3 GB file returned 
3221225472 on macOS). Relying on that fallback is still a contract violation: 
{{transferTo}} is documented to possibly transfer fewer bytes than requested.

h3. Failure scenario
{{readLock=fileLock}} consumer + file producer + source file > 2 GB on Linux: 
target is truncated at ~2 GB, no exception, source is gone after commit. The 
non-locked path ({{Files.copy}}) is unaffected.

h3. History
Introduced by CAMEL-13680 (2019). Notably the pre-2017 code this area evolved 
from *did* loop: {{while (position < size) position += in.transferTo(position, 
bufferSize, out);}} (removed in d8c0a53b6787).

h3. Suggested fix
Loop until {{channel.size()}} bytes are transferred (standard transferTo loop).

h3. Reproducer
Attached JUnit test (place in 
{{core/camel-core/src/test/java/org/apache/camel/component/file/}}, where the 
file component tests live). Uses a sparse 3 GB source file; needs ~3 GB free 
transient disk space. Asserts target length equals source length. It FAILS on 
Linux (target truncated to ~2147483647 bytes) and passes on macOS due to the 
JDK fallback described above. The fix - the standard loop until all bytes are 
transferred - is correct on all platforms.

----
_This issue was researched and filed by Claude Code on behalf of [~croway] 
(GitHub: Croway), as part of a deep code review of camel-file and camel-ftp. A 
JUnit reproducer is attached; it fails deterministically on Linux on current 
main (4.22.0-SNAPSHOT)._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to