// Download and write files atomically to the file system
-// Copyright (C) 2019 Simon Ruderich
+// Copyright (C) 2019-2021 Simon Ruderich
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
"syscall"
"time"
+ "github.com/google/renameio"
"github.com/pkg/errors"
)
t = zero // force download
}
- status, body, err := fetchIfModified(file.Url, &t)
+ oldT := t
+ status, body, err := fetchIfModified(file.Url,
+ file.Username, file.Password, file.CA, &t)
if err != nil {
return err
}
if status == http.StatusNotModified {
+ if oldT.IsZero() {
+ return fmt.Errorf("status code 304 " +
+ "but did not send If-Modified-Since")
+ }
log.Printf("%q -> %q: not modified", file.Url, file.Path)
return nil
}
return fmt.Errorf("refusing to write empty file")
}
- // Write the file in an atomic fashion by creating a temporary file
- // and renaming it over the target file
-
- dir := filepath.Dir(file.Path)
- name := filepath.Base(file.Path)
-
- f, err := ioutil.TempFile(dir, "tmp-"+name+"-")
+ f, err := renameio.TempFile(filepath.Dir(file.Path), file.Path)
if err != nil {
return err
}
- defer os.Remove(f.Name())
- defer f.Close()
+ defer f.Cleanup()
- // Apply permissions/user/group from the target file, use Stat instead
- // of Lstat as only the target's permissions are relevant
+ // Apply permissions/user/group from the target file but remove the
+ // write permissions to discourage manual modifications, use Stat
+ // instead of Lstat as only the target's permissions are relevant
stat, err := os.Stat(file.Path)
if err != nil {
// We do not create the path if it doesn't exist, because we
// do not know the proper permissions
return errors.Wrapf(err, "file.path %q must exist", file.Path)
}
- err = f.Chmod(stat.Mode())
+ err = f.Chmod(stat.Mode() & ^os.FileMode(0222)) // remove write perms
if err != nil {
return err
}
if err != nil {
return err
}
- err = f.Sync()
- if err != nil {
- return err
- }
- return os.Rename(f.Name(), file.Path)
+ return f.CloseAtomicallyReplace()
}