github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snaplock/runinhibit/inhibit.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package runinhibit contains operations for establishing, removing and
    21  // querying snap run inhibition lock.
    22  package runinhibit
    23  
    24  import (
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/osutil"
    32  )
    33  
    34  // defaultInhibitDir is the directory where inhibition files are stored.
    35  const defaultInhibitDir = "/var/lib/snapd/inhibit"
    36  
    37  // InhibitDir is the directory where inhibition files are stored.
    38  // This value can be changed by calling dirs.SetRootDir.
    39  var InhibitDir = defaultInhibitDir
    40  
    41  func init() {
    42  	dirs.AddRootDirCallback(func(root string) {
    43  		InhibitDir = filepath.Join(root, defaultInhibitDir)
    44  	})
    45  }
    46  
    47  // Hint is a string representing reason for the inhibition of "snap run".
    48  type Hint string
    49  
    50  const (
    51  	// HintNotInhibited is used when "snap run" is not inhibited.
    52  	HintNotInhibited Hint = ""
    53  	// HintInhibitedGateRefresh represents inhibition of a "snap run" while gate-auto-refresh hook is run.
    54  	HintInhibitedGateRefresh Hint = "gate-refresh"
    55  	// HintInhibitedForRefresh represents inhibition of a "snap run" while a refresh change is being performed.
    56  	HintInhibitedForRefresh Hint = "refresh"
    57  )
    58  
    59  func hintFile(snapName string) string {
    60  	return filepath.Join(InhibitDir, snapName+".lock")
    61  }
    62  
    63  func openHintFileLock(snapName string) (*osutil.FileLock, error) {
    64  	return osutil.NewFileLockWithMode(hintFile(snapName), 0644)
    65  }
    66  
    67  // LockWithHint sets a persistent "snap run" inhibition lock, for the given snap, with a given hint.
    68  //
    69  // The hint cannot be empty. It should be one of the Hint constants defined in
    70  // this package. With the hint in place "snap run" will not allow the snap to
    71  // start and will block, presenting a user interface if possible.
    72  func LockWithHint(snapName string, hint Hint) error {
    73  	if len(hint) == 0 {
    74  		return fmt.Errorf("lock hint cannot be empty")
    75  	}
    76  	if err := os.MkdirAll(InhibitDir, 0755); err != nil {
    77  		return err
    78  	}
    79  	flock, err := openHintFileLock(snapName)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	defer flock.Close()
    84  
    85  	if err := flock.Lock(); err != nil {
    86  		return err
    87  	}
    88  	f := flock.File()
    89  	if err := f.Truncate(0); err != nil {
    90  		return err
    91  	}
    92  	_, err = f.WriteString(string(hint))
    93  	return err
    94  }
    95  
    96  // Unlock truncates the run inhibition lock, for the given snap.
    97  //
    98  // An empty inhibition lock means uninhibited "snap run".
    99  func Unlock(snapName string) error {
   100  	flock, err := openHintFileLock(snapName)
   101  	if os.IsNotExist(err) {
   102  		return nil
   103  	}
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer flock.Close()
   108  
   109  	if err := flock.Lock(); err != nil {
   110  		return err
   111  	}
   112  	f := flock.File()
   113  	return f.Truncate(0)
   114  }
   115  
   116  // IsLocked returns the state of the run inhibition lock for the given snap.
   117  //
   118  // It returns the current, non-empty hit if inhibition is in place. Otherwise
   119  // it returns an empty hint.
   120  func IsLocked(snapName string) (Hint, error) {
   121  	fname := filepath.Join(InhibitDir, snapName+".lock")
   122  	flock, err := osutil.OpenExistingLockForReading(fname)
   123  	if os.IsNotExist(err) {
   124  		return "", nil
   125  	}
   126  	if err != nil {
   127  		return "", err
   128  	}
   129  	defer flock.Close()
   130  
   131  	if err := flock.ReadLock(); err != nil {
   132  		return "", err
   133  	}
   134  
   135  	buf, err := ioutil.ReadAll(flock.File())
   136  	if err != nil {
   137  		return "", err
   138  	}
   139  	return Hint(string(buf)), nil
   140  }
   141  
   142  // RemoveLockFile removes the run inhibition lock for the given snap.
   143  //
   144  // This function should not be used as a substitute of Unlock, as race-free
   145  // ability to inspect the inhibition state relies on flock(2) which requires the
   146  // file to exist in the first place and non-privileged processes cannot create
   147  // it.
   148  //
   149  // The function does not fail if the inhibition lock does not exist.
   150  func RemoveLockFile(snapName string) error {
   151  	err := os.Remove(hintFile(snapName))
   152  	if err != nil && !os.IsNotExist(err) {
   153  		return err
   154  	}
   155  	return nil
   156  }