Posix Provider
This document describes the implementation details of the Posix file provider for managing files and directories on Unix-like systems.
Provider Selection
The Posix provider is the default and only file provider. It is always available and returns priority 1 for all file resources.
Operations
Store (Create/Update File)
Process:
- Verify parent directory exists
- Parse file mode from octal string
- Open source file if
sourceproperty is set - Create temporary file in the same directory as target
- Set file permissions on temp file
- Write content (from
sourcefile orcontentsproperty) - Set ownership (chown) on temp file
- Close temp file
- Atomic rename temp file to target path
Atomic Write Pattern:
The temp file is created in the same directory as the target to ensure os.Rename() is atomic (same filesystem).
Content Sources:
| Property | Behavior |
|---|---|
contents | Write string directly to file (template-resolved) |
source | Copy from local file path (adjusted for working directory) |
If both are empty, an empty file is created.
Error Handling:
| Condition | Behavior |
|---|---|
| Parent directory doesn’t exist | Return error: “is not a directory” |
| Invalid mode format | Return error from strconv.ParseUint |
| Source file not found | Return error from os.Open |
| Permission denied | Return error from underlying syscall |
| Rename failure | Return error: “could not rename temporary file” |
CreateDirectory
Process:
- Parse file mode from octal string
- Create directory and parents via
os.MkdirAll() - Lookup numeric UID/GID from owner/group names
- Set permissions via
os.Chmod()(ensures correct mode even if umask affected MkdirAll) - Set ownership via
os.Chown()
Command Sequence:
The explicit Chmod after MkdirAll is necessary because MkdirAll applies the process umask to the mode.
Remove
Process:
- When
forceistrue, the path is removed withos.RemoveAll(). - When
forceisfalse, the path is removed withos.Remove(). - A path that does not exist is treated as a no-op (no error).
- When
os.Remove()fails withsyscall.ENOTEMPTY, the error is wrapped with guidance pointing the user atforce: true.
Error Handling:
| Condition | Behavior |
|---|---|
| Path does not exist | Return nil |
force: false, directory not empty | Return wrapped error: "cannot remove <path>: directory is not empty, set 'force: true' ..." |
| Other syscall failure | Return error from underlying syscall |
Symlink Behavior:
os.RemoveAll() does not follow symlinks during traversal. If the target path is itself a symlink, only the symlink is removed and its target is left intact. A directory tree that contains symlinks to external locations is safe to remove with force: true: the symlink entries are unlinked, but the directories they point to are not deleted.
Context Cancellation:
os.RemoveAll() does not observe ctx. Removal of a very large tree cannot be interrupted mid-walk. This is acceptable for typical CCM workloads but should be considered when scheduling removal of large directories.
Status
Process:
- Initialize state with default metadata
- Call
os.Stat()on file path - Based on result, populate state accordingly
State Detection:
os.Stat() Result | Ensure Value | Metadata |
|---|---|---|
| File exists | present | Size, mtime, owner, group, mode, checksum |
| Directory exists | directory | Size, mtime, owner, group, mode |
os.ErrNotExist | absent | None |
os.ErrPermission | absent | None (logged as warning) |
| Other error | (unchanged) | None (logged as warning) |
Metadata Collection:
| Field | Source |
|---|---|
Name | From properties |
Provider | “posix” |
Size | FileInfo.Size() |
MTime | FileInfo.ModTime() |
Owner | util.GetFileOwner() - resolves UID to username |
Group | util.GetFileOwner() - resolves GID to group name |
Mode | util.GetFileOwner() - octal string (e.g., “0644”) |
Checksum | util.Sha256HashFile() - SHA256 hash (files only) |
Note: Checksum is only calculated for regular files, not directories.
Idempotency
The file resource achieves idempotency by comparing current state against desired state:
State Checks
The isDesiredState() function checks (in order):
- Ensure value matches -
present,absent, ordirectory - Content checksum matches - SHA256 of contents vs existing file (for files only)
- Owner matches - Owner comparison, normalized through
util.UserIDMatchesso a manifest written with a numeric UID compares equal to a named user with the same UID, and vice versa - Group matches - Group comparison, normalized through
util.GroupIDMatchesusing the same rules - Mode matches - Octal permission string comparison
Decision Flow
Content Comparison
For files with ensure: present:
| Content Source | Checksum Calculation |
|---|---|
contents property | Sha256HashBytes([]byte(contents)) |
source property | Sha256HashFile(adjustedSourcePath) |
The source path is adjusted based on the manager’s working directory when set.
Mode Validation
File modes are validated during resource creation:
- Strip optional
0oor0Oprefix - Parse as octal number (base 8)
- Validate range: must be β€
0777
Valid Mode Examples:
| Input | Parsed Value |
|---|---|
"0644" | 0o644 |
"644" | 0o644 |
"0o755" | 0o755 |
"0O700" | 0o700 |
Invalid Mode Examples:
| Input | Error |
|---|---|
"0888" | Invalid octal digit |
"1777" | Exceeds maximum (setuid/setgid not supported via mode) |
"rw-r--r--" | Not octal format |
Ownership Resolution
Owner and group values are resolved to numeric UID/GID via:
Resolution depends on the value’s form:
- A purely-numeric value (digits only) is parsed directly as a UID or GID. The system user database is not consulted, matching the semantics of
chown(1)when given a numeric argument. - Any other value is resolved through the system user database (
/etc/passwd,/etc/group, or equivalent) usinguser.Lookup(owner)anduser.LookupGroup(group).
Error Handling:
| Condition | Behavior |
|---|---|
| Empty value | Return error: “user name cannot be empty” or “group name cannot be empty” |
| Named user not in database | Return error: “could not lookup user” |
| Named group not in database | Return error: “could not lookup group” |
| Numeric value out of int range | Return error from strconv.Atoi |
Numeric values are not validated against the user database. A file may be chowned to a UID or GID that has no matching entry, which is intentional for namespaced or container scenarios.
State Comparison
State comparison reads ownership from the filesystem as the on-disk numeric UID/GID, reverse-resolved to a name when possible by util.GetFileOwner. Because the manifest may use either form, comparison is delegated to util.UserIDMatches and util.GroupIDMatches, which normalize both sides to numeric IDs before comparing. This keeps a manifest stable regardless of which form was chosen.
Working Directory Support
When a manager has a working directory set (e.g., from extracted manifest), the source property path is adjusted:
This allows manifests to use relative paths for source files bundled with the manifest.
Platform Support
The Posix provider uses Unix-specific system calls:
| Operation | System Call |
|---|---|
| Get file owner/group | syscall.Stat_t (UID/GID from stat) |
| Set ownership | os.Chown() β chown(2) |
| Set permissions | os.Chmod() β chmod(2) |
The provider has separate implementations for Unix and Windows (file_unix.go, file_windows.go in internal/util), with Windows returning errors for ownership operations.
Security Considerations
Atomic Writes
Files are written atomically via temp file + rename. This prevents:
- Partial file reads during write
- Corruption if process is interrupted
- Race conditions with concurrent readers
Permission Ordering
Permissions and ownership are set on the temp file before rename:
Chmod- Set permissions- Write content
Chown- Set ownership- Rename to target
This ensures the file never exists at the target path with incorrect permissions.
Path Validation
File paths must be absolute and clean (no . or .. components):
Required Properties
Owner, group, and mode are required properties and cannot be empty, preventing accidental creation of files with default/inherited permissions.