]> git.giacomolli.uk - onechat/blob - lock.go
Run each child with its own files and loop
[onechat] / lock.go
1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "syscall"
9 )
10
11 // tokenLockFileName is the lock file of the token file in the data directory.
12 const tokenLockFileName = "auth.lock"
13
14 // errTokensInUse tells that another onechat process uses the token file.
15 var errTokensInUse = errors.New("another onechat process uses the token file, such as the daemon")
16
17 // errLocked tells that another process owns the chat.
18 var errLocked = errors.New("another process owns the chat")
19
20 // lockTokens takes the lock of the token file in dir. Each process that reads
21 // or refreshes the tokens holds it, because a refresh in one process rotates
22 // the refresh token of the other and logs it out.
23 func lockTokens(dir string) (*os.File, error) {
24 lock, err := lockFile(filepath.Join(dir, tokenLockFileName))
25 if errors.Is(err, errLocked) {
26 return nil, errTokensInUse
27 }
28
29 return lock, err
30 }
31
32 // lockFile opens the file at path and takes an exclusive lock on it. The lock
33 // stays until the file closes or the process ends.
34 func lockFile(path string) (*os.File, error) {
35 file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
36 if err != nil {
37 return nil, fmt.Errorf("open lock file: %w", err)
38 }
39
40 err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
41 if errors.Is(err, syscall.EWOULDBLOCK) {
42 err = errLocked
43 }
44
45 if err != nil {
46 return nil, errors.Join(fmt.Errorf("lock chat: %w", err), file.Close())
47 }
48
49 return file, nil
50 }