@@ -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,26 @@ 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+ func WithCopyFileSync () CopyDirOpt {
77+ return func (o * copyDirOpts ) error {
78+ o .fileSync = true
79+ return nil
80+ }
81+ }
82+
83+ // WithCopyDirSync ensures full crash durability during CopyDir.
84+ // This implies file-level fsync (WithCopyFileSync) and additionally fsyncs
85+ // each directory after all its entries have been copied.
86+ func WithCopyDirSync () CopyDirOpt {
87+ return func (o * copyDirOpts ) error {
88+ o .fileSync = true
89+ o .dirSync = true
90+ return nil
91+ }
92+ }
93+
7294// CopyDir copies the directory from src to dst.
7395// Most efficient copy of files is attempted.
7496func CopyDir (dst , src string , opts ... CopyDirOpt ) error {
@@ -143,7 +165,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
143165 if err := os .Link (link , target ); err != nil {
144166 return fmt .Errorf ("failed to create hard link: %w" , err )
145167 }
146- } else if err := CopyFile (target , source ); err != nil {
168+ } else if err := copyFileWithOpts (target , source , o ); err != nil {
147169 return fmt .Errorf ("failed to copy files: %w" , err )
148170 }
149171 case (fileInfo .Mode () & os .ModeSymlink ) == os .ModeSymlink :
@@ -185,16 +207,51 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
185207 return err
186208 }
187209 }
188- return dr .Err ()
210+ if err := dr .Err (); err != nil {
211+ return err
212+ }
213+
214+ if o .dirSync {
215+ if err := syncDirectory (dst ); err != nil {
216+ return err
217+ }
218+ }
219+
220+ return nil
221+ }
222+
223+ func copyFileWithOpts (target , source string , o * copyDirOpts ) error {
224+ if o .fileSync {
225+ return CopyFile (target , source , WithFileSync ())
226+ }
227+ return CopyFile (target , source )
228+ }
229+
230+ type CopyFileOpt func (* copyFileConfig )
231+
232+ type copyFileConfig struct {
233+ sync bool
234+ }
235+
236+ // WithFileSync ensures the copied file is fsynced to persistent storage before returning.
237+ // By default, files are not synced to avoid unnecessary I/O latency.
238+ func WithFileSync () CopyFileOpt {
239+ return func (c * copyFileConfig ) {
240+ c .sync = true
241+ }
189242}
190243
191244// CopyFile copies the source file to the target.
192245// The most efficient means of copying is used for the platform.
193- func CopyFile (target , source string ) error {
194- return copyFile (target , source )
246+ func CopyFile (target , source string , opts ... CopyFileOpt ) error {
247+ var cfg copyFileConfig
248+ for _ , o := range opts {
249+ o (& cfg )
250+ }
251+ return copyFile (target , source , cfg .sync )
195252}
196253
197- func openAndCopyFile (target , source string ) error {
254+ func openAndCopyFile (target , source string , sync bool ) error {
198255 src , err := os .Open (source )
199256 if err != nil {
200257 return fmt .Errorf ("failed to open source %s: %w" , source , err )
@@ -206,6 +263,13 @@ func openAndCopyFile(target, source string) error {
206263 }
207264 defer tgt .Close ()
208265
209- _ , err = io .Copy (tgt , src )
210- return err
266+ if _ , err = io .Copy (tgt , src ); err != nil {
267+ return err
268+ }
269+ if sync {
270+ if err := tgt .Sync (); err != nil {
271+ return fmt .Errorf ("failed to sync target %s: %w" , target , err )
272+ }
273+ }
274+ return nil
211275}
0 commit comments