Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

Files and directories

Single-page view · as markdown

Files and directories

Peios / Using Peios / Peiosutils / Files and directories

This topic covers the commands that change the file system: creating files and directories, copying and moving them, removing them, and adjusting their size. If a command brings a file into existence, takes one out of it, or duplicates one, it is here.

This page names every command, and then explains the one theme that runs through the whole topic on Peios: what happens to a file's security descriptor when the file is created or copied.

The commands #

CommandPurpose
cpCopy files and directories.
mvMove or rename files and directories.
rmRemove files, and with -r, directories.
rmdirRemove directories, but only empty ones.
mkdirCreate directories.
mkfifoCreate named pipes (FIFOs).
mknodCreate special files — device nodes and FIFOs.
touchCreate empty files, or update a file's timestamps.
lnCreate hard links and symbolic links.
link and unlinkThe single-purpose, low-level versions of "make a hard link" and "remove a file".
shredDestroy a file's contents by overwriting them, so they cannot be recovered.
ddCopy data block by block, converting it on the way.
dfReport how much space each file system has, and how much is free.
duReport how much space files and directories use.
truncateShrink or extend a file to an exact size.
mktempCreate a temporary file or directory with a safely unique name.

Every file has a security descriptor #

On Peios, every file and directory carries a security descriptor — the record of who owns it and who may do what to it. Access to a file is decided from its descriptor; see Security descriptors for the full picture.

That fact shapes two groups of commands in this topic.

Creating a file gives it a descriptor #

When mkdir, mkfifo, mknod, or touch creates a new object, that object needs a security descriptor. By default it gets one inherited from the directory it is created in — the new object picks up the access rules its parent directory hands down.

All four of these commands share one set of options — the creation flags — that let you set the new object's descriptor explicitly instead of taking the inherited default: name an owner, set a different group, lock the object so it does not inherit, give it an integrity label, or supply a complete descriptor. The flags are documented in full on the mkdir page; mkfifo, mknod, and touch each link back to it.

Copying a file makes a fresh descriptor — unless you preserve #

cp creates new files, and a cross-file-system mv does too. A brand-new copy gets a brand-new descriptor inherited from its destination directory — it does not carry the source file's security across by default.

When you do want the copy to keep the source's owner, access rules, timestamps, or other attributes, that is the job of the --preserve family of options. cp and mv share an identical --preserve surface; it is documented on the cp page.

Removing a file checks whether you may write it #

rm and shred both make a decision based on whether you can write a file. rm prompts before removing a file you cannot write to; shred's --force overrides a file's protection so it can be destroyed. Both judge "can you write this?" by a live access check against the file's security descriptor — not by any decorative metadata on the inode. Each command's page explains exactly where that check is made.

Where to start #

For copying — and for the whole --preserve model — read cp.

For creating files and directories, and the shared creation flags, read mkdir.

For removing things safely, read rm.

cp

Peios / Using Peios / Peiosutils / Files and directories

cp copies files and directories.

cp [options] source dest
cp [options] source... directory
cp [options] -t directory source...

In the first form, cp copies one source to one dest. In the other two, it copies any number of sources into a directory, keeping their names.

$ cp report.txt report-backup.txt
$ cp report.txt notes.txt /home/jack/archive/

Copying directories #

cp refuses to copy a directory unless you ask it to descend into one:

OptionEffect
-r, -R, --recursiveCopy directories, and everything inside them, recursively.
-a, --archiveA faithful recursive copy: copy the whole tree, follow no symbolic links, and preserve every attribute. Equivalent to -d -R --preserve-all.

-a is the option to use when you want the copy to be as close to the original as possible — same structure, same security, same timestamps. A plain -r copies the contents but, as the next section explains, gives the copies fresh security of their own.

What a copy's security descriptor is #

This is the part of cp that is specific to Peios, so it is worth being precise.

Every file carries a security descriptor — the record of who owns it and who may do what to it. When cp copies a file, it creates a new file, and that new file needs a descriptor.

By default, a copy gets a fresh descriptor — it does not inherit the source's. Specifically, a plain cp:

  • gives the copy a descriptor inherited from the destination directory, exactly as if you had created a new file there;
  • makes you the owner of the copy;
  • gives the copy fresh timestamps — the copy's modification time is "now".

This is usually what you want. A file copied into your home directory should be governed by your home directory's rules and owned by you, regardless of where it came from. But when you need the copy to keep something from the source, you ask for it explicitly — with the --preserve family.

The --preserve family #

--preserve carries chosen attributes from the source onto the copy. The attributes you can preserve:

AttributeWhat it carries from the source
ownerThe owner SID.
daclThe whole DACL — every access rule on the file, inherited rules included.
saclThe whole SACL — the audit rules and the integrity label.
daclniThe DACL with inherited rules stripped out — only the rules set explicitly on the source.
saclniThe SACL with inherited rules stripped out.
timestampsThe access and modification times.
linksThe hard-link structure — files hard-linked together in the source stay hard-linked in the copy.
securityThe security.peios.* extended attributes (other than the descriptor itself).
xattrsAll other extended attributes.

You rarely name those individually. These options select sensible sets:

OptionPreserves
-pTimestamps only.
--preserveTimestamps only — the same as -p.
--preserve=LISTExactly the comma-separated attributes you name.
--preserve-allEvery attribute in the table above.
--sdowner, dacl, sacl — the full security descriptor, copied verbatim.
--sd-explicitowner, daclni, saclni — the source's explicitly set rules only.
--no-preserve=LISTTurns the named attributes back off — useful to subtract from a broader option.

--sd versus --sd-explicit #

Both carry the source's security across; they differ in how they treat inherited rules.

  • --sd copies the descriptor exactly — inherited rules and all. The copy ends up with precisely the access rules the source had, even if it lands in a directory that would have handed down something different. Use it when the copy must be a security-exact duplicate.
  • --sd-explicit copies only the rules that were set explicitly on the source, and lets the destination directory supply inheritance for the rest. Use it when copying a file into a different directory and you want the file's own tailored rules to come along while still picking up the new location's inherited rules.

For what "inherited" versus "explicit" means, see Inheritance.

When a preserve cannot be honoured #

A requested preserve is a firm instruction. If cp cannot carry an attribute across — for example, writing the copy's SACL requires the SeSecurityPrivilege privilege, and the caller does not have it — cp treats that as a hard error and the copy fails. It does not quietly produce a copy that is missing what you asked to preserve.

There is one exception, and it is a matter of definition rather than a softening of the rule. security and xattrs name extended attributes, and some filesystems have no extended attributes at all — FAT, 9p, and a squashfs image built without an attribute table are the ones you will meet. Copying from such a filesystem carries nothing, because there was nothing there: the preserve is satisfied vacuously, and the copy succeeds.

That is narrow on purpose. It applies only to reading the source. If the source does have extended attributes and the destination filesystem cannot hold them, the preserve genuinely failed and cp still stops:

# cp -a /home/jack/notes /mnt/usb-fat/
cp: failed to set extended attributes on '/mnt/usb-fat/notes': Operation not supported

The distinction matters most for -a, which requests both attribute classes on every file it touches. Without it, one xattr-less filesystem anywhere in a tree would abort the whole copy partway through.

Overwriting an existing destination #

By default, if the destination already exists, cp overwrites it. These options change that.

OptionEffect
-i, --interactiveAsk before overwriting an existing file.
-n, --no-clobberNever overwrite an existing file; skip it silently.
-u, --update[=WHICH]Overwrite only when the source is newer than the destination. WHICH can be all, none, or older.
-f, --forceIf an existing destination cannot be opened for writing, remove it and try the copy again.
--remove-destinationRemove the destination before opening it, always — contrast --force, which only removes on failure.
-b, --backup[=CONTROL]Before overwriting, make a backup of the destination.

Symbolic links in the source #

When a source is a symbolic link, cp can copy the link itself or the file it points to.

OptionEffect
-L, --dereferenceAlways follow symbolic links — copy the file they point to.
-P, --no-dereferenceNever follow symbolic links — copy the link itself.
-HFollow only the symbolic links named directly on the command line, not links found while recursing.
-dCopy links as links, and preserve hard-link structure. Short for --no-dereference --preserve=links.

Other ways to copy #

cp can also produce something other than a plain duplicate.

OptionEffect
-l, --linkCreate a hard link to the source instead of copying its data.
-s, --symbolic-linkCreate a symbolic link to the source instead of copying.
--reflink[=WHEN]Make a lightweight copy-on-write clone where the file system supports it. WHEN is always, auto, or never.
--sparse[=WHEN]Control whether runs of zero bytes are stored as holes rather than written out. WHEN is always, auto, or never.
--attributes-onlyCreate the destination and copy its attributes, but not its data.

Other options #

OptionEffect
-t, --target-directory=DIRCopy every source into DIR. Useful when the directory is not the last argument.
-T, --no-target-directoryTreat dest as a plain file even if it is a directory.
--parentsRecreate the source's leading directories under the destination.
-x, --one-file-systemDo not cross into a different file system while recursing.
--strip-trailing-slashesStrip any trailing slashes from each source argument before using it.
-v, --verbosePrint each file as it is copied.
--debugExplain in detail how each file was copied. Implies -v.
--progressShow a progress bar.

Exit status #

CodeMeaning
0Every file was copied successfully.
1A file could not be copied — including a requested --preserve that could not be honoured.

mv

Peios / Using Peios / Peiosutils / Files and directories

mv moves files and directories from one place to another. Moving a file within the same directory, under a new name, is how you rename it.

mv [options] source dest
mv [options] source... directory
mv [options] -t directory source...
$ mv draft.txt report.txt                 # rename
$ mv report.txt notes.txt /home/jack/done/ # move into a directory

A move is either a rename or a copy-and-delete #

mv does its job in one of two ways, and which one decides what happens to the file's security.

Within one file system, a move is a rename. Nothing is copied: the file stays exactly where it physically is, and only its name and directory entry change. The file keeps its inode, and so it keeps its security descriptor unchanged — every owner, access rule, audit rule, and timestamp is exactly as before. A rename does not re-evaluate inheritance; a file does not pick up new rules just because it landed in a new directory.

Across file systems, a move is a copy followed by a delete. When the destination is on a different file system, mv cannot just rename — it copies the data to a new file on the destination, then removes the original. That new file is a genuinely new object, so, exactly as with cp, it gets a fresh security descriptor inherited from the destination directory unless you ask otherwise.

So: rename a file and its security is untouched; move it to another file system and the copy starts fresh.

Preserving security on a cross-file-system move #

For the copy-and-delete case, mv carries the same --preserve family that cp does — an identical set of options, behaving the same way. They let a cross-file-system move carry the source's owner, access rules, timestamps, and other attributes onto the new file instead of taking the destination directory's inherited default.

The full --preserve reference — the attribute list, --sd, --sd-explicit, --preserve-all, --no-preserve, and what happens when a preserve cannot be honoured — is on the cp page. Everything there applies to mv unchanged.

On a same-file-system move the --preserve options have nothing to do: a rename already keeps every attribute, because it is the same file.

Overwriting an existing destination #

By default mv overwrites an existing destination. These options change that. When more than one of -i, -f, -n is given, the last one wins.

OptionEffect
-i, --interactiveAsk before overwriting an existing file.
-f, --forceDo not prompt before overwriting.
-n, --no-clobberNever overwrite an existing file.
-u, --update[=WHICH]Overwrite only when the source is newer. WHICH can be all, none, or older.
-b, --backup[=CONTROL]Make a backup of the destination before overwriting it.

Other options #

OptionEffect
-t, --target-directory=DIRMove every source into DIR.
-T, --no-target-directoryTreat dest as a plain file even if it is a directory.
--strip-trailing-slashesStrip any trailing slashes from each source argument.
-v, --verbosePrint each file as it is moved.
--debugExplain in detail how each file was moved. Implies -v.
--progressShow a progress bar.

Exit status #

CodeMeaning
0Every file was moved successfully.
1A file could not be moved.

rm

Peios / Using Peios / Peiosutils / Files and directories

rm removes files. With -r, it removes directories and everything inside them.

rm [options] file...
$ rm draft.txt old-notes.txt
$ rm -r build/

Removal is not reversible — once rm removes a file, its directory entry is gone. rm has a few safety behaviours, below, but the basic rule stands: think before you confirm.

Removing directories #

rm will not remove a directory unless you say so:

OptionEffect
-r, -R, --recursiveRemove directories and all of their contents, recursively.
-d, --dirRemove a directory, but only if it is already empty.

The write-protected prompt #

By default — when you have not passed -f and rm is running interactively — rm prompts before removing a file you cannot write to:

$ rm policy.db
rm: remove write-protected regular file 'policy.db'?

The point of that prompt is to catch mistakes: a file you cannot write is one you most likely did not mean to delete.

"Write-protected" here means a real access check. rm decides it by asking the kernel whether your token may write the file — a live check against the file's security descriptor. It is not read off any decorative metadata on the inode; it is the same authority question that any write to the file would face. So the prompt is honest: if rm says a file is write-protected, it is a file the access model would genuinely stop you writing.

(The check is about write access to the file's contents. Whether you may actually remove the file is a separate question, decided by its own access right — so you can be prompted about a write-protected file and still be allowed to delete it. The prompt is a courtesy check, not the authorisation.)

Controlling the prompts #

OptionEffect
-f, --forceNever prompt. Also ignore files that do not exist instead of reporting them.
-iPrompt before every removal.
-IPrompt just once before removing more than three files or before a recursive removal. Less intrusive than -i, but still a check against a slip.
--interactive[=WHEN]Set the prompting explicitly: never, once, or always.

The root failsafe #

rm refuses to recursively remove / — the root of the file system — because doing so by accident would be catastrophic.

OptionEffect
--preserve-rootRefuse to recurse on /. This is the default; you do not need to ask for it.
--no-preserve-rootDisable the failsafe. Required, in full and unabbreviated, if you genuinely intend a recursive operation on /.

Other options #

OptionEffect
-v, --verbosePrint each file as it is removed.
--progressShow a progress bar.

rm and shred #

rm unlinks a file — it removes the name, and the space becomes free. The file's data may still be physically present on the device until something else overwrites it. When you need the contents to be genuinely unrecoverable, use shred, which overwrites the data before removing the file.

Exit status #

CodeMeaning
0Every requested removal succeeded (or, with -f, the file did not exist).
1A file could not be removed.

rmdir

Peios / Using Peios / Peiosutils / Files and directories

rmdir removes directories — but only empty ones. If a directory still has anything in it, rmdir leaves it alone and reports the failure.

rmdir [options] directory...
$ rmdir old-cache/

That refusal to touch a non-empty directory is the whole point of rmdir: it is the safe way to remove a directory you believe is empty. If it is not empty, you find out instead of losing the contents. To remove a directory together with everything inside it, use rm -r.

Options #

OptionEffect
-p, --parentsRemove the directory and then its ancestors, as long as each becomes empty in turn. rmdir -p a/b/c removes a/b/c, then a/b, then a.
--ignore-fail-on-non-emptyDo not treat "directory not empty" as a failure. Other failures are still reported. Useful when clearing out whatever directories happen to be empty and leaving the rest.
-v, --verbosePrint a line for each directory as it is removed.

Exit status #

CodeMeaning
0Every directory was removed.
1A directory could not be removed — it was not empty, did not exist, or removal was refused.

mkdir

Peios / Using Peios / Peiosutils / Files and directories

mkdir creates directories.

mkdir [options] directory...
$ mkdir reports
$ mkdir -p projects/2026/q2

Creating parent directories #

By default mkdir creates exactly the directories you name, and fails if a parent is missing. -p removes that restriction:

OptionEffect
-p, --parentsCreate any missing parent directories along the way. Also makes mkdir succeed quietly if the directory already exists.
-v, --verbosePrint a line for each directory as it is created.

The creation flags #

A new directory needs a security descriptor — the record of who owns it and who may do what to it. By default, a new directory inherits its descriptor from the directory it is created in: it picks up the access rules its parent hands down. For most directories that is exactly right, and you pass no extra options at all.

When you need the new directory to have a different descriptor, the creation flags set it explicitly. These five flags are shared, unchanged, by mkdir, mkfifo, mknod, and touch — whenever one of those commands creates a new object, it accepts this same set. This page is the full reference for them.

FlagArgumentEffect
--ownerSIDMake this principal the owner, instead of you.
--groupSIDSet the object's group.
--labellevelGive the object a mandatory integrity label.
--no-inherit—Do not inherit rules from the parent directory; lock the object to its owner.
--sddlSDDL stringSupply the entire security descriptor directly.

A SID argument is either a literal S-1-… value or a well-known alias — BA for the built-in administrators, for example.

--label takes one of these levels, lowest to highest: untrusted, low, medium, medium-plus, high, system, protected. The label applies the standard rule for files and directories — a principal below the object's level cannot write to it.

Inherited, or explicit #

The default and --no-inherit are the two ends of a choice.

  • Inherited (the default) — the new directory tracks its parent. Rules the parent hands down apply to it, and as the parent's rules change the directory follows along. This keeps a tree of directories consistent without per-directory effort.
  • --no-inherit — the new directory takes nothing from its parent. Its access is locked to its owner, and it does not follow the parent's rules. Use it to carve out a directory whose security stands apart from the tree around it.

See Inheritance for the full model.

--sddl for a complete descriptor #

The four flags above are shortcuts for common adjustments. When you need to specify the whole descriptor — a particular set of access rules, audit rules, owner, and group all at once — --sddl takes it as a single string in the security-descriptor definition language:

$ mkdir --sddl 'O:BAG:BAD:(A;OICI;FA;;;BA)' secure-area

--sddl is mutually exclusive with --owner, --group, --label, and --no-inherit — it already says everything those flags would say, so combining them is rejected.

How the descriptor is applied #

mkdir creates the directory first and then applies the descriptor to it. The two steps are not separately visible — mkdir either finishes with the directory created and secured as asked, or fails as a whole. If applying the descriptor fails, the half-created directory is not left behind.

Exit status #

CodeMeaning
0Every directory was created.
1A directory could not be created, or a creation flag could not be applied.

mkfifo

Peios / Using Peios / Peiosutils / Files and directories

mkfifo creates named pipes, also called FIFOs.

mkfifo [options] name...
$ mkfifo events

A named pipe is a file that two programs use to pass a stream of data: one writes into it, the other reads from it, and the data flows through in order — first in, first out, which is what "FIFO" stands for. Unlike an ordinary pipe between two commands, a named pipe has a name on the file system, so the two programs do not have to be started together or be related to each other.

Setting the new pipe's security #

A FIFO is a file, and a new file needs a security descriptor. By default a new FIFO inherits its descriptor from the directory it is created in.

mkfifo accepts the shared creation flags — --owner, --group, --label, --no-inherit, and --sddl — to set that descriptor explicitly instead. They behave exactly as they do for mkdir; the full reference is on the mkdir page.

Exit status #

CodeMeaning
0Every FIFO was created.
1A FIFO could not be created — for example, the name already exists.

mknod

Peios / Using Peios / Peiosutils / Files and directories

mknod creates special files — files that are not data but an interface to something else: a device, or a named pipe.

mknod [options] name type [major minor]
$ mknod /dev/mydisk b 8 0
$ mknod backlog p

The type argument #

The type says what kind of special file to create:

TypeCreates
bA block device — a device addressed in fixed-size blocks, such as a disk.
c or uA character device — a device addressed as a stream of bytes, such as a terminal.
pA named pipe (FIFO).

For a block or character device (b, c, u) you must also give a major and a minor number. The major number selects which driver handles the device; the minor number tells that driver which specific device is meant. For a pipe (p), the major and minor numbers are omitted — a pipe has no driver behind it.

A major or minor number is read as hexadecimal if it begins with 0x, as octal if it begins with 0, and as decimal otherwise.

mknod NAME p and mkfifo NAME do the same thing; mkfifo exists as the clearer way to ask for just a pipe.

Setting the new file's security #

A special file needs a security descriptor like any other file, and by default a new one inherits its descriptor from the directory it is created in.

mknod accepts the shared creation flags — --owner, --group, --label, --no-inherit, and --sddl — to set that descriptor explicitly. They behave exactly as they do for mkdir; the full reference is on the mkdir page.

Exit status #

CodeMeaning
0The special file was created.
1It could not be created — a missing or invalid type, missing device numbers, or a name that already exists.

touch

Peios / Using Peios / Peiosutils / Files and directories

touch does one of two things, depending on whether the file already exists:

  • if the file does not exist, touch creates it, empty;
  • if the file does exist, touch updates its timestamps to the current time.
touch [options] [file...]
$ touch notes.txt        # create notes.txt if absent, else bump its times

The "create an empty file" behaviour is the everyday use. The "update timestamps" behaviour matters to tools that decide what work to do by comparing file times — touch is how you tell such a tool that a file should be considered freshly changed.

Which timestamps, and to what #

By default touch sets both the access time and the modification time to the current moment. These options narrow or redirect that.

OptionEffect
-aChange only the access time.
-mChange only the modification time.
--time=WORDChoose which time to change by name: access, atime, or use for the access time; modify or mtime for the modification time.
-d, --date=STRINGUse the time described by STRING instead of the current time.
-t STAMPUse the time given as [[CC]YY]MMDDhhmm[.ss] instead of the current time.
-r, --reference=FILEUse the timestamps of FILE instead of the current time.
-h, --no-dereferenceIf the named file is a symbolic link, change the link's own timestamps rather than those of the file it points to.

Not creating a file #

To use touch purely to update timestamps, and never to create anything:

OptionEffect
-c, --no-createDo not create a file that does not exist. A named file that is absent is silently skipped.

Setting a created file's security #

When touch creates a file, that new file needs a security descriptor, and by default it inherits one from the directory it is created in.

touch accepts the shared creation flags — --owner, --group, --label, --no-inherit, and --sddl — to set that descriptor explicitly. They behave exactly as they do for mkdir; the full reference is on the mkdir page.

These flags apply only on the create path. If touch is updating the timestamps of a file that already exists, there is no new descriptor to set, and the creation flags have nothing to do — they do not alter an existing file's security. Pair them with -c and they never take effect at all, since -c means nothing is ever created.

Exit status #

CodeMeaning
0Every file was created or updated as requested.
1A file could not be created or updated.

ln

Peios / Using Peios / Peiosutils / Files and directories

ln creates links — extra ways to reach a file.

ln [options] target link_name
ln [options] target
ln [options] target... directory
ln [options] -t directory target...
$ ln -s /opt/app/bin/app /lcl/bin/app   # a symbolic link
$ ln data.csv data-archive.csv                # a hard link

Hard links and symbolic links #

There are two kinds of link, and they are genuinely different things.

A hard link is another name for the same file. The file's data exists once; a hard link is an additional directory entry pointing at it. Every hard link to a file is equal — none is "the original" — and the file's data stays as long as at least one hard link remains. Hard links cannot span file systems, and cannot be made to directories.

A symbolic link is a small file that simply contains a path. When something opens a symbolic link, it is redirected to whatever that path names. The symbolic link and its target are separate files: if the target is moved or removed, the link still exists but now points at nothing — it "dangles". Symbolic links can point anywhere, across file systems and at directories.

ln creates hard links by default, and symbolic links with -s. When in doubt, -s — a symbolic link's behaviour is the easier one to reason about.

The two also differ in how they are secured. A hard link is not a new file, so creating one involves no new security descriptor — every name for the file shares the file's existing one. A symbolic link is a new file, and like any new file it inherits its own descriptor from the directory it is created in.

The forms #

  • ln target link_name — create one link named link_name.
  • ln target — create a link to target in the current directory, with the same final name.
  • ln target... directory — create a link to each target inside directory.
  • ln -t directory target... — the same, with the directory named first.

Options #

OptionEffect
-s, --symbolicMake symbolic links instead of hard links.
-f, --forceRemove an existing destination file so the link can be created.
-i, --interactiveAsk before removing an existing destination.
-r, --relativeFor a symbolic link, write the target as a path relative to the link's own location, rather than an absolute path.
-n, --no-dereferenceIf link_name is itself a symbolic link to a directory, treat it as an ordinary file — replace the link rather than create something inside the directory it points to.
-L, --logicalWhen the target is a symbolic link, link to what it points to.
-P, --physicalMake a hard link to a symbolic link itself, rather than to its target.
-t, --target-directory=DIRCreate all links inside DIR.
-T, --no-target-directoryTreat link_name as a plain file, never as a directory to create links inside.
-b, --backup[=CONTROL]Back up an existing destination before replacing it.
-S, --suffix=SUFFIXUse SUFFIX for backup file names.
-v, --verbosePrint the name of each link as it is created.

Inspecting links afterwards #

readlink prints where a symbolic link points. The low-level link command makes a single hard link with no options, for scripts that want exactly that and nothing else.

Exit status #

CodeMeaning
0Every link was created.
1A link could not be created.

link and unlink

Peios / Using Peios / Peiosutils / Files and directories

link and unlink are the bare, single-purpose versions of two operations that ln and rm also perform. Each does exactly one thing, takes exactly its operands, and has no options. They exist for scripts that want one precise operation with no surrounding behaviour — no prompting, no link kinds to choose, no recursion.

link #

link file1 file2

link creates one hard link: a new name, file2, for the existing file file1. file1 must exist; file2 must not.

$ link data.csv data-archive.csv

That is the whole command. It performs the link operation directly and reports whether it succeeded. For anything more — symbolic links, replacing an existing destination, linking into a directory — use ln.

unlink #

unlink file

unlink removes one file: it deletes the directory entry file. It takes a single name and removes it directly.

$ unlink stale.lock

unlink does not prompt, does not recurse, and does not remove directories. For removing several files, removing directories, or any of the safety prompts, use rm.

Why they exist #

ln and rm are the commands to use day to day — they are flexible and have the safety behaviours. link and unlink are deliberately rigid: a script that calls unlink will only ever remove a single file, and a reviewer can see that at a glance. The narrowness is the feature.

Exit status #

Both commands:

CodeMeaning
0The operation succeeded.
1The operation failed — a missing or already-existing operand, or a refused request.

shred

Peios / Using Peios / Peiosutils / Files and directories

shred destroys a file's contents. It does not just unlink the file — it overwrites the data, repeatedly, so that what was there cannot be read back even with effort and specialised equipment.

shred [options] file...
$ shred -u -v secret-keys.txt

rm removes a file's name; the data may sit on the device, recoverable, until something else happens to overwrite it. shred overwrites the data on purpose. Use it when a file held something that must not be recoverable.

By default shred overwrites a file three times and then leaves the file in place — empty of its old contents but still present. That default exists because shred is often pointed at device files, which usually should not be removed. To remove the file after shredding it, pass -u.

When shredding actually works #

shred relies on one assumption: that writing to the file overwrites the same physical storage the old data occupied. On a traditional file system that holds. Several common file system designs break it — anything that writes new data to a fresh location instead of in place, keeps snapshots, journals file data, caches copies elsewhere, or compresses. On those, shred cannot guarantee the old data is gone.

Backups and remote mirrors are a separate matter entirely: shred cannot reach a copy of the file that lives somewhere else.

Treat shred as effective on a plain local file system and uncertain otherwise.

Options #

OptionEffect
-n, --iterations=NOverwrite N times instead of the default of 3.
-u, --remove[=HOW]Remove the file after overwriting it. HOW controls how thoroughly the name itself is obscured before the file is unlinked.
-z, --zeroAdd a final pass that writes zeros, so the file does not visibly look shredded afterwards.
-s, --size=NShred N bytes, rather than the whole file. Accepts size suffixes such as K, M, G.
-x, --exactDo not round the shredded size up to the next full block. Without it, shred rounds up so the block's slack space is overwritten too.
--random-source=FILETake the random bytes for the overwrite passes from FILE.
-v, --verboseShow progress as each pass runs.
-f, --forceOverride a file's protection: when a live access check shows you cannot write the file, rewrite its security descriptor so the overwrite can proceed (see below).

--force and a file's protection #

To overwrite a file, shred has to be able to write it. If the file's security descriptor does not grant you write access, an ordinary shred cannot proceed.

-f (--force) handles that case. When --force is given and a live access check shows you cannot currently write the file, shred rewrites the file's security descriptor to one that grants full access, and then overwrites the file. The reasoning is deliberate: the file is about to be destroyed anyway, and --force is you saying "override this file's protection."

--force can do this only when you are allowed to change the file's security in the first place — that is, when you own the file or hold the right to rewrite its access rules. If you cannot change the descriptor, --force cannot grant itself write access, and the shred still fails. --force overrides a file's protection; it does not manufacture authority you do not have.

Exit status #

CodeMeaning
0Every file was shredded successfully.
1A file could not be shredded.

dd

Peios / Using Peios / Peiosutils / Files and directories

dd copies data from one place to another, a block at a time, and can transform the data as it goes. It is the tool for low-level copying — writing a disk image to a device, extracting an exact byte range out of a file, copying from or to a raw device.

dd [operand]...
$ dd if=disk.img of=/dev/mydisk bs=4M status=progress

How dd is told what to do #

dd does not take options in the usual -x form. It takes operands, each written name=value, in any order. With no if= operand it reads from standard input; with no of= operand it writes to standard output.

The operands #

OperandMeaning
if=FILERead input from FILE.
of=FILEWrite output to FILE.
bs=BYTESRead and write BYTES at a time. Sets both the input and output block size at once.
ibs=BYTESInput block size (default 512).
obs=BYTESOutput block size (default 512).
cbs=BYTESConversion block size — used by the block and unblock conversions.
count=NStop after N input blocks, rather than reading to the end.
skip=NSkip N input blocks before copying. Also spelled iseek=N.
seek=NSkip N output blocks before writing. Also spelled oseek=N.
conv=LISTApply the comma-separated conversions below.
iflag=LISTComma-separated input flags — how the input is opened and read.
oflag=LISTComma-separated output flags — how the output is opened and written.
status=LEVELHow much to report: progress (periodic stats while copying), noxfer (final counts only), none (silent).

A size value accepts a unit suffix (K, M, G, …). So bs=4M is four mebibytes per block.

Conversions (conv=) #

ValueEffect
ucase / lcaseConvert the data to upper-case / lower-case.
swabSwap every adjacent pair of bytes.
syncPad each input block out to its full size — with zeros, or with spaces alongside block/unblock.
block / unblockConvert between newline-terminated lines and fixed-size cbs records.
sparseWhere an output block is all zeros, seek past it instead of writing it.
exclFail if the output file already exists.
nocreatFail if the output file does not already exist.
notruncDo not truncate the output file when opening it.
noerrorContinue past read errors instead of stopping.
fdatasync / fsyncFlush the data — or the data and metadata — to storage before finishing.

When dd creates the output file — an of= file that does not already exist — the new file needs a security descriptor, and it inherits one from the directory it is created in.

Input and output flags (iflag=, oflag=) #

FlagApplies toEffect
count_bytesinputInterpret count=N as a number of bytes, not blocks.
skip_bytesinputInterpret skip=N as a number of bytes.
fullblockinputWait for a full ibs of data on each read.
seek_bytesoutputInterpret seek=N as a number of bytes.
appendoutputOpen the output in append mode.
directbothUse direct I/O, bypassing the cache.
dsync / syncbothUse synchronised I/O — for data, or for data and metadata.
nonblockbothUse non-blocking I/O.
noatimebothDo not update the file's access time.
nocachebothAsk the system to drop the file's cached pages.
directorybothFail unless the file is a directory.

What dd prints #

Unless status=none is set, dd prints a summary when it finishes:

16+0 records in
16+0 records out
67108864 bytes (67 MB, 64 MiB) copied, 1.234 s, 54.4 MB/s

The records in / records out counts are written complete+partial — full-sized blocks plus any short final block. status=progress prints the last line periodically during a long copy.

Exit status #

CodeMeaning
0The copy completed.
1The copy failed — a bad operand, an I/O error, or a file that could not be opened.

df

Peios / Using Peios / Peiosutils / Files and directories

df — "disk free" — reports, for each file system, how much space it has and how much is still available.

df [options] [file...]

With no arguments, df reports on every mounted file system. Given a file, it reports on the one file system that file lives on.

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       456G  198G  235G  46% /
/dev/sda1       511M   12M  499M   3% /boot

The default columns #

Each line is one file system:

  • Filesystem — the device or source backing it.
  • Size — its total capacity.
  • Used — space in use.
  • Avail — space still available.
  • Use% — used space as a percentage.
  • Mounted on — where it is attached in the directory tree.

Choosing the units #

By default sizes are shown in blocks. These options change that.

OptionEffect
-h, --human-readableShow sizes with unit suffixes — 456G, 12M — using powers of 1024.
-H, --siLikewise, but using powers of 1000.
-B, --block-size=SIZEShow sizes scaled to SIZE — -BM reports in megabytes.
-kUse 1024-byte blocks.

Choosing what to report #

OptionEffect
-a, --allInclude dummy and otherwise-hidden file systems that df would normally skip.
-l, --localReport only local file systems, skipping network-mounted ones.
-t, --type=TYPEReport only file systems of type TYPE.
-x, --exclude-type=TYPEReport everything except file systems of type TYPE.
--totalAdd a final line giving the grand total across everything reported.

Changing the output #

OptionEffect
-i, --inodesReport inode counts — total, used, and free — instead of block space. A file system can run out of inodes while it still has space.
-T, --print-typeAdd a column showing each file system's type.
--output[=LIST]Replace the default columns with exactly the fields named in LIST. With no list, print every available field.
-P, --portabilityUse a fixed, simple column layout that does not wrap a long device name onto its own line.

Sync behaviour #

OptionEffect
--syncFlush pending writes before reading the usage figures, for the most up-to-date numbers.
--no-syncDo not flush first. This is the default and is faster.

Exit status #

CodeMeaning
0The report was produced.
1A failure — for example, a named file does not exist, or the table of file systems could not be read.

du

Peios / Using Peios / Peiosutils / Files and directories

du — "disk usage" — reports how much space files and directories occupy. Where df tells you about a whole file system, du tells you where the space has actually gone.

du [options] [file...]
$ du -sh /home/jack/projects
4.2G    /home/jack/projects

With no arguments, du works on the current directory. By default it walks the directory tree, prints the cumulative size of each directory (a directory's size includes everything under it), and ends with the total for the argument itself. Individual files are not listed unless you ask for them.

What to count and how deep #

OptionEffect
-a, --allList every file, not only directories.
-s, --summarizePrint only a single total for each argument — no per-subdirectory breakdown.
-d, --max-depth=NShow totals only down to N levels below each argument. -d 0 is the same as -s.
-S, --separate-dirsGive each directory's own size, excluding its subdirectories.
-c, --totalAdd a final grand-total line.
--inodesCount inodes used rather than space used.

What "size" means #

OptionEffect
-h, --human-readableSizes with unit suffixes — 4.2G — using powers of 1024.
--siLikewise, using powers of 1000.
-B, --block-size=SIZEScale sizes to SIZE.
-k / -mUse 1024-byte / 1024×1024-byte units.
--apparent-sizeReport the apparent size of files — how much data they contain — rather than the disk space they occupy. The two differ for sparse files and because of block rounding.
-b, --bytesReport plain byte counts. Equivalent to --apparent-size --block-size=1.

Following symbolic links #

By default du does not follow symbolic links.

OptionEffect
-L, --dereferenceFollow all symbolic links and count what they point to.
-D, -H, --dereference-argsFollow only the symbolic links named directly on the command line.
-P, --no-dereferenceFollow no symbolic links. This is the default.
-x, --one-file-systemDo not cross into a different file system while walking.
-l, --count-linksCount a hard-linked file every time it is encountered, instead of once.

Limiting the output #

OptionEffect
--exclude=PATTERNSkip files and directories whose name matches PATTERN.
--exclude-from=FILESkip everything matching any pattern listed in FILE.
-t, --threshold=SIZEShow only entries at least SIZE (or, with a negative SIZE, at most that big).
--files0-from=FTake the list of files to measure from F, NUL-separated. - means standard input.
--time[=WORD]Also show a timestamp — by default the latest modification time anywhere in the directory.
-0, --nullEnd each output line with a NUL character instead of a newline.
-v, --verboseReport extra detail about what is being processed.

Exit status #

CodeMeaning
0The report was produced.
1A file or directory could not be accessed.

truncate

Peios / Using Peios / Peiosutils / Files and directories

truncate sets a file's size to an exact figure — shrinking it, or extending it.

truncate [options] file...
$ truncate -s 0 logfile        # empty the file
$ truncate -s 1G disk.img      # make a 1 GiB file

Shrinking a file discards everything past the new size. Extending it adds space that reads back as zero bytes; that added space is a hole — it costs no actual storage until something writes real data into it, which is how truncate -s 1G can create a "1 GiB file" instantly.

By default, truncate creates a file that does not yet exist (as an empty file, then sized as asked). A file created this way needs a security descriptor, and it inherits one from the directory it is created in.

Specifying the size #

-s (--size) takes the size. A plain number sets the size outright. A unit suffix scales it: K, M, G, T, … where the bare letter is a power of 1024 (K = 1024) and the letter with B is a power of 1000 (KB = 1000).

A size may also begin with a prefix, which makes it relative to the file's current size:

PrefixMeaning
+Extend by this much.
-Reduce by this much.
<Shrink to this size only if the file is currently larger ("at most").
>Extend to this size only if the file is currently smaller ("at least").
/Round the size down to a multiple of this number.
%Round the size up to a multiple of this number.
$ truncate -s +4K notes.txt    # 4 KiB larger than it is now
$ truncate -s '<1M' notes.txt  # cap it at 1 MiB, leave smaller files alone

Options #

OptionEffect
-s, --size=SIZESet or adjust the size to SIZE, as described above.
-r, --reference=RFILEBase the size on the current size of RFILE instead of giving a number. Combine with a relative -s to mean "the size of RFILE, adjusted".
-c, --no-createDo not create a file that does not exist; skip it instead.

Exit status #

CodeMeaning
0Every file was resized.
1A file could not be resized — a bad size, or a file that could not be opened.

mktemp

Peios / Using Peios / Peiosutils / Files and directories

mktemp creates a temporary file — or, with -d, a temporary directory — gives it a name that is not already taken, and prints that name.

mktemp [options] [template]
$ mktemp
/tmp/tmp.QV8x3kZ1aR
$ mktemp -d
/tmp/tmp.7bNc0wPe2L

The reason to use mktemp rather than inventing a name yourself is safety. A script that picks a fixed name like /tmp/work has two problems: two copies of the script collide, and a name an attacker can predict is a name an attacker can interfere with before the script gets to it. mktemp chooses an unpredictable name and creates the file in the same step, so nothing can slip in between. It prints the name it used, for the script to capture:

work=$(mktemp)
# ...use "$work"...
rm "$work"

The temporary file or directory mktemp creates is a new object, and a new object needs a security descriptor; it inherits one from the directory it is created in.

Templates #

The name comes from a template — a name with a run of trailing X characters, each of which mktemp replaces with a random character. More Xs means a wider range of possible names.

$ mktemp report-XXXXXX.txt
report-a8Kp2Q.txt

With no template, mktemp uses a built-in default that places the file in the temporary directory. A template needs at least three Xs.

Options #

OptionEffect
-d, --directoryCreate a directory instead of a file.
-p, --tmpdir[=DIR]Create the temporary object inside DIR. With no DIR, use the directory named by the TMPDIR environment variable, or the system temporary directory. The template is then just the final name component.
--suffix=SUFFIXAppend SUFFIX after the random part of the name — for giving the file an extension.
-u, --dry-runDo not create anything; just print a name that would be free. This reintroduces the race the command exists to avoid — use it only when you genuinely cannot create the object yet.
-q, --quietPrint no error message if creation fails; rely on the exit status.

Exit status #

CodeMeaning
0The temporary file or directory was created; its name was printed.
1Creation failed — a bad template, or a directory that does not exist.

sd

Peios / Using Peios / Peiosutils / Files and directories

sd is the command-line tool for working with security descriptors on files. Everything this topic describes — owners, DACLs, ACEs, the SACL, integrity labels, inheritance — sd is how you read it and change it from a shell.

sd subcommand path [arguments]
$ sd show ./report.txt
$ sd allow ./report.txt alice:read
$ sd owner ./report.txt BA

Where ls -l shows a file's owner and a summary, and cp --preserve carries a descriptor across, sd is the tool that edits the descriptor directly.

The subcommands #

GroupSubcommandDoes
InspectshowPrint the descriptor on a path.
checkSimulate an access check against the path.
DACLallowAdd an allow rule for one or more principals.
denyAdd a deny rule for one or more principals.
removeDrop every DACL rule for the named principals.
AuditingauditAdd an audit rule to the SACL.
unauditDrop every SACL rule for the named principals.
OwnershipownerSet the descriptor's owner.
groupSet the descriptor's group.
IntegrityintegritySet the mandatory integrity label.
InheritanceinheritTurn inheritance protection on or off.
resetDrop the file's own rules and re-inherit from the parent.
propagatePush inheritance down to descendants.
WholesalesetReplace the entire descriptor at once.

Naming a principal #

Wherever a subcommand takes a PRINCIPAL, it accepts any of:

FormExampleMeaning
@self@selfThe user SID of the token running sd.
@owner@ownerA placeholder that the access check substitutes with the file's own owner.
A well-known labelEveryone, Administrators, LocalSystemA named built-in principal.
A two-letter aliasWD, BA, SYThe short alias for a well-known principal.
A raw SIDS-1-5-32-544Any SID, written out in full.

See SIDs for what these are.

Naming permissions #

Wherever a subcommand takes PERMS, several notations are accepted, and may be mixed:

FormExampleMeaning
Single lettersrwx, r, mr read, w write, x execute, d delete, m modify, f full, c change-permissions, o take-ownership.
Wordsread,write, modifyThe same set, spelled out.
Fine-grained namesread-data,append,traverseIndividual low-level rights, for precise rules.
Raw hex0x1F01FFAn access mask written directly.

Run letters together (rwx) or separate names with commas (read,write,execute).

Inspecting #

sd show #

Prints the descriptor on a path — the owner, the group, the DACL, the SACL.

$ sd show ./report.txt
FlagEffect
--sddlRender the descriptor as an SDDL string.
--rawRender SIDs in raw S-1-… form only.
--labelRender SIDs as their labels where known.
--allVerbose — decode every flag and show raw masks alongside.
--jsonEmit JSON.

sd check #

Simulates an access decision: "would this access be allowed?" — without performing it.

$ sd check ./report.txt write
$ sd check ./report.txt read --pid 4821 --explain
Argument / flagEffect
PERMSThe access to test for.
--pid PIDCheck against process PID's token instead of your own.
--explainShow why the decision came out as it did — the rule-by-rule walk.

sd check is the first thing to reach for when an access is denied and you do not know why. --explain walks the descriptor the same way the kernel does.

Changing the DACL #

The DACL is the list of allow and deny rules. These three subcommands edit it.

sd allow and sd deny #

Add an allow (or deny) rule for one or more PRINCIPAL:PERMS pairs.

$ sd allow ./report.txt alice:read bob:rw
$ sd deny  ./report.txt Everyone:write
FlagEffect
--flags LISTACE inheritance flags — CI container-inherit, OI object-inherit, NP no-propagate, IO inherit-only; none clears them.
--if EXPRMake it a conditional rule, applied only when EXPR is true.
--replaceDrop any existing rules for this principal and kind first, instead of appending.
--recursive, -rApply to every descendant of the path.

Remember that a deny rule, when it matches, wins over any allow — see DACL evaluation.

sd remove #

Drops every DACL rule — allow and deny — for the named principals.

$ sd remove ./report.txt bob carol
FlagEffect
--allow-emptyPermit the result to be a present-but-empty DACL, which denies everyone. Without this, sd refuses to produce one.
--recursive, -rApply to every descendant.

Auditing #

sd audit and sd unaudit #

The SACL holds audit rules — see The SACL. sd audit adds one; sd unaudit drops every SACL rule for the named principals.

$ sd audit ./secrets.db Everyone:write:failure

An audit spec is PRINCIPAL:PERMS:WHEN, where WHEN is success, failure, or both — which outcomes to log. sd audit takes the same --flags, --if, --replace, and -r options as sd allow.

Ownership #

sd owner and sd group #

Set the descriptor's owner or group SID.

$ sd owner ./report.txt alice
$ sd group ./report.txt Administrators

Both take a single PRINCIPAL and accept -r. Changing an owner is itself an access-controlled act — see Ownership.

Integrity #

sd integrity #

Sets the file's mandatory integrity label.

$ sd integrity ./report.txt high

The level is one of untrusted, low, medium, medium-plus, high, system, protected. The standard catalog is five levels (untrusted, low, medium, high, system); medium-plus (RID 8448) and protected (RID 20480) are non-standard Windows-compatibility levels — the kernel compares any S-1-16-<rid> numerically.

FlagEffect
--policy BITSThe label's policy bits, comma-separated: NW no write-up, NR no read-up, NX no execute-up.
--recursive, -rApply to every descendant.

For what the label does, see Mandatory integrity control.

Inheritance #

sd inherit #

Turns inheritance protection on or off — the + mark ls -l shows.

$ sd inherit off ./report.txt    # lock the file; stop inheriting
$ sd inherit on  ./report.txt    # let it inherit from its parent again

inherit on lets the file inherit rules from its parent directory. inherit off protects the file — it keeps its current rules and stops tracking the parent.

FlagEffect
--strip-inheritedWhen turning protection off, also drop the inherited rules already on the file.
--recursive, -rApply to every descendant.

sd reset #

Drops the file's own explicit rules and rebuilds its DACL purely from what the parent directory hands down — returning the file to "inherits everything".

sd propagate #

Pushes this directory's inheritable rules down into its descendants, refreshing what they inherit. Use it after changing a directory's rules so the children pick up the change.

See Inheritance for the full model.

Replacing the whole descriptor #

sd set #

Replaces the entire descriptor in one step.

$ sd set ./report.txt 'O:BAG:BAD:P(A;;FA;;;BA)(A;;0x1200a9;;;BU)'
Argument / flagEffect
SDDLThe new descriptor as an SDDL string. - reads it from standard input.
--binary FILEInstead of SDDL, read the raw descriptor bytes from FILE (- for standard input).
--components LISTOverride which parts of the descriptor (owner, group, DACL, SACL) the operation writes.

Common flags #

These apply across the subcommands:

FlagEffect
--recursive, -rApply the change to every descendant of the path.
--no-follow-symlinks, -POperate on a symbolic link itself, not the file it points to.
--jsonEmit JSON instead of human-readable output.

Exit status #

CodeMeaning
0The operation succeeded — or, for sd check, the access would be allowed.
non-zeroThe operation failed, the path was unreachable, or — for sd check — the access would be denied.

Peios Learn — documentation for the Peios project.

Built with Trail.