-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
files.go
172 lines (152 loc) · 4.59 KB
/
files.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package utils
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"github.com/pkg/errors"
"go.uber.org/multierr"
)
// FileExists returns true if a file at the passed string exists.
func FileExists(name string) (bool, error) {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, errors.Wrapf(err, "failed to check if file exists %q", name)
}
return true, nil
}
// TooPermissive checks if the file has more than the allowed permissions
func TooPermissive(fileMode, maxAllowedPerms os.FileMode) bool {
return fileMode&^maxAllowedPerms != 0
}
// IsFileOwnedByChainlink attempts to read fileInfo to verify file owner
func IsFileOwnedByChainlink(fileInfo os.FileInfo) (bool, error) {
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return false, errors.Errorf("Unable to determine file owner of %s", fileInfo.Name())
}
return int(stat.Uid) == os.Getuid(), nil
}
// EnsureDirAndMaxPerms ensures that the given path exists, that it's a directory,
// and that it has permissions that are no more permissive than the given ones.
//
// - If the path does not exist, it is created
// - If the path exists, but is not a directory, an error is returned
// - If the path exists, and is a directory, but has the wrong perms, it is chmod'ed
func EnsureDirAndMaxPerms(path string, perms os.FileMode) error {
stat, err := os.Stat(path)
if err != nil && !os.IsNotExist(err) {
// Regular error
return err
} else if os.IsNotExist(err) {
// Dir doesn't exist, create it with desired perms
return os.MkdirAll(path, perms)
} else if !stat.IsDir() {
// Path exists, but it's a file, so don't clobber
return errors.Errorf("%v already exists and is not a directory", path)
} else if stat.Mode() != perms {
// Dir exists, but wrong perms, so chmod
return os.Chmod(path, stat.Mode()&perms)
}
return nil
}
// WriteFileWithMaxPerms writes `data` to `path` and ensures that
// the file has permissions that are no more permissive than the given ones.
func WriteFileWithMaxPerms(path string, data []byte, perms os.FileMode) (err error) {
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perms)
if err != nil {
return err
}
defer func() { err = multierr.Combine(err, f.Close()) }()
err = EnsureFileMaxPerms(f, perms)
if err != nil {
return
}
_, err = f.Write(data)
return
}
// EnsureFileMaxPerms ensures that the given file has permissions
// that are no more permissive than the given ones.
func EnsureFileMaxPerms(file *os.File, perms os.FileMode) error {
stat, err := file.Stat()
if err != nil {
return err
}
if stat.Mode() == perms {
return nil
}
return file.Chmod(stat.Mode() & perms)
}
// EnsureFilepathMaxPerms ensures that the file at the given filepath
// has permissions that are no more permissive than the given ones.
func EnsureFilepathMaxPerms(filepath string, perms os.FileMode) (err error) {
dst, err := os.OpenFile(filepath, os.O_RDWR, perms)
if err != nil {
return err
}
defer func() { err = multierr.Combine(err, dst.Close()) }()
return EnsureFileMaxPerms(dst, perms)
}
// FileSize repesents a file size in bytes.
type FileSize uint64
// nolint
const (
KB = 1000
MB = 1000 * KB
GB = 1000 * MB
TB = 1000 * GB
)
var (
fsregex = regexp.MustCompile(`(\d+\.?\d*)(tb|gb|mb|kb|b)?`)
fsUnitMap = map[string]int{
"tb": TB,
"gb": GB,
"mb": MB,
"kb": KB,
"b": 1,
"": 1,
}
)
// MarshalText encodes s as a human readable string.
func (s FileSize) MarshalText() ([]byte, error) {
if s >= TB {
return []byte(fmt.Sprintf("%.2ftb", float64(s)/TB)), nil
} else if s >= GB {
return []byte(fmt.Sprintf("%.2fgb", float64(s)/GB)), nil
} else if s >= MB {
return []byte(fmt.Sprintf("%.2fmb", float64(s)/MB)), nil
} else if s >= KB {
return []byte(fmt.Sprintf("%.2fkb", float64(s)/KB)), nil
}
return []byte(fmt.Sprintf("%db", s)), nil
}
// UnmarshalText parses a file size from bs in to s.
func (s *FileSize) UnmarshalText(bs []byte) error {
lc := strings.ToLower(strings.TrimSpace(string(bs)))
matches := fsregex.FindAllStringSubmatch(lc, -1)
if len(matches) != 1 || len(matches[0]) != 3 || fmt.Sprintf("%s%s", matches[0][1], matches[0][2]) != lc {
return errors.Errorf(`bad filesize expression: "%v"`, string(bs))
}
var (
num = matches[0][1]
unit = matches[0][2]
)
value, err := strconv.ParseFloat(num, 64)
if err != nil {
return errors.Errorf(`bad filesize value: "%v"`, string(bs))
}
u, ok := fsUnitMap[unit]
if !ok {
return errors.Errorf(`bad filesize unit: "%v"`, unit)
}
*s = FileSize(value * float64(u))
return nil
}
func (s FileSize) String() string {
str, _ := s.MarshalText()
return string(str)
}