Перейти к содержанию

Команды mv и rm

Как работает mv

mv перемещает или переименовывает файл. Основной системный вызов — rename:

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

Если источник и назначение находятся в одной файловой системе, rename просто перезаписывает запись в директории — имя меняется атомарно, данные на диске не копируются. Это делает операцию очень быстрой и безопасной с точки зрения целостности.

Если файловые системы разные (например, перемещение с одного раздела на другой), классическая mv выполняет следующие шаги:

  1. Копирование содержимого файла (эквивалентно cp);
  2. Удаление исходного файла через unlink.

Атомарность в этом случае не гарантируется.

rename также работает с директориями: если newpath — существующая пустая директория, она заменяется.

Как работает rm

rm удаляет файл, вызывая unlink:

#include <unistd.h>

int unlink(const char *pathname);

unlink выполняет две вещи:

  • удаляет запись (имя → inode) из родительской директории;
  • уменьшает счётчик жёстких ссылок st_nlink у inode.

Данные файла реально освобождаются только когда st_nlink достигает нуля и нет ни одного открытого дескриптора на этот inode. Именно поэтому можно удалить файл, пока он открыт другим процессом — процесс продолжит читать его до закрытия дескриптора.

Для удаления директорий используется отдельный системный вызов rmdir, который работает только с пустыми каталогами:

#include <unistd.h>

int rmdir(const char *pathname);

Реализация mv

#include <stdio.h>

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s src dst\n", argv[0]);
        return 1;
    }

    if (rename(argv[1], argv[2]) == -1) {
        perror("rename");
        return 1;
    }
    return 0;
}

Реализация rm

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s file...\n", argv[0]);
        return 1;
    }

    int status = 0;
    for (int i = 1; i < argc; ++i) {
        if (unlink(argv[i]) == -1) {
            perror(argv[i]);
            status = 1;
        }
    }
    return status;
}

renameat2 и атомарная замена

Linux-специфичный renameat2 поддерживает дополнительные флаги:

// Флаги для renameat2 (linux/fs.h):
// RENAME_NOREPLACE — не перезаписывать newpath если он существует (EEXIST)
// RENAME_EXCHANGE  — атомарно поменять местами oldpath и newpath
// RENAME_WHITEOUT  — для overlayfs: оставить "белую дыру" на месте oldpath

Флаг RENAME_EXCHANGE позволяет атомарно поменять местами два файла — ни в один момент времени один из них не исчезает. Это полезно для атомарного обновления конфигурационных файлов: записать новый файл под временным именем, затем поменять местами.

Флаг RENAME_NOREPLACE эквивалентен link + unlink, но в одном системном вызове: переименовать только если назначения нет.

Удаление файлов, открытых процессами

unlink удаляет имя из директории, но не освобождает данные немедленно, если на inode есть открытые дескрипторы. Данные освобождаются только когда счётчик ссылок st_nlink достигает нуля и нет открытых файловых описаний. Это используется на практике: создать временный файл, сразу unlink его (чтобы не оставить мусор), а продолжать работать через дескриптор:

int fd = open("/tmp/tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0600);
unlink("/tmp/tmpfile");   // имя удалено, но fd остаётся рабочим
// ... работаем с fd ...
close(fd);                // данные освобождаются только здесь

Связанные темы

Источники

  • man 2 rename — системный вызов rename
  • man 2 unlink — удаление файла
  • man 2 rmdir — удаление пустой директории
  • man 2 renameat — вариант rename с дескрипторами директорий
  • man 2 renameat2 — атомарная замена и дополнительные флаги