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 }