package main import ( "errors" "fmt" "os" "path/filepath" "syscall" ) // tokenLockFileName is the lock file of the token file in the data directory. const tokenLockFileName = "auth.lock" // errTokensInUse tells that another onechat process uses the token file. var errTokensInUse = errors.New("another onechat process uses the token file, such as the daemon") // errLocked tells that another process owns the chat. var errLocked = errors.New("another process owns the chat") // lockTokens takes the lock of the token file in dir. Each process that reads // or refreshes the tokens holds it, because a refresh in one process rotates // the refresh token of the other and logs it out. func lockTokens(dir string) (*os.File, error) { lock, err := lockFile(filepath.Join(dir, tokenLockFileName)) if errors.Is(err, errLocked) { return nil, errTokensInUse } return lock, err } // lockFile opens the file at path and takes an exclusive lock on it. The lock // stays until the file closes or the process ends. func lockFile(path string) (*os.File, error) { file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600) if err != nil { return nil, fmt.Errorf("open lock file: %w", err) } err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) if errors.Is(err, syscall.EWOULDBLOCK) { err = errLocked } if err != nil { return nil, errors.Join(fmt.Errorf("lock chat: %w", err), file.Close()) } return file, nil }