-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfile.go
More file actions
127 lines (117 loc) · 2.85 KB
/
Copy pathfile.go
File metadata and controls
127 lines (117 loc) · 2.85 KB
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
package editpro
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
var ErrSymlink = errors.New("refusing to replace a symbolic link")
// WriteOptions controls safe file replacement.
type WriteOptions struct {
// NoSync skips syncing temporary file contents before replacement.
NoSync bool
}
// EditFile applies Replace and commits a changed result through a same-directory
// temporary file. Symbolic links are rejected to avoid replacing the link itself.
func EditFile(path string, old, replacement []byte, editOpts ReplaceOptions, writeOpts WriteOptions) (ReplaceResult, error) {
src, err := readRegularFile(path)
if err != nil {
return ReplaceResult{}, err
}
result, err := Replace(src, old, replacement, editOpts)
if err != nil || !result.Changed {
return result, err
}
if err := writeFile(path, result.Data, writeOpts); err != nil {
return ReplaceResult{}, err
}
return result, nil
}
// InsertFile applies Insert and safely commits a changed result.
func InsertFile(path string, content []byte, insertOpts InsertOptions, writeOpts WriteOptions) (InsertResult, error) {
src, err := readRegularFile(path)
if err != nil {
return InsertResult{}, err
}
result, err := Insert(src, content, insertOpts)
if err != nil || !result.Changed {
return result, err
}
if err := writeFile(path, result.Data, writeOpts); err != nil {
return InsertResult{}, err
}
return result, nil
}
func writeFile(path string, data []byte, opts WriteOptions) (err error) {
info, err := os.Lstat(path)
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
return ErrSymlink
}
if !info.Mode().IsRegular() {
return fmt.Errorf("destination is not a regular file: %s", path)
}
mode := info.Mode().Perm()
dir := filepath.Dir(path)
tmp, err := os.CreateTemp(dir, ".editpro-*")
if err != nil {
return err
}
tmpName := tmp.Name()
committed := false
defer func() {
if !committed {
_ = tmp.Close()
_ = os.Remove(tmpName)
}
}()
if err = tmp.Chmod(mode); err != nil {
return err
}
if _, err = writeAll(tmp, data); err != nil {
return err
}
if !opts.NoSync {
if err = tmp.Sync(); err != nil {
return err
}
}
if err = tmp.Close(); err != nil {
return err
}
if err = replaceFile(tmpName, path); err != nil {
return err
}
committed = true
return nil
}
func readRegularFile(path string) ([]byte, error) {
info, err := os.Lstat(path)
if err != nil {
return nil, err
}
if info.Mode()&os.ModeSymlink != 0 {
return nil, ErrSymlink
}
if !info.Mode().IsRegular() {
return nil, fmt.Errorf("source is not a regular file: %s", path)
}
return os.ReadFile(path)
}
func writeAll(w io.Writer, data []byte) (int, error) {
written := 0
for written < len(data) {
n, err := w.Write(data[written:])
written += n
if err != nil {
return written, err
}
if n == 0 {
return written, io.ErrShortWrite
}
}
return written, nil
}