Skip to content
This repository was archived by the owner on Oct 13, 2023. It is now read-only.

Commit 3d2ef50

Browse files
authored
Merge pull request #40563 from thaJeztah/19.03_backport_fix_windows_file_handles
[19.03 backport] Use FILE_SHARE_DELETE for log files on Windows. Upstream-commit: f432f71595086d3b015dffb7b61491636e1b81ac Component: engine
2 parents a1658a0 + 64e3e12 commit 3d2ef50

4 files changed

Lines changed: 271 additions & 4 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// +build !windows
2+
3+
package loggerutils
4+
5+
import "os"
6+
7+
func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
8+
return os.OpenFile(name, flag, perm)
9+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package loggerutils
2+
3+
import (
4+
"os"
5+
"syscall"
6+
"unsafe"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
12+
if name == "" {
13+
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
14+
}
15+
h, err := syscallOpen(fixLongPath(name), flag|syscall.O_CLOEXEC, syscallMode(perm))
16+
if err != nil {
17+
return nil, errors.Wrap(err, "error opening file")
18+
}
19+
return os.NewFile(uintptr(h), name), nil
20+
}
21+
22+
// syscallOpen is copied from syscall.Open but is modified to
23+
// always open a file with FILE_SHARE_DELETE
24+
func syscallOpen(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
25+
if len(path) == 0 {
26+
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
27+
}
28+
29+
pathp, err := syscall.UTF16PtrFromString(path)
30+
if err != nil {
31+
return syscall.InvalidHandle, err
32+
}
33+
var access uint32
34+
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
35+
case syscall.O_RDONLY:
36+
access = syscall.GENERIC_READ
37+
case syscall.O_WRONLY:
38+
access = syscall.GENERIC_WRITE
39+
case syscall.O_RDWR:
40+
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
41+
}
42+
if mode&syscall.O_CREAT != 0 {
43+
access |= syscall.GENERIC_WRITE
44+
}
45+
if mode&syscall.O_APPEND != 0 {
46+
access &^= syscall.GENERIC_WRITE
47+
access |= syscall.FILE_APPEND_DATA
48+
}
49+
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
50+
var sa *syscall.SecurityAttributes
51+
if mode&syscall.O_CLOEXEC == 0 {
52+
sa = makeInheritSa()
53+
}
54+
var createmode uint32
55+
switch {
56+
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
57+
createmode = syscall.CREATE_NEW
58+
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
59+
createmode = syscall.CREATE_ALWAYS
60+
case mode&syscall.O_CREAT == syscall.O_CREAT:
61+
createmode = syscall.OPEN_ALWAYS
62+
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
63+
createmode = syscall.TRUNCATE_EXISTING
64+
default:
65+
createmode = syscall.OPEN_EXISTING
66+
}
67+
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
68+
return h, e
69+
}
70+
71+
func makeInheritSa() *syscall.SecurityAttributes {
72+
var sa syscall.SecurityAttributes
73+
sa.Length = uint32(unsafe.Sizeof(sa))
74+
sa.InheritHandle = 1
75+
return &sa
76+
}
77+
78+
// fixLongPath returns the extended-length (\\?\-prefixed) form of
79+
// path when needed, in order to avoid the default 260 character file
80+
// path limit imposed by Windows. If path is not easily converted to
81+
// the extended-length form (for example, if path is a relative path
82+
// or contains .. elements), or is short enough, fixLongPath returns
83+
// path unmodified.
84+
//
85+
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
86+
//
87+
// Copied from os.OpenFile
88+
func fixLongPath(path string) string {
89+
// Do nothing (and don't allocate) if the path is "short".
90+
// Empirically (at least on the Windows Server 2013 builder),
91+
// the kernel is arbitrarily okay with < 248 bytes. That
92+
// matches what the docs above say:
93+
// "When using an API to create a directory, the specified
94+
// path cannot be so long that you cannot append an 8.3 file
95+
// name (that is, the directory name cannot exceed MAX_PATH
96+
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
97+
//
98+
// The MSDN docs appear to say that a normal path that is 248 bytes long
99+
// will work; empirically the path must be less then 248 bytes long.
100+
if len(path) < 248 {
101+
// Don't fix. (This is how Go 1.7 and earlier worked,
102+
// not automatically generating the \\?\ form)
103+
return path
104+
}
105+
106+
// The extended form begins with \\?\, as in
107+
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
108+
// The extended form disables evaluation of . and .. path
109+
// elements and disables the interpretation of / as equivalent
110+
// to \. The conversion here rewrites / to \ and elides
111+
// . elements as well as trailing or duplicate separators. For
112+
// simplicity it avoids the conversion entirely for relative
113+
// paths or paths containing .. elements. For now,
114+
// \\server\share paths are not converted to
115+
// \\?\UNC\server\share paths because the rules for doing so
116+
// are less well-specified.
117+
if len(path) >= 2 && path[:2] == `\\` {
118+
// Don't canonicalize UNC paths.
119+
return path
120+
}
121+
if !isAbs(path) {
122+
// Relative path
123+
return path
124+
}
125+
126+
const prefix = `\\?`
127+
128+
pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
129+
copy(pathbuf, prefix)
130+
n := len(path)
131+
r, w := 0, len(prefix)
132+
for r < n {
133+
switch {
134+
case os.IsPathSeparator(path[r]):
135+
// empty block
136+
r++
137+
case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
138+
// /./
139+
r++
140+
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
141+
// /../ is currently unhandled
142+
return path
143+
default:
144+
pathbuf[w] = '\\'
145+
w++
146+
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
147+
pathbuf[w] = path[r]
148+
w++
149+
}
150+
}
151+
}
152+
// A drive's root directory needs a trailing \
153+
if w == len(`\\?\c:`) {
154+
pathbuf[w] = '\\'
155+
w++
156+
}
157+
return string(pathbuf[:w])
158+
}
159+
160+
// copied from os package for os.OpenFile
161+
func syscallMode(i os.FileMode) (o uint32) {
162+
o |= uint32(i.Perm())
163+
if i&os.ModeSetuid != 0 {
164+
o |= syscall.S_ISUID
165+
}
166+
if i&os.ModeSetgid != 0 {
167+
o |= syscall.S_ISGID
168+
}
169+
if i&os.ModeSticky != 0 {
170+
o |= syscall.S_ISVTX
171+
}
172+
// No mapping for Go's ModeTemporary (plan9 only).
173+
return
174+
}
175+
176+
func isAbs(path string) (b bool) {
177+
v := volumeName(path)
178+
if v == "" {
179+
return false
180+
}
181+
path = path[len(v):]
182+
if path == "" {
183+
return false
184+
}
185+
return os.IsPathSeparator(path[0])
186+
}
187+
188+
func volumeName(path string) (v string) {
189+
if len(path) < 2 {
190+
return ""
191+
}
192+
// with drive letter
193+
c := path[0]
194+
if path[1] == ':' &&
195+
('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
196+
'A' <= c && c <= 'Z') {
197+
return path[:2]
198+
}
199+
// is it UNC
200+
if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) &&
201+
!os.IsPathSeparator(path[2]) && path[2] != '.' {
202+
// first, leading `\\` and next shouldn't be `\`. its server name.
203+
for n := 3; n < l-1; n++ {
204+
// second, next '\' shouldn't be repeated.
205+
if os.IsPathSeparator(path[n]) {
206+
n++
207+
// third, following something characters. its share name.
208+
if !os.IsPathSeparator(path[n]) {
209+
if path[n] == '.' {
210+
break
211+
}
212+
for ; n < l; n++ {
213+
if os.IsPathSeparator(path[n]) {
214+
break
215+
}
216+
}
217+
return path[:n]
218+
}
219+
break
220+
}
221+
}
222+
}
223+
return ""
224+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package loggerutils
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"gotest.tools/assert"
10+
)
11+
12+
func TestOpenFileDelete(t *testing.T) {
13+
tmpDir, err := ioutil.TempDir("", t.Name())
14+
assert.NilError(t, err)
15+
defer os.RemoveAll(tmpDir)
16+
17+
f, err := openFile(filepath.Join(tmpDir, "test.txt"), os.O_CREATE|os.O_RDWR, 644)
18+
assert.NilError(t, err)
19+
defer f.Close()
20+
21+
assert.NilError(t, os.RemoveAll(f.Name()))
22+
}
23+
24+
func TestOpenFileRename(t *testing.T) {
25+
tmpDir, err := ioutil.TempDir("", t.Name())
26+
assert.NilError(t, err)
27+
defer os.RemoveAll(tmpDir)
28+
29+
f, err := openFile(filepath.Join(tmpDir, "test.txt"), os.O_CREATE|os.O_RDWR, 0644)
30+
assert.NilError(t, err)
31+
defer f.Close()
32+
33+
assert.NilError(t, os.Rename(f.Name(), f.Name()+"renamed"))
34+
}

components/engine/daemon/logger/loggerutils/logfile.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ type GetTailReaderFunc func(ctx context.Context, f SizeReaderAt, nLogLines int)
111111

112112
// NewLogFile creates new LogFile
113113
func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, marshaller logger.MarshalFunc, decodeFunc makeDecoderFunc, perms os.FileMode, getTailReader GetTailReaderFunc) (*LogFile, error) {
114-
log, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms)
114+
log, err := openFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms)
115115
if err != nil {
116116
return nil, err
117117
}
@@ -182,7 +182,7 @@ func (w *LogFile) checkCapacityAndRotate() error {
182182
w.rotateMu.Unlock()
183183
return err
184184
}
185-
file, err := os.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
185+
file, err := openFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
186186
if err != nil {
187187
w.rotateMu.Unlock()
188188
return err
@@ -250,7 +250,7 @@ func compressFile(fileName string, lastTimestamp time.Time) {
250250
}
251251
}()
252252

253-
outFile, err := os.OpenFile(fileName+".gz", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0640)
253+
outFile, err := openFile(fileName+".gz", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0640)
254254
if err != nil {
255255
logrus.Errorf("Failed to open or create gzip log file: %v", err)
256256
return
@@ -451,7 +451,7 @@ func decompressfile(fileName, destFileName string, since time.Time) (*os.File, e
451451
return nil, nil
452452
}
453453

454-
rs, err := os.OpenFile(destFileName, os.O_CREATE|os.O_RDWR, 0640)
454+
rs, err := openFile(destFileName, os.O_CREATE|os.O_RDWR, 0640)
455455
if err != nil {
456456
return nil, errors.Wrap(err, "error creating file for copying decompressed log stream")
457457
}

0 commit comments

Comments
 (0)