mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
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
This commit is contained in:
@@ -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(())
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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<Path>) -> io::Result<File> {
|
||||
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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user