@@ -33,7 +33,9 @@ type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
3333type copyDirOpts struct {
3434 xeh XAttrErrorHandler
3535 // xex contains a set of xattrs to exclude when copying
36- xex map [string ]struct {}
36+ xex map [string ]struct {}
37+ fileSync bool
38+ dirSync bool
3739}
3840
3941type CopyDirOpt func (* copyDirOpts ) error
@@ -69,6 +71,34 @@ func WithXAttrExclude(keys ...string) CopyDirOpt {
6971 }
7072}
7173
74+ // WithCopyFileSync ensures each file copied within CopyDir is fsynced to
75+ // persistent storage before proceeding to the next file.
76+ //
77+ // By default, files are not synced. Versions prior to v0.5.0 never synced;
78+ // v0.5.0 added unconditional per-file fsync which caused a performance
79+ // regression.
80+ func WithCopyFileSync () CopyDirOpt {
81+ return func (o * copyDirOpts ) error {
82+ o .fileSync = true
83+ return nil
84+ }
85+ }
86+
87+ // WithCopyDirSync ensures full crash durability during CopyDir.
88+ // This implies file-level fsync (WithCopyFileSync) and additionally fsyncs
89+ // each directory after all its entries have been copied.
90+ //
91+ // By default, neither files nor directories are synced. Versions prior to
92+ // v0.5.0 never synced; v0.5.0 added unconditional per-file fsync which
93+ // caused a performance regression.
94+ func WithCopyDirSync () CopyDirOpt {
95+ return func (o * copyDirOpts ) error {
96+ o .fileSync = true
97+ o .dirSync = true
98+ return nil
99+ }
100+ }
101+
72102// CopyDir copies the directory from src to dst.
73103// Most efficient copy of files is attempted.
74104func CopyDir (dst , src string , opts ... CopyDirOpt ) error {
@@ -143,7 +173,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
143173 if err := os .Link (link , target ); err != nil {
144174 return fmt .Errorf ("failed to create hard link: %w" , err )
145175 }
146- } else if err := CopyFile (target , source ); err != nil {
176+ } else if err := copyFileWithOpts (target , source , o ); err != nil {
147177 return fmt .Errorf ("failed to copy files: %w" , err )
148178 }
149179 case (fileInfo .Mode () & os .ModeSymlink ) == os .ModeSymlink :
@@ -185,16 +215,54 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
185215 return err
186216 }
187217 }
188- return dr .Err ()
218+ if err := dr .Err (); err != nil {
219+ return err
220+ }
221+
222+ if o .dirSync {
223+ if err := syncDirectory (dst ); err != nil {
224+ return err
225+ }
226+ }
227+
228+ return nil
229+ }
230+
231+ func copyFileWithOpts (target , source string , o * copyDirOpts ) error {
232+ if o .fileSync {
233+ return CopyFile (target , source , WithFileSync ())
234+ }
235+ return CopyFile (target , source )
236+ }
237+
238+ type CopyFileOpt func (* copyFileConfig )
239+
240+ type copyFileConfig struct {
241+ sync bool
242+ }
243+
244+ // WithFileSync ensures the copied file is fsynced to persistent
245+ // storage before returning.
246+ //
247+ // By default, files are not synced. Versions prior to v0.5.0 never synced;
248+ // v0.5.0 added unconditional per-file fsync which caused a performance regression.
249+ func WithFileSync () CopyFileOpt {
250+ return func (c * copyFileConfig ) {
251+ c .sync = true
252+ }
189253}
190254
191255// CopyFile copies the source file to the target.
192256// The most efficient means of copying is used for the platform.
193- func CopyFile (target , source string ) error {
194- return copyFile (target , source )
257+ func CopyFile (target , source string , opts ... CopyFileOpt ) error {
258+ var cfg copyFileConfig
259+ for _ , o := range opts {
260+ o (& cfg )
261+ }
262+ return copyFile (target , source , cfg .sync )
195263}
196264
197- func openAndCopyFile (target , source string ) error {
265+ func openAndCopyFile (target , source string , sync bool ) error {
198266 src , err := os .Open (source )
199267 if err != nil {
200268 return fmt .Errorf ("failed to open source %s: %w" , source , err )
@@ -206,6 +274,13 @@ func openAndCopyFile(target, source string) error {
206274 }
207275 defer tgt .Close ()
208276
209- _ , err = io .Copy (tgt , src )
210- return err
277+ if _ , err = io .Copy (tgt , src ); err != nil {
278+ return err
279+ }
280+ if sync {
281+ if err := tgt .Sync (); err != nil {
282+ return fmt .Errorf ("failed to sync target %s: %w" , target , err )
283+ }
284+ }
285+ return nil
211286}
0 commit comments