1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build !plan9
6
7package lockedfile
8
9import (
10	"io/fs"
11	"os"
12
13	"cmd/go/internal/lockedfile/internal/filelock"
14)
15
16func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
17	// On BSD systems, we could add the O_SHLOCK or O_EXLOCK flag to the OpenFile
18	// call instead of locking separately, but we have to support separate locking
19	// calls for Linux and Windows anyway, so it's simpler to use that approach
20	// consistently.
21
22	f, err := os.OpenFile(name, flag&^os.O_TRUNC, perm)
23	if err != nil {
24		return nil, err
25	}
26
27	switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
28	case os.O_WRONLY, os.O_RDWR:
29		err = filelock.Lock(f)
30	default:
31		err = filelock.RLock(f)
32	}
33	if err != nil {
34		f.Close()
35		return nil, err
36	}
37
38	if flag&os.O_TRUNC == os.O_TRUNC {
39		if err := f.Truncate(0); err != nil {
40			// The documentation for os.O_TRUNC says “if possible, truncate file when
41			// opened”, but doesn't define “possible” (golang.org/issue/28699).
42			// We'll treat regular files (and symlinks to regular files) as “possible”
43			// and ignore errors for the rest.
44			if fi, statErr := f.Stat(); statErr != nil || fi.Mode().IsRegular() {
45				filelock.Unlock(f)
46				f.Close()
47				return nil, err
48			}
49		}
50	}
51
52	return f, nil
53}
54
55func closeFile(f *os.File) error {
56	// Since locking syscalls operate on file descriptors, we must unlock the file
57	// while the descriptor is still valid — that is, before the file is closed —
58	// and avoid unlocking files that are already closed.
59	err := filelock.Unlock(f)
60
61	if closeErr := f.Close(); err == nil {
62		err = closeErr
63	}
64	return err
65}
66