mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-09 23:00:58 +00:00
Stop folder when running out of disk space (fixes #2057)
& tweaks by calmh
This commit is contained in:
parent
6a58033f2b
commit
dfaa999291
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -9,6 +9,10 @@
|
|||||||
"ImportPath": "github.com/bkaradzic/go-lz4",
|
"ImportPath": "github.com/bkaradzic/go-lz4",
|
||||||
"Rev": "4f7c2045dbd17b802370e2e6022200468abf02ba"
|
"Rev": "4f7c2045dbd17b802370e2e6022200468abf02ba"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/calmh/du",
|
||||||
|
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/calmh/logger",
|
"ImportPath": "github.com/calmh/logger",
|
||||||
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||||
|
24
Godeps/_workspace/src/github.com/calmh/du/LICENSE
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/calmh/du/LICENSE
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org>
|
14
Godeps/_workspace/src/github.com/calmh/du/README.md
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/calmh/du/README.md
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
du
|
||||||
|
==
|
||||||
|
|
||||||
|
Get total and available disk space on a given volume.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
http://godoc.org/github.com/calmh/du
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Public Domain
|
21
Godeps/_workspace/src/github.com/calmh/du/cmd/du/main.go
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/calmh/du/cmd/du/main.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/calmh/du"
|
||||||
|
)
|
||||||
|
|
||||||
|
var KB = int64(1024)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage, err := du.Get(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Free:", usage.FreeBytes/(KB*KB), "MiB")
|
||||||
|
fmt.Println("Available:", usage.AvailBytes/(KB*KB), "MiB")
|
||||||
|
fmt.Println("Size:", usage.TotalBytes/(KB*KB), "MiB")
|
||||||
|
}
|
8
Godeps/_workspace/src/github.com/calmh/du/diskusage.go
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/calmh/du/diskusage.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package du
|
||||||
|
|
||||||
|
// Usage holds information about total and available storage on a volume.
|
||||||
|
type Usage struct {
|
||||||
|
TotalBytes int64 // Size of volume
|
||||||
|
FreeBytes int64 // Unused size
|
||||||
|
AvailBytes int64 // Available to a non-privileged user
|
||||||
|
}
|
24
Godeps/_workspace/src/github.com/calmh/du/diskusage_posix.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/calmh/du/diskusage_posix.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// +build !windows,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package du
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get returns the Usage of a given path, or an error if usage data is
|
||||||
|
// unavailable.
|
||||||
|
func Get(path string) (Usage, error) {
|
||||||
|
var stat syscall.Statfs_t
|
||||||
|
err := syscall.Statfs(filepath.Clean(path), &stat)
|
||||||
|
if err != nil {
|
||||||
|
return Usage{}, err
|
||||||
|
}
|
||||||
|
u := Usage{
|
||||||
|
FreeBytes: int64(stat.Bfree) * int64(stat.Bsize),
|
||||||
|
TotalBytes: int64(stat.Blocks) * int64(stat.Bsize),
|
||||||
|
AvailBytes: int64(stat.Bavail) * int64(stat.Bsize),
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/calmh/du/diskusage_unsupported.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/calmh/du/diskusage_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// +build netbsd openbsd solaris
|
||||||
|
|
||||||
|
package du
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrUnsupported = errors.New("unsupported platform")
|
||||||
|
|
||||||
|
// Get returns the Usage of a given path, or an error if usage data is
|
||||||
|
// unavailable.
|
||||||
|
func Get(path string) (Usage, error) {
|
||||||
|
return Usage{}, ErrUnsupported
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/calmh/du/diskusage_windows.go
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/calmh/du/diskusage_windows.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package du
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get returns the Usage of a given path, or an error if usage data is
|
||||||
|
// unavailable.
|
||||||
|
func Get(path string) (Usage, error) {
|
||||||
|
h := syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
c := h.MustFindProc("GetDiskFreeSpaceExW")
|
||||||
|
|
||||||
|
var u Usage
|
||||||
|
|
||||||
|
ret, _, err := c.Call(
|
||||||
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
|
||||||
|
uintptr(unsafe.Pointer(&u.FreeBytes)),
|
||||||
|
uintptr(unsafe.Pointer(&u.TotalBytes)),
|
||||||
|
uintptr(unsafe.Pointer(&u.AvailBytes)))
|
||||||
|
|
||||||
|
if ret == 0 {
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
@ -837,6 +837,7 @@ func defaultConfig(myName string) config.Configuration {
|
|||||||
ID: "default",
|
ID: "default",
|
||||||
RawPath: locations[locDefFolder],
|
RawPath: locations[locDefFolder],
|
||||||
RescanIntervalS: 60,
|
RescanIntervalS: 60,
|
||||||
|
MinDiskFreePct: 1,
|
||||||
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
|
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
13
internal/config/testdata/v11.xml
vendored
Normal file
13
internal/config/testdata/v11.xml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<configuration version="11">
|
||||||
|
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
|
||||||
|
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
|
||||||
|
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
|
||||||
|
<minDiskFreePct>1</minDiskFreePct>
|
||||||
|
</folder>
|
||||||
|
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
|
||||||
|
<address>a</address>
|
||||||
|
</device>
|
||||||
|
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
|
||||||
|
<address>b</address>
|
||||||
|
</device>
|
||||||
|
</configuration>
|
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
OldestHandledVersion = 5
|
OldestHandledVersion = 5
|
||||||
CurrentVersion = 10
|
CurrentVersion = 11
|
||||||
MaxRescanIntervalS = 365 * 24 * 60 * 60
|
MaxRescanIntervalS = 365 * 24 * 60 * 60
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,6 +74,7 @@ type FolderConfiguration struct {
|
|||||||
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
|
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
|
||||||
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
|
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
|
||||||
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
|
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
|
||||||
|
MinDiskFreePct int `xml:"minDiskFreePct" json:"minDiskFreePct"`
|
||||||
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
|
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
|
||||||
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
|
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
|
||||||
Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
|
Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
|
||||||
@ -364,6 +365,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
|
|||||||
if cfg.Version == 9 {
|
if cfg.Version == 9 {
|
||||||
convertV9V10(cfg)
|
convertV9V10(cfg)
|
||||||
}
|
}
|
||||||
|
if cfg.Version == 10 {
|
||||||
|
convertV10V11(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
// Hash old cleartext passwords
|
// Hash old cleartext passwords
|
||||||
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
|
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
|
||||||
@ -460,6 +464,14 @@ func ChangeRequiresRestart(from, to Configuration) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertV10V11(cfg *Configuration) {
|
||||||
|
// Set minimum disk free of existing folders to 1%
|
||||||
|
for i := range cfg.Folders {
|
||||||
|
cfg.Folders[i].MinDiskFreePct = 1
|
||||||
|
}
|
||||||
|
cfg.Version = 11
|
||||||
|
}
|
||||||
|
|
||||||
func convertV9V10(cfg *Configuration) {
|
func convertV9V10(cfg *Configuration) {
|
||||||
// Enable auto normalization on existing folders.
|
// Enable auto normalization on existing folders.
|
||||||
for i := range cfg.Folders {
|
for i := range cfg.Folders {
|
||||||
|
@ -92,6 +92,7 @@ func TestDeviceConfig(t *testing.T) {
|
|||||||
Pullers: 16,
|
Pullers: 16,
|
||||||
Hashers: 0,
|
Hashers: 0,
|
||||||
AutoNormalize: true,
|
AutoNormalize: true,
|
||||||
|
MinDiskFreePct: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedDevices := []DeviceConfiguration{
|
expectedDevices := []DeviceConfiguration{
|
||||||
|
@ -96,6 +96,10 @@ func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
|
|||||||
return Wrap(path, cfg), nil
|
return Wrap(path, cfg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Wrapper) ConfigPath() string {
|
||||||
|
return w.path
|
||||||
|
}
|
||||||
|
|
||||||
// Stop stops the Serve() loop. Set and Replace operations will panic after a
|
// Stop stops the Serve() loop. Set and Replace operations will panic after a
|
||||||
// Stop.
|
// Stop.
|
||||||
func (w *Wrapper) Stop() {
|
func (w *Wrapper) Stop() {
|
||||||
|
@ -45,6 +45,7 @@ const (
|
|||||||
indexBatchSize = 1000 // Either way, don't include more files than this
|
indexBatchSize = 1000 // Either way, don't include more files than this
|
||||||
reqValidationTime = time.Hour // How long to cache validation entries for Request messages
|
reqValidationTime = time.Hour // How long to cache validation entries for Request messages
|
||||||
reqValidationCacheSize = 1000 // How many entries to aim for in the validation cache size
|
reqValidationCacheSize = 1000 // How many entries to aim for in the validation cache size
|
||||||
|
minHomeDiskFreePct = 1.0 // Stop when less space than this is available on the home (config & db) disk
|
||||||
)
|
)
|
||||||
|
|
||||||
type service interface {
|
type service interface {
|
||||||
@ -1230,6 +1231,10 @@ func (m *Model) internalScanFolderSubs(folder string, subs []string) error {
|
|||||||
return errors.New("no such folder")
|
return errors.New("no such folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := m.CheckFolderHealth(folder); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_ = ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")) // Ignore error, there might not be an .stignore
|
_ = ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")) // Ignore error, there might not be an .stignore
|
||||||
|
|
||||||
// Required to make sure that we start indexing at a directory we're already
|
// Required to make sure that we start indexing at a directory we're already
|
||||||
@ -1658,6 +1663,10 @@ func (m *Model) BringToFront(folder, file string) {
|
|||||||
// CheckFolderHealth checks the folder for common errors and returns the
|
// CheckFolderHealth checks the folder for common errors and returns the
|
||||||
// current folder error, or nil if the folder is healthy.
|
// current folder error, or nil if the folder is healthy.
|
||||||
func (m *Model) CheckFolderHealth(id string) error {
|
func (m *Model) CheckFolderHealth(id string) error {
|
||||||
|
if free, err := osutil.DiskFreePercentage(m.cfg.ConfigPath()); err == nil && free < minHomeDiskFreePct {
|
||||||
|
return errors.New("out of disk space")
|
||||||
|
}
|
||||||
|
|
||||||
folder, ok := m.cfg.Folders()[id]
|
folder, ok := m.cfg.Folders()[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("folder does not exist")
|
return errors.New("folder does not exist")
|
||||||
@ -1673,6 +1682,8 @@ func (m *Model) CheckFolderHealth(id string) error {
|
|||||||
err = errors.New("folder path missing")
|
err = errors.New("folder path missing")
|
||||||
} else if !folder.HasMarker() {
|
} else if !folder.HasMarker() {
|
||||||
err = errors.New("folder marker missing")
|
err = errors.New("folder marker missing")
|
||||||
|
} else if free, errDfp := osutil.DiskFreePercentage(folder.Path()); errDfp == nil && free < float64(folder.MinDiskFreePct) {
|
||||||
|
err = errors.New("out of disk space")
|
||||||
}
|
}
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
// If we don't have any files in the index, and the directory
|
// If we don't have any files in the index, and the directory
|
||||||
|
@ -437,6 +437,7 @@ func (p *rwFolder) pullerIteration(ignores *ignore.Matcher) int {
|
|||||||
// !!!
|
// !!!
|
||||||
|
|
||||||
changed := 0
|
changed := 0
|
||||||
|
pullFileSize := int64(0)
|
||||||
|
|
||||||
fileDeletions := map[string]protocol.FileInfo{}
|
fileDeletions := map[string]protocol.FileInfo{}
|
||||||
dirDeletions := []protocol.FileInfo{}
|
dirDeletions := []protocol.FileInfo{}
|
||||||
@ -485,6 +486,7 @@ func (p *rwFolder) pullerIteration(ignores *ignore.Matcher) int {
|
|||||||
default:
|
default:
|
||||||
// A new or changed file or symlink. This is the only case where we
|
// A new or changed file or symlink. This is the only case where we
|
||||||
// do stuff concurrently in the background
|
// do stuff concurrently in the background
|
||||||
|
pullFileSize += file.Size()
|
||||||
p.queue.Push(file.Name, file.Size(), file.Modified)
|
p.queue.Push(file.Name, file.Size(), file.Modified)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,6 +494,17 @@ func (p *rwFolder) pullerIteration(ignores *ignore.Matcher) int {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check if we are able to store all files on disk
|
||||||
|
if pullFileSize > 0 {
|
||||||
|
folder, ok := p.model.cfg.Folders()[p.folder]
|
||||||
|
if ok {
|
||||||
|
if free, err := osutil.DiskFreeBytes(folder.Path()); err == nil && free < pullFileSize {
|
||||||
|
l.Infof("Puller (folder %q): insufficient disk space available to pull %d files (%.2fMB)", p.folder, changed, float64(pullFileSize)/1024/1024)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reorder the file queue according to configuration
|
// Reorder the file queue according to configuration
|
||||||
|
|
||||||
switch p.order {
|
switch p.order {
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/calmh/du"
|
||||||
"github.com/syncthing/syncthing/lib/sync"
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -210,3 +211,13 @@ func init() {
|
|||||||
func IsWindowsExecutable(path string) bool {
|
func IsWindowsExecutable(path string) bool {
|
||||||
return execExts[strings.ToLower(filepath.Ext(path))]
|
return execExts[strings.ToLower(filepath.Ext(path))]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DiskFreeBytes(path string) (free int64, err error) {
|
||||||
|
u, err := du.Get(path)
|
||||||
|
return u.FreeBytes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DiskFreePercentage(path string) (freePct float64, err error) {
|
||||||
|
u, err := du.Get(path)
|
||||||
|
return (float64(u.FreeBytes) / float64(u.TotalBytes)) * 100, err
|
||||||
|
}
|
||||||
|
@ -164,3 +164,18 @@ func TestInWritableDirWindowsRename(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiskUsage(t *testing.T) {
|
||||||
|
free, err := osutil.DiskFreePercentage(".")
|
||||||
|
if err != nil {
|
||||||
|
if runtime.GOOS == "netbsd" ||
|
||||||
|
runtime.GOOS == "openbsd" ||
|
||||||
|
runtime.GOOS == "solaris" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
t.Errorf("Unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
if free < 1 {
|
||||||
|
t.Error("Disk is full?", free)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user