1// +build !windows,!plan9
2
3package pidfile
4
5import (
6	"fmt"
7	"io"
8	"os"
9	"sync"
10	"syscall"
11)
12
13var (
14	pidFile     *os.File
15	pidFileName string
16)
17
18type pidfile struct {
19	once sync.Once
20	f    *os.File
21	path string
22}
23
24func (p *pidfile) Close() error {
25	p.once.Do(func() {
26		// Try and remove file, don't care if it fails.
27		os.Remove(p.path)
28
29		p.f.Close()
30		p.f = nil
31	})
32
33	return nil
34}
35
36// Opens and locks a file and writes the current PID to it. The file is kept open
37// until you close the returned interface, at which point it is deleted. It may also
38// be deleted if the program exits without closing the returned interface.
39func Open(path string) (io.Closer, error) {
40	return OpenWith(path, fmt.Sprintf("%d\n", os.Getpid()))
41}
42
43// Opens and locks a file and writes body to it. The file is kept open until
44// you close the returned interface, at which point it is deleted. It may also
45// be deleted if the program exits without closing the returned interface.
46func OpenWith(path, body string) (io.Closer, error) {
47	f, err := open(path)
48	if err != nil {
49		return nil, err
50	}
51
52	_, err = f.WriteString(body)
53	if err != nil {
54		f.Close()
55		return nil, err
56	}
57
58	return &pidfile{
59		f:    f,
60		path: path,
61	}, nil
62}
63
64func open(path string) (*os.File, error) {
65	var f *os.File
66	var err error
67
68	for {
69		f, err = os.OpenFile(path,
70			syscall.O_RDWR|syscall.O_CREAT|syscall.O_EXCL, 0644)
71		if err != nil {
72			if !os.IsExist(err) {
73				return nil, err
74			}
75
76			f, err = os.OpenFile(path, syscall.O_RDWR, 0644)
77			if err != nil {
78				if os.IsNotExist(err) {
79					continue
80				}
81				return nil, err
82			}
83		}
84
85		err = syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &syscall.Flock_t{
86			Type: syscall.F_WRLCK,
87		})
88		if err != nil {
89			f.Close()
90			return nil, err
91		}
92
93		st1 := syscall.Stat_t{}
94		err = syscall.Fstat(int(f.Fd()), &st1) // ffs
95		if err != nil {
96			f.Close()
97			return nil, err
98		}
99
100		st2 := syscall.Stat_t{}
101		err = syscall.Stat(path, &st2)
102		if err != nil {
103			f.Close()
104
105			if os.IsNotExist(err) {
106				continue
107			}
108
109			return nil, err
110		}
111
112		if st1.Ino != st2.Ino {
113			f.Close()
114			continue
115		}
116
117		break
118	}
119
120	return f, nil
121}
122