fallocate & truncate

Published: 14-02-2017

Updated: 17-02-2017

By: Maxime de Roucy

tags: fallocate truncate

Dans cette article j’essaie de résumer et d’expliquer les possibilitées offertes par fallocate (util-linux) et truncate (coreutils).

truncate permet de modifier la fin d’un fichier, réduire ou augmenter la taille d’un fichier. fallocate permet de modifier n’importe quelle bloque d’un fichier.

truncate

Dans cette section j’interprète les résultats brutes sur truncate, obtenus lors des tests.

fichier non ouvert

init
état du fichier: <data:truc><data:much>
action: truncate
état du fichier: <data:truc>
action: écriture en fin de fichier
état du fichier: <data:truc><data:plop>

fichier ouvert en écriture simple (non append)

init
état du fichier, et « seek » du processus: <data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier.
action: truncate
état du fichier, et « seek » du processus: <data:truc>-----------|
« seek » du processus: 2 blocs après le début du fichier.
Note: le seek pointe sur une position qui n’existe pas dans le fichier.
action: écriture en fin de fichier
état du fichier, et « seek » du processus: <data:truc><metadata:\0><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.
Note: en début d’écriture, le noyau enregistre des \0 sous forme de métadata pour combler l’écart entre la fin du fichier et le seek ; transformant le fichier en sparse file.

fichier ouvert en écriture append

init
état du fichier: <data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier.
action: truncate
état du fichier: <data:truc>
action: écriture
état du fichier: <data:truc><data:plop>

fallocate

Dans cette section j’interprète les résultats brutes sur fallocate, obtenus lors des tests.

collapse-range

L’option « –collapse-range » n’est pas supportée sur btrfs.

fichier non ouvert

init
état du fichier: <data:truc><data:much>
action: collapse-range
état du fichier: <data:much>
action: écriture en fin de fichier
état du fichier: <data:much><data:plop>

fichier ouvert en écriture simple (non append)

init
état du fichier, et « seek » du processus: <data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier.
action: collapse-range
état du fichier, et « seek » du processus: <data:much>-----------|
« seek » du processus: 2 blocs après le début du fichier.
Note: le seek pointe sur une position qui n’existe pas dans le fichier.
action: écriture
état du fichier, et « seek » du processus: <data:much><metadata:\0><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.
Note: en début d’écriture, le noyau enregistre des \0 sous forme de métadata pour combler l’écart entre la fin du fichier et le seek ; transformant le fichier en sparse file.

fichier ouvert en écriture append

init
état du fichier: <data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier.
action: collapse-range
état du fichier: <data:much>
action: écriture
état du fichier: <data:much><data:plop>

punch-hole

fichier non ouvert

init
état du fichier: <data:truc><data:much>
action: punch-hole
état du fichier: <metadata:\0><data:much>
action: écriture en fin de fichier
état du fichier: <metadata:\0><data:much><data:plop>

fichier ouvert en écriture simple (non append)

init
état du fichier, et « seek » du processus: <data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier.
action: punch-hole
état du fichier, et « seek » du processus: <metadata:\0><data:much>|
« seek » du processus: 2 blocs après le début du fichier.
action: écriture
état du fichier, et « seek » du processus: <metadata:\0><data:much><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.

fichier ouvert en écriture append

init:
état du fichier: <data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier.
action: punch-hole
état du fichier: <metadata:\0><data:much>
action: écriture
état du fichier: <metadata:\0><data:much><data:plop>

zero-range

L’option « –zero-range » n’est pas supportée sur btrfs.

fichier non ouvert

init
état du fichier: <data:truc><data:much>
action: collapse-range
état du fichier: <data:\0><data:much>
action: écriture en fin de fichier
état du fichier: <data:\0><data:much><data:plop>

fichier ouvert en écriture simple (non append)

init
état du fichier, et « seek » du processus: <data:truc><data:much>|
« seek » du processus: 2 blocs après le début du fichier.
action: zero-range
état du fichier, et « seek » du processus: <data:\0><data:much>|
« seek » du processus: 2 blocs après le début du fichier.
action: écriture
état du fichier, et « seek » du processus: <data:\0><data:much><data:plop>|
« seek » du processus: 3 blocs après le début du fichier.

fichier ouvert en écriture append

init
état du fichier: <data:truc><data:much>
Note: le processus n’a pas de « seek » sur le fichier, car il l’a ouvert en mode append. De toute façon il écrit à la fin du fichier.
action: zero-range
état du fichier: <data:\0><data:much>
action: écriture
état du fichier: <data:\0><data:much><data:plop>

Note sur l’ouverture des fichiers

seek

Dans cet article, j’appel la position d’écriture d’un processus sur un fichier son « seek ». C’est un abut de language. En fait « seek » (au plutôt lseek), dans de nombreux languages de programmation, est le nom donné à la fonction permettant de modifier cette position.

fichier ouvert en écriture simple (non append)

On peut savoir qu’un fichier est ouvert en écriture « simple » (non append) par un processus de pid P en observant les flags d’ouvertures de son file discriptor dans /proc/<P>/fdinfo/…. Pour faire simple, si les flags match l’expression rationel ^\d{4}0\d{2}[12]$ alors le fichier est ouvert en écriture « simple ».

fichier ouvert en écriture append

On peut savoir qu’un fichier est ouvert en écriture « append » par un processus de pid P en observant les flags d’ouvertures de son file discriptor dans /proc/<P>/fdinfo/…. Pour faire simple, si les flags match l’expression rationel ^\d{4}2\d{2}[12]$ alors le fichier est ouvert en écriture « simple ».

C/C++

En C++ un fichier ouvert en « écriture simple » est un fichier qui a été ouvert avec le flag « out » mais pas le flag « app ». Un fichier ouvert en « écriture/append » a été ouvert avec les flag « out » et « app ».

Tests

Pour comprendre le fonctionnement d’un outil, le mieu est parfois, tous simplement, de le tester. J’ai donc créé un script de test (appelé tests.sh) et l’ai fait tourner sur les deux filesystem que j’utilise régulièrement : btrfs et ext4.

#!/bin/bash

export LC_ALL=C
trap 'rm -f  8KiB_init_file testfile fifo_input' EXIT

echo -n -e "\n## les bloques de mon dd font 4096B (4KiB) j'utilise donc des multiples de cette valeurs\n\n"

set -x

dd if=/dev/urandom of=8KiB_init_file bs=8KiB count=1 &> /dev/null
du --block-size=1 --apparent-size 8KiB_init_file
du --block-size=1 8KiB_init_file

function print_size {
	sleep 1
	du --block-size=1 --apparent-size $1
	du --block-size=1 $1
	dd if=/dev/urandom of=$2 bs=4KiB count=1 conv=notrunc oflag=append &> /dev/null
	sleep 1
	du --block-size=1 --apparent-size $1
	du --block-size=1 $1
}

function test_collapse-range {
	sleep 1
	fallocate --collapse-range --offset 0 --length 4KiB $1 && \
	print_size $1 $2
	echo
}

function test_punch-hole {
	sleep 1
	fallocate --punch-hole --offset 0 --length 4KiB $1 && \
	print_size $1 $2
	echo
}

function test_zero-range {
	sleep 1
	fallocate --zero-range --offset 0 --length 4KiB $1 && \
	print_size $1 $2
	echo
}

function test_truncate {
	sleep 1
	truncate -s 4K $1 && \
	print_size $1 $2
	echo
}

echo -n -e "\n##########\n## test sur fichier non ouvert\n##########\n\n"

cp 8KiB_init_file testfile
test_collapse-range testfile testfile
cp 8KiB_init_file testfile
test_punch-hole testfile testfile
cp 8KiB_init_file testfile
test_zero-range testfile testfile
cp 8KiB_init_file testfile
test_truncate testfile testfile

echo -n -e "\n##########\n## test sur fichier ouvert (non append)\n##########\n\n"

mkfifo fifo_input

cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_collapse-range testfile fifo_input

cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_punch-hole testfile fifo_input

cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_zero-range testfile fifo_input

cp 8KiB_init_file testfile
./open fifo_input testfile &> /dev/null &
test_truncate testfile fifo_input

echo -n -e "\n##########\n## test sur fichier ouvert (append)\n##########\n\n"

cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_collapse-range testfile fifo_input

cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_punch-hole testfile fifo_input

cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_zero-range testfile fifo_input

cp 8KiB_init_file testfile
./open -append fifo_input testfile &> /dev/null &
test_truncate testfile fifo_input

open est un petit programe créé pour cette démonstration, il ouvre testfile, se déplace à la fin du fichier est y écrit ce qu’il lit dans fifo. L’option « -append » permet de faire en sorte qu’il ouvre testfile en mode append.

ext4

Voici les résultats obtenu sur ext4.

max@test-host $ ./tests.sh

## les bloques de mon dd font 4096B (4KiB) j'utilise donc des multiples de cette valeurs

+ dd if=/dev/urandom of=8KiB_init_file bs=8KiB count=1
+ du --block-size=1 --apparent-size 8KiB_init_file
8192    8KiB_init_file
+ du --block-size=1 8KiB_init_file
8192    8KiB_init_file
+ echo -n -e '\n##########\n## test sur fichier non ouvert\n##########\n\n'

##########
## test sur fichier non ouvert
##########

+ cp 8KiB_init_file testfile
+ test_collapse-range testfile testfile
+ sleep 1
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_punch-hole testfile testfile
+ sleep 1
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_zero-range testfile testfile
+ sleep 1
+ fallocate --zero-range --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
12288   testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_truncate testfile testfile
+ sleep 1
+ truncate -s 4K testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ echo -n -e '\n##########\n## test sur fichier ouvert (non append)\n##########\n\n'

##########
## test sur fichier ouvert (non append)
##########

+ mkfifo fifo_input
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ ./open fifo_input testfile
+ sleep 1
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
12288   testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ echo -n -e '\n##########\n## test sur fichier ouvert (append)\n##########\n\n'

##########
## test sur fichier ouvert (append)
##########

+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
12288   testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ rm -f 8KiB_init_file testfile fifo_input

brtfs

Voici les résultats obtenu sur btrfs.

max@test-host $ ./tests.sh

## les bloques de mon dd font 4096B (4KiB) j'utilise donc des multiples de cette valeurs

+ dd if=/dev/urandom of=8KiB_init_file bs=8KiB count=1
+ du --block-size=1 --apparent-size 8KiB_init_file
8192    8KiB_init_file
+ du --block-size=1 8KiB_init_file
8192    8KiB_init_file
+ echo -n -e '\n##########\n## test sur fichier non ouvert\n##########\n\n'

##########
## test sur fichier non ouvert
##########

+ cp 8KiB_init_file testfile
+ test_collapse-range testfile testfile
+ sleep 1
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo

+ cp 8KiB_init_file testfile
+ test_punch-hole testfile testfile
+ sleep 1
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_zero-range testfile testfile
+ sleep 1
+ fallocate --zero-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo

+ cp 8KiB_init_file testfile
+ test_truncate testfile testfile
+ sleep 1
+ truncate -s 4K testfile
+ print_size testfile testfile
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=testfile bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ echo -n -e '\n##########\n## test sur fichier ouvert (non append)\n##########\n\n'

##########
## test sur fichier ouvert (non append)
##########

+ mkfifo fifo_input
+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo

+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
10240   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo

+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
10240   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ echo -n -e '\n##########\n## test sur fichier ouvert (append)\n##########\n\n'

##########
## test sur fichier ouvert (append)
##########

+ cp 8KiB_init_file testfile
+ test_collapse-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --collapse-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo

+ cp 8KiB_init_file testfile
+ test_punch-hole testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --punch-hole --offset 0 --length 4KiB testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
12288   testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ cp 8KiB_init_file testfile
+ test_zero-range testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ fallocate --zero-range --offset 0 --length 4KiB testfile
fallocate: fallocate failed: Operation not supported
+ echo

+ cp 8KiB_init_file testfile
+ test_truncate testfile fifo_input
+ sleep 1
+ ./open -append fifo_input testfile
+ truncate -s 4K testfile
+ print_size testfile fifo_input
+ sleep 1
+ du --block-size=1 --apparent-size testfile
4096    testfile
+ du --block-size=1 testfile
4096    testfile
+ dd if=/dev/urandom of=fifo_input bs=4KiB count=1 conv=notrunc oflag=append
+ sleep 1
+ du --block-size=1 --apparent-size testfile
8192    testfile
+ du --block-size=1 testfile
8192    testfile
+ echo

+ rm -f 8KiB_init_file testfile fifo_input