github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/updater/service/pid.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"syscall"
    11  )
    12  
    13  // LockPIDFile manages a lock file containing the PID for the current process.
    14  type LockPIDFile struct {
    15  	name string
    16  	file *os.File
    17  	log  Log
    18  }
    19  
    20  // NewLockPIDFile creates a LockPIDFile for filename name.
    21  func NewLockPIDFile(name string, log Log) *LockPIDFile {
    22  	return &LockPIDFile{name: name, log: log}
    23  }
    24  
    25  // Lock writes the pid to filename after acquiring a lock on the file.
    26  // When the process exits, the lock will be released.
    27  func (f *LockPIDFile) Lock() (err error) {
    28  	// make the parent directory
    29  	_, err = os.Stat(filepath.Dir(f.name))
    30  	if os.IsNotExist(err) {
    31  		err = os.MkdirAll(filepath.Dir(f.name), 0700)
    32  		if err != nil {
    33  			return err
    34  		}
    35  	} else if err != nil {
    36  		return err
    37  	}
    38  
    39  	if f.file, err = os.OpenFile(f.name, os.O_CREATE|os.O_RDWR, 0600); err != nil {
    40  		return err
    41  	}
    42  
    43  	// LOCK_EX = exclusive
    44  	// LOCK_NB = nonblocking
    45  	if err = syscall.Flock(int(f.file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
    46  		f.file.Close()
    47  		f.file = nil
    48  		return err
    49  	}
    50  
    51  	pid := os.Getpid()
    52  	fmt.Fprintf(f.file, "%d", pid)
    53  	if err = f.file.Sync(); err != nil {
    54  		return err
    55  	}
    56  
    57  	f.log.Debugf("Locked pidfile %s for pid=%d", f.name, pid)
    58  
    59  	return nil
    60  }
    61  
    62  // Close releases the lock by closing and removing the file.
    63  func (f *LockPIDFile) Close() (err error) {
    64  	if f.file != nil {
    65  		if e1 := f.file.Close(); e1 != nil {
    66  			f.log.Warningf("Error closing pid file: %s\n", e1)
    67  		}
    68  		f.log.Debugf("Cleaning up pidfile %s", f.name)
    69  		if err = os.Remove(f.name); err != nil {
    70  			f.log.Warningf("Error removing pidfile: %s\n", err)
    71  		}
    72  	}
    73  	return
    74  }