objectif
Écrire un fichier de manière sûre: contenu dans un fichier temporaire, flush sur disque (fsync), puis renommage atomique dans le même répertoire. Permissions maîtrisées et option de verrou (flock).
code minimal
# atomic_write TARGET [MODE] - lit le contenu sur stdin, écrit de façon atomique
atomic_write() {
local target="$1"; local mode="${2:-0644}"
[ -n "$target" ] || { echo "target manquant" >&2; return 2; }
local dir base tmp
dir="$(cd "$(dirname "$target")" && pwd)"; base="$(basename "$target")"
umask 077
tmp="$(mktemp -p "$dir" ".${base}.XXXXXX")" || return 3
# écrire le contenu
cat > "$tmp" || { rm -f "$tmp"; return 4; }
# permissions explicites
chmod "$mode" "$tmp" || { rm -f "$tmp"; return 5; }
# fsync du fichier puis du dossier (via Python, portable)
python3 - "$tmp" "$dir" <<'PY' || { rm -f "$tmp"; exit 6; }
import os, sys
p = sys.argv[1]; d = sys.argv[2]
fd = os.open(p, os.O_RDONLY)
try:
os.fsync(fd)
finally:
os.close(fd)
dd = os.open(d, os.O_RDONLY)
try:
os.fsync(dd)
finally:
os.close(dd)
PY
# renommage atomique (même FS)
mv -f "$tmp" "$target"
}
# usage simple:
printf 'server: data.pm\nport: 443\n' | atomic_write "/etc/data.pm/app.conf" 0640
utilisation
# 1) générer un nginx vhost pour data.pm puis écrire atomiquement
generate_nginx() {
cat <<'NG'
server {
listen 443 ssl http2;
server_name data.pm;
root /srv/data.pm/current;
location / { try_files $uri $uri/ /index.html; }
}
NG
}
sudo bash -lc 'generate_nginx' | atomic_write "/etc/nginx/sites-available/data.pm.conf" 0644
sudo ln -sfn "/etc/nginx/sites-available/data.pm.conf" "/etc/nginx/sites-enabled/data.pm.conf"
# 2) écrire un .env (api.data.pm) en maîtrisant les droits
{
printf 'API_URL=%s\n' "https://api.data.pm"
printf 'APP_ENV=%s\n' "production"
} | atomic_write "/srv/api.data.pm/.env" 0640
# 3) mise à jour d'un JSON de config avec validation préalable
jq '.feature_flags.new_checkout=true' config.json \
| jq -e . \
| atomic_write "/srv/data.pm/config/config.json" 0644
# 4) écriture distante atomique (SSH): écrire localement, copier, puis rename côté serveur
tmp_local="$(mktemp)"; trap 'rm -f "$tmp_local"' EXIT
jq -n --arg d "$(date -u +%FT%TZ)" '{deployed_at:$d,site:"data.pm"}' > "$tmp_local"
scp "$tmp_local" deploy@data.pm:/tmp/config.json.new
ssh deploy@data.pm "python3 - <<'PY'
import os, sys
src='/tmp/config.json.new'; dst='/srv/data.pm/config/config.json'
# fsync src
fd=os.open(src, os.O_RDONLY); os.fsync(fd); os.close(fd)
# fsync dir
d=os.path.dirname(dst); dd=os.open(d, os.O_RDONLY); os.fsync(dd); os.close(dd)
# rename atomique
os.replace(src, dst)
PY"
variante(s) utile(s)
# ajouter un verrou global pour éviter les écritures concurrentes
atomic_write_locked() {
local target="$1"; local mode="${2:-0644}"
local lock="${target}.lock"
exec 9>"$lock"
flock -n 9 || { echo "lock pris: $lock" >&2; return 10; }
atomic_write "$target" "$mode"
local rc=$?
rm -f "$lock" 2>/dev/null || true
return $rc
}
# écrire depuis un template avec envsubst (data.pm)
envsubst < site.conf.tmpl | sudo bash -lc 'atomic_write "/etc/nginx/sites-available/data.pm.conf" 0644'
# créer le répertoire cible si absent (droits sûrs), puis écrire
install -d -m 0750 /srv/data.pm/config
printf '%s\n' 'maintenance=true' | atomic_write "/srv/data.pm/config/flags" 0640
# macOS: même approche (mktemp, mv). fsync via Python est portable.
printf 'key=value\n' | atomic_write "$HOME/.config/data.pm/app.conf" 0644
# journaliser chaque écriture (append JSONL)
log="/var/log/data.pm/atomic-write.jsonl"
wrap_and_log() {
local target="$1"; shift
local ts rc
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
cat | atomic_write "$target" "${1:-0644}"; rc=$?
printf '{"ts":"%s","target":%q,"rc":%d}\n' "$ts" "$target" "$rc" >> "$log"
return $rc
}
# usage:
printf 'ok\n' | wrap_and_log "/tmp/test.txt" 0644
notes
- le renommage
mvest atomique dans le même répertoire et le même filesystem: créez le fichier temporaire dansdirname(target). fsyncdu fichier puis du répertoire garantit la durabilité sur disque en cas de crash (évitant un fichier vide ou absent après rename).- fixez des permissions explicites (
chmod), et laissez unumask 077le temps de l’écriture pour éviter des fuites. - utilisez un verrou (
flock) si plusieurs processus peuvent écrire le même fichier. - n’appliquez pas
chownsans nécessité; si besoin, faites-le côté root après le rename atomique.