summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/lockedfile/lockedfile_filelock.go
blob: efc66461ed2fd6a2f12ca0b0440cc3f6ddb81f73 (plain)
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
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !plan9

package lockedfile

import (
	"io/fs"
	"os"

	"cmd/go/internal/fsys"
	"cmd/go/internal/lockedfile/internal/filelock"
)

func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
	// On BSD systems, we could add the O_SHLOCK or O_EXLOCK flag to the OpenFile
	// call instead of locking separately, but we have to support separate locking
	// calls for Linux and Windows anyway, so it's simpler to use that approach
	// consistently.

	f, err := fsys.OpenFile(name, flag&^os.O_TRUNC, perm)
	if err != nil {
		return nil, err
	}

	switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
	case os.O_WRONLY, os.O_RDWR:
		err = filelock.Lock(f)
	default:
		err = filelock.RLock(f)
	}
	if err != nil {
		f.Close()
		return nil, err
	}

	if flag&os.O_TRUNC == os.O_TRUNC {
		if err := f.Truncate(0); err != nil {
			// The documentation for os.O_TRUNC says “if possible, truncate file when
			// opened”, but doesn't define “possible” (golang.org/issue/28699).
			// We'll treat regular files (and symlinks to regular files) as “possible”
			// and ignore errors for the rest.
			if fi, statErr := f.Stat(); statErr != nil || fi.Mode().IsRegular() {
				filelock.Unlock(f)
				f.Close()
				return nil, err
			}
		}
	}

	return f, nil
}

func closeFile(f *os.File) error {
	// Since locking syscalls operate on file descriptors, we must unlock the file
	// while the descriptor is still valid — that is, before the file is closed —
	// and avoid unlocking files that are already closed.
	err := filelock.Unlock(f)

	if closeErr := f.Close(); err == nil {
		err = closeErr
	}
	return err
}