Class OwnerOnlyAtomicFile

java.lang.Object
com.ultikits.ultitools.utils.OwnerOnlyAtomicFile

final class OwnerOnlyAtomicFile extends Object
The single atomic, owner-only file writer behind both framework-owned files under <server root>/.ultikits/: CredentialStore's credentials.json and CloudStateStore's cloud-outbox.json. It was extracted from CredentialStore.writeLocked (plan 22-03, S-15) so the durability rule lives in one place; both callers delegate here and neither keeps a copy.

One write, in order: create the parent directories; create the temporary file in the target's own directory (owner-only, rw-------, at creation on a POSIX file system, and narrowed again after opening in case an interrupted earlier write left the same name with another mode, D19-060); write the body through a UTF-8 encoder; flush and force the bytes to disk; optionally run a hook; replace the target with StandardCopyOption.ATOMIC_MOVE and StandardCopyOption.REPLACE_EXISTING; force the directory (best-effort). A crash therefore leaves either the previous complete file or the new complete file, never a torn one, and a power loss cannot leave the target naming bytes that never reached the disk (#573). When the move did not happen, the temporary file is removed.

This class decides nothing about what is written or whether a write is allowed: it never reads the target, so it cannot judge operator content. Each caller states in its own javadoc why its write path cannot overwrite operator-written configuration (the configuration-layer rule, D-31).

  • Field Details

    • OWNER_ONLY

      static final Set<PosixFilePermission> OWNER_ONLY
      Owner read and write only: the mode of every file this class writes on POSIX (D19-060).
    • LOGGER

      private static final Logger LOGGER
  • Constructor Details

    • OwnerOnlyAtomicFile

      private OwnerOnlyAtomicFile()
  • Method Details

    • write

      static void write(Path target, String tempFileName, OwnerOnlyAtomicFile.WriterBody body) throws IOException
      Replaces target atomically with the content body writes.
      Parameters:
      target - the file to replace (or create)
      tempFileName - the bare name of the temporary file, created in target's directory
      body - writes the complete new content
      Throws:
      AtomicMoveNotSupportedException - if the file system cannot replace the target atomically; thrown unchanged so a caller can report it with its own message, never silently replaced by a non-atomic copy
      IOException - if any other step fails; the target is then left unchanged
    • write

      static void write(Path target, String tempFileName, OwnerOnlyAtomicFile.WriterBody body, Runnable beforeMove) throws IOException
      As write(Path, String, WriterBody), running beforeMove after the temporary file is complete and forced to disk and before the atomic move. Only CloudStateStore's test seam passes a hook.
      Parameters:
      beforeMove - run between the forced temporary file and the move, or null
      Throws:
      IOException
    • forceDirectory

      private static void forceDirectory(Path directory)
      Forces a directory's entries to disk after the rename, so the rename itself survives a power loss. Best-effort: Windows cannot open a directory as a channel, and a failure here cannot undo a rename that already happened, so it is logged at Level.FINE and not reported as a failed write.