From 1996c6aec6e22a53ab156b7eae83c5aaf8e8bf7a Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 31 Aug 2026 21:41:50 -0700 Subject: [PATCH] volume: open volume files with O_NOATIME (#11055) * volume server: open volume files with O_NOATIME Nothing reads the atime of .dat, .idx, .sdx, or EC files, but every needle read still dirtied the inode: even relatime writes atime on the first read after each write, so an actively written volume paid a metadata write per read/write cycle, and strictatime mounts paid one per read. Open the serving handles with O_NOATIME, falling back to a plain open when the file belongs to another owner (EPERM). Claude-Session: https://claude.ai/code/session_015uVY4diBgEn3VYQoc2eMuD * seaweed-volume: mirror the O_NOATIME volume file opens Same change as the Go volume server: serving handles for .dat, .idx, .sdx, .ecx, .ecj, and shard files open with O_NOATIME on Linux, with a plain-open fallback on EPERM. Claude-Session: https://claude.ai/code/session_015uVY4diBgEn3VYQoc2eMuD * route the tier-down and recreate .dat opens through the no-atime helper Review caught the Rust tier-down swap opening the local .dat directly. The Go swapToLocalDatBackend and the zero-length read-only .dat recreate in maybeWriteSuperBlock had the same gap: all three install long-lived serving handles. Claude-Session: https://claude.ai/code/session_015uVY4diBgEn3VYQoc2eMuD --- .../src/storage/erasure_coding/ec_shard.rs | 17 ++++---- .../src/storage/erasure_coding/ec_volume.rs | 34 ++++++++------- seaweed-volume/src/storage/mod.rs | 1 + .../src/storage/needle_map/file_pool.rs | 7 +++- seaweed-volume/src/storage/volume.rs | 41 ++++++++----------- seaweed-volume/src/storage/volume_open.rs | 27 ++++++++++++ weed/server/volume_grpc_tier_download.go | 2 +- weed/storage/backend/volume_create.go | 2 +- weed/storage/backend/volume_create_linux.go | 2 +- weed/storage/backend/volume_open.go | 11 +++++ weed/storage/backend/volume_open_linux.go | 22 ++++++++++ weed/storage/erasure_coding/ec_shard.go | 3 +- weed/storage/erasure_coding/ec_volume.go | 5 ++- weed/storage/needle_map_file_pool.go | 3 +- weed/storage/volume_checking.go | 2 +- weed/storage/volume_loading.go | 10 ++--- weed/storage/volume_super_block.go | 2 +- 17 files changed, 132 insertions(+), 59 deletions(-) create mode 100644 seaweed-volume/src/storage/volume_open.rs create mode 100644 weed/storage/backend/volume_open.go create mode 100644 weed/storage/backend/volume_open_linux.go diff --git a/seaweed-volume/src/storage/erasure_coding/ec_shard.rs b/seaweed-volume/src/storage/erasure_coding/ec_shard.rs index 5f246adcf..55ee72594 100644 --- a/seaweed-volume/src/storage/erasure_coding/ec_shard.rs +++ b/seaweed-volume/src/storage/erasure_coding/ec_shard.rs @@ -4,6 +4,7 @@ use std::fs::{self, File, OpenOptions}; use std::io::{self, Write}; use crate::storage::types::*; +use crate::storage::volume_open::open_volume_file; pub const DATA_SHARDS_COUNT: usize = 10; pub const PARITY_SHARDS_COUNT: usize = 4; @@ -50,7 +51,7 @@ impl EcVolumeShard { /// Open the shard file for reading. pub fn open(&mut self) -> io::Result<()> { let path = self.file_name(); - let file = File::open(&path)?; + let file = open_volume_file(OpenOptions::new().read(true), &path)?; self.ecd_file_size = file.metadata()?.len() as i64; self.ecd_file = Some(file); Ok(()) @@ -59,12 +60,14 @@ impl EcVolumeShard { /// Create the shard file for writing. pub fn create(&mut self) -> io::Result<()> { let path = self.file_name(); - let file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(&path)?; + let file = open_volume_file( + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true), + &path, + )?; self.ecd_file = Some(file); self.ecd_file_size = 0; Ok(()) diff --git a/seaweed-volume/src/storage/erasure_coding/ec_volume.rs b/seaweed-volume/src/storage/erasure_coding/ec_volume.rs index e44659d66..51b85c424 100644 --- a/seaweed-volume/src/storage/erasure_coding/ec_volume.rs +++ b/seaweed-volume/src/storage/erasure_coding/ec_volume.rs @@ -14,6 +14,7 @@ use crate::storage::erasure_coding::ec_locate; use crate::storage::erasure_coding::ec_shard::*; use crate::storage::needle::needle::{get_actual_size, Needle, NeedleError}; use crate::storage::types::*; +use crate::storage::volume_open::open_volume_file; /// An erasure-coded volume managing its local shards and index. pub struct EcVolume { @@ -435,7 +436,7 @@ impl EcVolume { // Matches Go which opens ecx for writing via MarkNeedleDeleted. let ecx_path = vol.ecx_file_name(); if std::path::Path::new(&ecx_path).exists() { - let file = OpenOptions::new().read(true).write(true).open(&ecx_path)?; + let file = open_volume_file(OpenOptions::new().read(true).write(true), &ecx_path)?; vol.ecx_file_size = file.metadata()?.len() as i64; vol.ecx_file = Some(file); } else if dir_idx != dir { @@ -447,7 +448,8 @@ impl EcVolume { volume_id = volume_id.0, "ecx file not found in idx dir, falling back to data dir" ); - let file = OpenOptions::new().read(true).write(true).open(&fallback_ecx)?; + let file = + open_volume_file(OpenOptions::new().read(true).write(true), &fallback_ecx)?; vol.ecx_file_size = file.metadata()?.len() as i64; vol.ecx_file = Some(file); vol.ecx_actual_dir = dir.to_string(); @@ -463,12 +465,14 @@ impl EcVolume { let ecj_base = crate::storage::volume::volume_file_name(&vol.ecx_actual_dir, collection, volume_id); let ecj_path = format!("{}.ecj", ecj_base); - let ecj_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .append(true) - .open(&ecj_path)?; + let ecj_file = open_volume_file( + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .append(true), + &ecj_path, + )?; vol.ecj_file_size = ecj_file.metadata()?.len() as i64; vol.ecj_file = Some(ecj_file); @@ -1559,12 +1563,14 @@ impl EcVolume { // in-memory deleted set (all of its contents are now materialized // in .ecx), and reset the cached size. fs::remove_file(&ecj_path)?; - let ecj_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .append(true) - .open(&ecj_path)?; + let ecj_file = open_volume_file( + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .append(true), + &ecj_path, + )?; self.ecj_file = Some(ecj_file); self.ecj_file_size = 0; if let Ok(mut set) = self.deleted_needles.write() { diff --git a/seaweed-volume/src/storage/mod.rs b/seaweed-volume/src/storage/mod.rs index 9ded27ac0..e3d192f18 100644 --- a/seaweed-volume/src/storage/mod.rs +++ b/seaweed-volume/src/storage/mod.rs @@ -10,5 +10,6 @@ pub mod super_block; pub mod types; pub mod volume; pub mod volume_idx_repair; +pub mod volume_open; pub mod volume_report; pub mod volume_report_hash; diff --git a/seaweed-volume/src/storage/needle_map/file_pool.rs b/seaweed-volume/src/storage/needle_map/file_pool.rs index d55a090ac..c5ba0fcc2 100644 --- a/seaweed-volume/src/storage/needle_map/file_pool.rs +++ b/seaweed-volume/src/storage/needle_map/file_pool.rs @@ -18,6 +18,8 @@ use std::fs::{File, OpenOptions}; use std::io; use std::sync::{Arc, Mutex, OnceLock}; +use crate::storage::volume_open::open_volume_file; + /// Descriptors the pool keeps open. Matches Go's `maxPooledIndexFiles`. pub const MAX_POOLED_INDEX_FILES: usize = 1024; @@ -68,7 +70,10 @@ impl IndexFilePool { // Opened outside the lock: a cold open blocks on disk, and holding a // process-wide mutex across it would serialize every volume's lookups. - let file = Arc::new(OpenOptions::new().read(true).write(writable).open(path)?); + let file = Arc::new(open_volume_file( + OpenOptions::new().read(true).write(writable), + path, + )?); Ok(self.insert(key, file)) } diff --git a/seaweed-volume/src/storage/volume.rs b/seaweed-volume/src/storage/volume.rs index 6373c9eda..eef63bee4 100644 --- a/seaweed-volume/src/storage/volume.rs +++ b/seaweed-volume/src/storage/volume.rs @@ -26,6 +26,7 @@ use crate::storage::needle_map::sorted_file::SortedFileNeedleMap; use crate::storage::needle_map::{CompactNeedleMap, NeedleMap, NeedleMapKind, RedbNeedleMap}; use crate::storage::super_block::{ReplicaPlacement, SuperBlock, SUPER_BLOCK_SIZE}; use crate::storage::types::*; +use crate::storage::volume_open::open_volume_file; // ============================================================================ // Errors @@ -736,12 +737,12 @@ impl Volume { let metadata = fs::metadata(&dat_path)?; // Try to open read-write; fall back to read-only - match OpenOptions::new().read(true).write(true).open(&dat_path) { + match open_volume_file(OpenOptions::new().read(true).write(true), &dat_path) { Ok(file) => { self.dat_file = Some(file); } Err(e) if e.kind() == io::ErrorKind::PermissionDenied => { - self.dat_file = Some(File::open(&dat_path)?); + self.dat_file = Some(open_volume_file(OpenOptions::new().read(true), &dat_path)?); self.no_write_or_delete = true; } Err(e) => return Err(e.into()), @@ -762,11 +763,10 @@ impl Volume { if let Some(parent) = Path::new(&dat_path).parent() { fs::create_dir_all(parent)?; } - let file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&dat_path)?; + let file = open_volume_file( + OpenOptions::new().read(true).write(true).create(true), + &dat_path, + )?; if preallocate > 0 { preallocate_file(&file, preallocate); } @@ -982,7 +982,7 @@ impl Volume { if self.no_write_or_delete { // Open read-only if Path::new(&idx_path).exists() { - let mut idx_file = File::open(&idx_path)?; + let mut idx_file = open_volume_file(OpenOptions::new().read(true), idx_path)?; let nm = CompactNeedleMap::load_from_idx(&mut idx_file, self.version())?; self.nm = Some(NeedleMap::InMemory(nm)); } else { @@ -1001,11 +1001,10 @@ impl Volume { } } else { // Open read-write (create if missing) - let idx_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&idx_path)?; + let idx_file = open_volume_file( + OpenOptions::new().read(true).write(true).create(true), + idx_path, + )?; let idx_size = trim_torn_idx_tail(&idx_file, idx_path)?; let mut idx_reader = io::BufReader::new(&idx_file); @@ -1031,7 +1030,7 @@ impl Volume { if self.no_write_or_delete { // Open read-only if Path::new(&idx_path).exists() { - let mut idx_file = File::open(&idx_path)?; + let mut idx_file = open_volume_file(OpenOptions::new().read(true), idx_path)?; let nm = RedbNeedleMap::load_from_idx(&rdb_path, &mut idx_file, self.version())?; self.nm = Some(NeedleMap::Redb(nm)); } else { @@ -1050,11 +1049,10 @@ impl Volume { } } else { // Open read-write (create if missing) - let idx_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&idx_path)?; + let idx_file = open_volume_file( + OpenOptions::new().read(true).write(true).create(true), + idx_path, + )?; let idx_size = trim_torn_idx_tail(&idx_file, idx_path)?; let mut idx_reader = io::BufReader::new(&idx_file); @@ -2696,10 +2694,7 @@ impl Volume { /// Open the local .dat as the data backend, dropping any remote backend, so reads /// are served from local disk. Mirrors Go's swapToLocalDatBackend after a tier-down. pub(crate) fn open_local_dat_backend(&mut self) -> Result<(), VolumeError> { - let file = OpenOptions::new() - .read(true) - .write(true) - .open(self.dat_path())?; + let file = open_volume_file(OpenOptions::new().read(true).write(true), self.dat_path())?; self.remote_dat_file = None; self.dat_file = Some(file); Ok(()) diff --git a/seaweed-volume/src/storage/volume_open.rs b/seaweed-volume/src/storage/volume_open.rs new file mode 100644 index 000000000..964620b94 --- /dev/null +++ b/seaweed-volume/src/storage/volume_open.rs @@ -0,0 +1,27 @@ +//! Opening volume data and index files without access-time updates. + +use std::fs::{File, OpenOptions}; +use std::io; +use std::path::Path; + +/// Open a volume data or index file with `O_NOATIME`. Nothing reads these +/// files' atime, but without the flag every needle read dirties the inode — +/// even relatime writes atime on the first read after each write, so an +/// actively written volume pays a metadata write per read/write cycle. +/// Matches Go's `backend.OpenVolumeFile`. +pub fn open_volume_file(opts: &OpenOptions, path: impl AsRef) -> io::Result { + let path = path.as_ref(); + #[cfg(target_os = "linux")] + { + use std::os::unix::fs::OpenOptionsExt; + let mut noatime = opts.clone(); + noatime.custom_flags(libc::O_NOATIME); + match noatime.open(path) { + // O_NOATIME is refused unless we own the file or hold CAP_FOWNER. + Err(e) if e.raw_os_error() == Some(libc::EPERM) => opts.open(path), + result => result, + } + } + #[cfg(not(target_os = "linux"))] + opts.open(path) +} diff --git a/weed/server/volume_grpc_tier_download.go b/weed/server/volume_grpc_tier_download.go index 5860f859a..afac16b81 100644 --- a/weed/server/volume_grpc_tier_download.go +++ b/weed/server/volume_grpc_tier_download.go @@ -125,7 +125,7 @@ func (vs *VolumeServer) VolumeTierMoveDatFromRemote(req *volume_server_pb.Volume // swapToLocalDatBackend closes the remote data backend and opens the downloaded // local .dat as a DiskFile so reads are served from local disk. func swapToLocalDatBackend(v *storage.Volume, datFileName string) error { - dataFile, err := os.OpenFile(datFileName, os.O_RDWR, 0644) + dataFile, err := backend.OpenVolumeFile(datFileName, os.O_RDWR) if err != nil { return err } diff --git a/weed/storage/backend/volume_create.go b/weed/storage/backend/volume_create.go index 4f26f8694..6a9e09ef8 100644 --- a/weed/storage/backend/volume_create.go +++ b/weed/storage/backend/volume_create.go @@ -9,7 +9,7 @@ import ( ) func CreateVolumeFile(fileName string, preallocate int64, memoryMapSizeMB uint32) (BackendStorageFile, error) { - file, e := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + file, e := OpenVolumeFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC) if e != nil { return nil, e } diff --git a/weed/storage/backend/volume_create_linux.go b/weed/storage/backend/volume_create_linux.go index ab3481fd6..92c66c694 100644 --- a/weed/storage/backend/volume_create_linux.go +++ b/weed/storage/backend/volume_create_linux.go @@ -10,7 +10,7 @@ import ( ) func CreateVolumeFile(fileName string, preallocate int64, memoryMapSizeMB uint32) (BackendStorageFile, error) { - file, e := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + file, e := OpenVolumeFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC) if e != nil { return nil, e } diff --git a/weed/storage/backend/volume_open.go b/weed/storage/backend/volume_open.go new file mode 100644 index 000000000..85f90a7ff --- /dev/null +++ b/weed/storage/backend/volume_open.go @@ -0,0 +1,11 @@ +//go:build !linux + +package backend + +import "os" + +// OpenVolumeFile opens a volume data or index file. Only Linux can suppress +// atime updates per file descriptor; elsewhere this is a plain open. +func OpenVolumeFile(fileName string, flag int) (*os.File, error) { + return os.OpenFile(fileName, flag, 0644) +} diff --git a/weed/storage/backend/volume_open_linux.go b/weed/storage/backend/volume_open_linux.go new file mode 100644 index 000000000..3915abdaf --- /dev/null +++ b/weed/storage/backend/volume_open_linux.go @@ -0,0 +1,22 @@ +//go:build linux + +package backend + +import ( + "errors" + "os" + "syscall" +) + +// OpenVolumeFile opens a volume data or index file with O_NOATIME. Nothing +// reads these files' atime, but without the flag every needle read dirties +// the inode — even relatime writes atime on the first read after each write, +// so an actively written volume pays a metadata write per read/write cycle. +func OpenVolumeFile(fileName string, flag int) (*os.File, error) { + file, err := os.OpenFile(fileName, flag|syscall.O_NOATIME, 0644) + if err != nil && errors.Is(err, syscall.EPERM) { + // O_NOATIME is refused unless we own the file or hold CAP_FOWNER. + return os.OpenFile(fileName, flag, 0644) + } + return file, err +} diff --git a/weed/storage/erasure_coding/ec_shard.go b/weed/storage/erasure_coding/ec_shard.go index 5004916e9..99a950a2c 100644 --- a/weed/storage/erasure_coding/ec_shard.go +++ b/weed/storage/erasure_coding/ec_shard.go @@ -10,6 +10,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" "github.com/seaweedfs/seaweedfs/weed/stats" + "github.com/seaweedfs/seaweedfs/weed/storage/backend" "github.com/seaweedfs/seaweedfs/weed/storage/needle" "github.com/seaweedfs/seaweedfs/weed/storage/types" ) @@ -77,7 +78,7 @@ func NewEcVolumeShard(diskType types.DiskType, dirname string, collection string baseFileName := v.FileName() // open ecd file - if v.ecdFile, e = os.OpenFile(baseFileName+ToExt(int(shardId)), os.O_RDONLY, 0644); e != nil { + if v.ecdFile, e = backend.OpenVolumeFile(baseFileName+ToExt(int(shardId)), os.O_RDONLY); e != nil { if e == os.ErrNotExist || strings.Contains(e.Error(), "no such file or directory") { return nil, os.ErrNotExist } diff --git a/weed/storage/erasure_coding/ec_volume.go b/weed/storage/erasure_coding/ec_volume.go index d14d6a3ab..e3a8d6e96 100644 --- a/weed/storage/erasure_coding/ec_volume.go +++ b/weed/storage/erasure_coding/ec_volume.go @@ -12,6 +12,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb" "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" + "github.com/seaweedfs/seaweedfs/weed/storage/backend" "github.com/seaweedfs/seaweedfs/weed/storage/idx" "github.com/seaweedfs/seaweedfs/weed/storage/needle" "github.com/seaweedfs/seaweedfs/weed/storage/types" @@ -132,7 +133,7 @@ func NewEcVolume(diskType types.DiskType, dir string, dirIdx string, collection default: return nil, fmt.Errorf("cannot open ec volume index %s.ecx (or %s.ecx): %w", localBaseFileName, sharedBaseFileName, os.ErrNotExist) } - if ev.ecxFile, err = os.OpenFile(indexBaseFileName+".ecx", os.O_RDWR, 0644); err != nil { + if ev.ecxFile, err = backend.OpenVolumeFile(indexBaseFileName+".ecx", os.O_RDWR); err != nil { return nil, fmt.Errorf("cannot open ec volume index %s.ecx: %w", indexBaseFileName, err) } ecxFi, statErr := ev.ecxFile.Stat() @@ -144,7 +145,7 @@ func NewEcVolume(diskType types.DiskType, dir string, dirIdx string, collection ev.ecxCreatedAt = ecxFi.ModTime() // open ecj file and seed the in-memory deleted set from it. - if ev.ecjFile, err = os.OpenFile(indexBaseFileName+".ecj", os.O_RDWR|os.O_CREATE, 0644); err != nil { + if ev.ecjFile, err = backend.OpenVolumeFile(indexBaseFileName+".ecj", os.O_RDWR|os.O_CREATE); err != nil { return nil, fmt.Errorf("cannot open ec volume journal %s.ecj: %v", indexBaseFileName, err) } if ecjFi, statErr := ev.ecjFile.Stat(); statErr == nil { diff --git a/weed/storage/needle_map_file_pool.go b/weed/storage/needle_map_file_pool.go index a82808644..e76b10d09 100644 --- a/weed/storage/needle_map_file_pool.go +++ b/weed/storage/needle_map_file_pool.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/hashicorp/golang-lru/v2/simplelru" + "github.com/seaweedfs/seaweedfs/weed/storage/backend" ) // Read-only volumes — cloud-tiered ones above all — outnumber writable ones by @@ -78,7 +79,7 @@ func (p *indexFilePool) borrow(name string, writable bool) (*pooledFile, error) } // Opened outside the lock: a cold open blocks on disk, and holding a // process-wide mutex across it would serialize every volume's lookups. - file, err := os.OpenFile(name, flag, 0644) + file, err := backend.OpenVolumeFile(name, flag) if err != nil { return nil, err } diff --git a/weed/storage/volume_checking.go b/weed/storage/volume_checking.go index c758152fb..bb8b2efb4 100644 --- a/weed/storage/volume_checking.go +++ b/weed/storage/volume_checking.go @@ -17,7 +17,7 @@ import ( // openIndex returns a file descriptor for the volume's index, and the index size in bytes. func (v *Volume) openIndex() (*os.File, int64, error) { idxFileName := v.FileName(".idx") - idxFile, err := os.OpenFile(idxFileName, os.O_RDONLY, 0644) + idxFile, err := backend.OpenVolumeFile(idxFileName, os.O_RDONLY) if err != nil { return nil, 0, fmt.Errorf("failed to open IDX file %s for volume %v: %v", idxFileName, v.Id, err) } diff --git a/weed/storage/volume_loading.go b/weed/storage/volume_loading.go index 74217bc90..7d6ec9abe 100644 --- a/weed/storage/volume_loading.go +++ b/weed/storage/volume_loading.go @@ -71,7 +71,7 @@ func (v *Volume) reopenIdxForWrite() error { return nil } - indexFile, err := os.OpenFile(v.FileName(".idx"), os.O_RDWR|os.O_CREATE, 0644) + indexFile, err := backend.OpenVolumeFile(v.FileName(".idx"), os.O_RDWR|os.O_CREATE) if err != nil { return fmt.Errorf("reopen %s read-write: %v", v.FileName(".idx"), err) } @@ -192,10 +192,10 @@ func (v *Volume) load(alsoLoadIndex bool, createDatIfMissing bool, needleMapKind } var dataFile *os.File if canWrite { - dataFile, err = os.OpenFile(v.FileName(".dat"), os.O_RDWR|os.O_CREATE, 0644) + dataFile, err = backend.OpenVolumeFile(v.FileName(".dat"), os.O_RDWR|os.O_CREATE) } else { glog.V(0).Infof("opening %s in READONLY mode", v.FileName(".dat")) - dataFile, err = os.Open(v.FileName(".dat")) + dataFile, err = backend.OpenVolumeFile(v.FileName(".dat"), os.O_RDONLY) v.noWriteOrDelete = true } v.lastModifiedTsSeconds = uint64(modifiedTime.Unix()) @@ -267,12 +267,12 @@ func (v *Volume) load(alsoLoadIndex bool, createDatIfMissing bool, needleMapKind var indexFile *os.File if v.noWriteOrDelete { glog.V(0).Infoln("open to read file", v.FileName(".idx")) - if indexFile, err = os.OpenFile(v.FileName(".idx"), os.O_RDONLY, 0644); err != nil { + if indexFile, err = backend.OpenVolumeFile(v.FileName(".idx"), os.O_RDONLY); err != nil { return fmt.Errorf("cannot read Volume Index %s: %v", v.FileName(".idx"), err) } } else { glog.V(1).Infoln("open to write file", v.FileName(".idx")) - if indexFile, err = os.OpenFile(v.FileName(".idx"), os.O_RDWR|os.O_CREATE, 0644); err != nil { + if indexFile, err = backend.OpenVolumeFile(v.FileName(".idx"), os.O_RDWR|os.O_CREATE); err != nil { return fmt.Errorf("cannot write Volume Index %s: %v", v.FileName(".idx"), err) } } diff --git a/weed/storage/volume_super_block.go b/weed/storage/volume_super_block.go index 73203dccd..7035d9867 100644 --- a/weed/storage/volume_super_block.go +++ b/weed/storage/volume_super_block.go @@ -26,7 +26,7 @@ func (v *Volume) maybeWriteSuperBlock(ver needle.Version) error { if e != nil && os.IsPermission(e) { //read-only, but zero length - recreate it! var dataFile *os.File - if dataFile, e = os.Create(v.DataBackend.Name()); e == nil { + if dataFile, e = backend.OpenVolumeFile(v.DataBackend.Name(), os.O_RDWR|os.O_CREATE|os.O_TRUNC); e == nil { v.DataBackend = backend.NewDiskFile(dataFile) if _, e = v.DataBackend.WriteAt(v.SuperBlock.Bytes(), 0); e == nil { v.noWriteLock.Lock()