Memra

Copying, moving, removing — and the permissions each one needs

◈ 10 cards

The exact permission preconditions for cp, mv and rm; what mv really does within one file system; the trailing-slash and hidden-file traps that break the obvious command.

The fixture

One small tree carries the whole lesson. Your home directory holds a project and an empty backup directory:

~/proj/alpha.txt
~/proj/.hidden
~/proj/src/main.c
~/backup/          (empty)

Note the dotfile. It is there because half of what goes wrong in this lesson goes wrong because of a dotfile.

What each operation actually needs

This is the part of file processing that surprises people, and it is the part examiners like. Every one of these operations needs execute (search) permission on every directory in the pathname before anything else is even considered — that is what lets you traverse a path at all. On top of that:

cp source dest needs read on source. For the destination the rule splits:

  • if dest does not exist yet, you need write on the destination directory, because you are creating a new name in it;
  • if dest does exist, you need write on dest itself, and you do not need write on the directory — you are changing a file's contents, not adding a name.

An existing destination is overwritten silently. cp -i prompts first; cp -f goes ahead even where a prompt or a permission problem would otherwise stop it.

mv source dest needs write and execute on the source's directory — and no permission at all on the file itself. Read that again, because it is the fact people get wrong: moving a file is an operation on the directory entry, not on the file. You need write and execute on the destination directory too, and write on the destination file if one already exists there.

rm file needs write and execute on the directory that holds it — module 4's Permissions on directories is where that rule is derived and examined, so treat this as a reminder rather than a new fact. What matters here is the consequence for file processing: rm will delete a file you have no permission on at all, pausing only to confirm, so a read-only file in a directory you own is not protected.

What mv actually does

One line of reminder, because module 2's When does a file keep its identity? owns this and derives it in full: within one file system mv moves no data and the inode number is unchanged; across file systems it copies and deletes, and the inode number changes. The reason it matters here is that the permissions mv needs follow from it — a rename edits directory entries, so the permissions checked are the directories', not the file's.

Worked example — three failures, then a success

The fastest way to learn cp's preconditions is to violate them one at a time.

$ cp nosuch.txt copy.txt
cp: cannot stat 'nosuch.txt': No such file or directory

Failure one: no source. Nothing to read.

$ ls -l secret.txt
--w------- 1 ada ada 84 Sep  1 09:12 secret.txt
$ cp secret.txt copy.txt
cp: cannot open 'secret.txt' for reading: Permission denied

Failure two: the source exists but you lack read on it. Owning a file does not help — the bits are checked, not the ownership.

$ ls -l copy.txt
-r--r--r-- 1 ada ada 12 Sep  1 09:15 copy.txt
$ cp alpha.txt copy.txt
cp: cannot create regular file 'copy.txt': Permission denied

Failure three: the destination exists and you lack write on it. Now override:

$ cp -f alpha.txt copy.txt
$ ls -l alpha.txt copy.txt
-rw-r--r-- 1 ada ada 42 Aug 28 14:03 alpha.txt
-rw-r--r-- 1 ada ada 42 Sep  1 09:17 copy.txt

-f removed the unwritable destination and recreated it, so the copy succeeded — but look at the timestamps. copy.txt is stamped now, not with alpha.txt's modification time. Add -p to carry the owner, group, permission bits and modification time across:

$ cp -fp alpha.txt copy.txt
$ ls -l alpha.txt copy.txt
-rw-r--r-- 1 ada ada 42 Aug 28 14:03 alpha.txt
-rw-r--r-- 1 ada ada 42 Aug 28 14:03 copy.txt

Identical mtimes. That difference is the entire reason -p exists, and it matters whenever anything downstream — make, a backup script, a submission deadline — reads the timestamp.

Recursive copy, and the move that loses your dotfiles

cp -r ~/proj ~/backup copies the directory and its name: afterwards ~/backup/proj exists, holding alpha.txt, .hidden and src/. To copy the contents into ~/backup instead, name the contents: cp -r ~/proj/. ~/backup.

Moving is where the dotfile bites. mv ~/proj/* ~/backup/proj/ looks like it moves everything, and ls ~/proj afterwards looks empty. It is not. ls -a ~/proj still shows .hidden, because * is expanded by the shell, and a shell glob does not match a leading dot. The file was never passed to mv at all.

readwritten~after cp -r ~/proj ~/backupprojsource, untouchedbackupprojthe name came tooalpha.txt .hidden src/
Before the command, `backup` was empty. `cp -r` copied the directory *and its name*, so the copy is at `~/backup/proj`. To land the contents directly in `~/backup`, ask for the contents: `cp -r ~/proj/. ~/backup`.
~after mv ~/proj/* ~/backup/proj/projlooks empty to ls.hiddennever passed to mvbackup/projalpha.txt src/arrived
Same tree after `mv ~/proj/* ~/backup/proj/`. Two entries moved; `.hidden` never did, because the shell expanded `*` and `*` does not match a leading dot. `ls ~/proj` reports nothing — you have to ask with `ls -a`.
NORMAL ~/memra/learn/comp-325/copying-moving-and-removing utf-8 LF