How flags are used
All flags are passed to the file copy implementation specific to each operating system.
For example, in the case of UNIX-based systems, the class UnixCopyFile
uses the options to determine whether to perform certain additional operations.
Relevant section of the class UnixCopyFile
:
static Flags fromMoveOptions(CopyOption... options) {
Flags flags = new Flags();
for (CopyOption option: options) {
if (option == StandardCopyOption.ATOMIC_MOVE) {
flags.atomicMove = true;
continue;
}
if (option == StandardCopyOption.REPLACE_EXISTING) {
flags.replaceExisting = true;
continue;
}
if (option == LinkOption.NOFOLLOW_LINKS) {
// ignore
continue;
}
if (option == null)
throw new NullPointerException();
throw new UnsupportedOperationException("Unsupported copy option");
}
// a move requires that all attributes be copied but only fail if
// the basic attributes cannot be copied
flags.copyBasicAttributes = true;
flags.copyPosixAttributes = true;
flags.copyNonPosixAttributes = true;
flags.failIfUnableToCopyBasic = true;
return flags;
}
Attribute copy
Specifically on COPY_ATTRIBUTES
, usually copying a file means creating a new one in the destination location and copying the content.
File attributes such as last access date, modification date and permissions usually receive the default values of a new file.
Therefore, such a flag causes the new file to receive the same values in those attributes. Note that this is not always what we want. Copying only a few times has the complete sense of replicating in practice.
Relevant section of the class UnixCopyFile
:
// copy owner/permissions
if (flags.copyPosixAttributes) {
try {
fchown(fo, attrs.uid(), attrs.gid());
fchmod(fo, attrs.mode());
} catch (UnixException x) {
if (flags.failIfUnableToCopyPosix)
x.rethrowAsIOException(target);
}
}
// copy non POSIX attributes (depends on file system)
if (flags.copyNonPosixAttributes) {
source.getFileSystem().copyNonPosixAttributes(fi, fo);
}
// copy time attributes
if (flags.copyBasicAttributes) {
try {
futimes(fo,
attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
} catch (UnixException x) {
if (flags.failIfUnableToCopyBasic)
x.rethrowAsIOException(target);
}
}
Atomic operations
The flag ATOMIC_MOVE
does not actually apply to copy operations, despite the name of the enum
, but while moving a file.
In the case of UNIX-based systems, Java uses the command rename
to change the file from the source path to the destination path, as this command is atomic.
In addition, Java code does not perform other additional operations, such as checking if the target file exists, as this would make the process subject to race conditions, i.e., non-atomic.
Relevant section of the class UnixCopyFile
:
// handle atomic rename case
if (flags.atomicMove) {
try {
rename(source, target);
} catch (UnixException x) {
if (x.errno() == EXDEV) {
throw new AtomicMoveNotSupportedException(
source.getPathForExecptionMessage(),
target.getPathForExecptionMessage(),
x.errorString());
}
x.rethrowAsIOException(source, target);
}
return;
}
So copying operations are not atomic? This explains some problems I had, such as incomplete copies and sometimes copies with corrupted data.
– user28595
@diegofm It is a common problem. From the point of view of a program single-threaded, the disk operations are atomic. However, other processes or threads can see a file that has just been created while you are still starting to record the data in it.
– utluiz
If you work with file integration, where one program generates the file and another program consumes, I see two options: either you make the source program warn the target program when the file is complete, or, if you use directory monitoring, record the contents in a temporary file and, only when finished, move the file to the directory where it will be read.
– utluiz