mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00
549 lines
13 KiB
Go
549 lines
13 KiB
Go
/*
|
|
|
|
Parts of this file are a heavily modified C to Go
|
|
translation of BSD's /usr/src/lib/libc/gen/setmode.c
|
|
that contains the following copyright notice:
|
|
|
|
* Copyright (c) 1989, 1993, 1994
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Dave Borman at Cray Research, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
// Package mode provides a native Go implementation of BSD's setmode and getmode
|
|
// which can be used to modify the mode bits of an os.FileMode value based on
|
|
// a symbolic value as described by the Unix chmod command.
|
|
//
|
|
// For a full description of the mode string see chmod(1).
|
|
// Some examples include:
|
|
//
|
|
// 644 make a file readable by anyone and writable by the owner
|
|
// only.
|
|
//
|
|
// go-w deny write permission to group and others.
|
|
//
|
|
// =rw,+X set the read and write permissions to the usual defaults,
|
|
// but retain any execute permissions that are currently set.
|
|
//
|
|
// +X make a directory or file searchable/executable by everyone
|
|
// if it is already searchable/executable by anyone.
|
|
//
|
|
// 755
|
|
// u=rwx,go=rx
|
|
// u=rwx,go=u-w make a file readable/executable by everyone and writable by
|
|
// the owner only.
|
|
//
|
|
// go= clear all mode bits for group and others.
|
|
//
|
|
// go=u-w set the group bits equal to the user bits, but clear the
|
|
// group write bit.
|
|
//
|
|
// See Also:
|
|
//
|
|
// setmode(3): https://www.freebsd.org/cgi/man.cgi?query=setmode&sektion=3
|
|
// chmod(1): https://www.freebsd.org/cgi/man.cgi?query=chmod&sektion=1
|
|
package mode
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Set is a set of changes to apply to an os.FileMode.
|
|
// Changes include setting or clearing specific bits, copying bits from one
|
|
// user class to another (e.g. "u=go" sets the user permissions to a copy of
|
|
// the group and other permsissions), etc.
|
|
type Set struct {
|
|
cmds []bitcmd
|
|
}
|
|
|
|
type bitcmd struct {
|
|
cmd byte
|
|
cmd2 byte
|
|
bits modet
|
|
}
|
|
|
|
const (
|
|
cmd2Clear byte = 1 << iota
|
|
cmd2Set
|
|
cmd2GBits
|
|
cmd2OBits
|
|
cmd2UBits
|
|
)
|
|
|
|
func (c bitcmd) String() string {
|
|
c2 := ""
|
|
if c.cmd2 != 0 {
|
|
c2 = " cmd2:"
|
|
if c.cmd2&cmd2Clear != 0 {
|
|
c2 += " CLR"
|
|
}
|
|
if c.cmd2&cmd2Set != 0 {
|
|
c2 += " SET"
|
|
}
|
|
if c.cmd2&cmd2UBits != 0 {
|
|
c2 += " UBITS"
|
|
}
|
|
if c.cmd2&cmd2GBits != 0 {
|
|
c2 += " GBITS"
|
|
}
|
|
if c.cmd2&cmd2OBits != 0 {
|
|
c2 += " OBITS"
|
|
}
|
|
}
|
|
return fmt.Sprintf("cmd: %q bits %#05o%s", c.cmd, c.bits, c2)
|
|
}
|
|
|
|
// The String method will likely only be useful when testing.
|
|
func (s Set) String() string {
|
|
var buf strings.Builder
|
|
buf.Grow(21*len(s.cmds) + 10)
|
|
_, _ = buf.WriteString("set: {\n")
|
|
for _, c := range s.cmds {
|
|
_, _ = buf.WriteString(c.String())
|
|
_ = buf.WriteByte('\n')
|
|
}
|
|
_, _ = buf.WriteString("}")
|
|
return buf.String()
|
|
}
|
|
|
|
// ErrSyntax indicates an argument does not represent a valid mode.
|
|
var ErrSyntax = errors.New("invalid syntax")
|
|
|
|
// Apply changes the provided os.FileMode based on the given umask and
|
|
// absolute or symbolic mode value.
|
|
//
|
|
// Apply is a convience to calling ParseWithUmask followed by Apply.
|
|
// Since it needs to parse the mode value string on each call it
|
|
// should only be used when mode value string will not be reapplied.
|
|
func Apply(s string, perm os.FileMode, umask uint) (os.FileMode, error) {
|
|
set, err := ParseWithUmask(s, umask)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return set.Apply(perm), nil
|
|
}
|
|
|
|
// Parse takes an absolute (octal) or symbolic mode value,
|
|
// as described in chmod(1), as an argument and returns
|
|
// the set of bit operations representing the mode value
|
|
// that can be applied to specific os.FileMode values.
|
|
//
|
|
// Same as ParseWithUmask(s, 0).
|
|
func Parse(s string) (Set, error) {
|
|
return ParseWithUmask(s, 0)
|
|
}
|
|
|
|
// TODO(dchapes): A Set.Parse method that reuses existing memory.
|
|
|
|
// TODO(dchapes): Only call syscall.Umask when abosolutely necessary and
|
|
// provide a Set method to query if set is umask dependant (and perhaps
|
|
// the umask that was in effect when parsed).
|
|
|
|
// ParseWithUmask is like Parse but uses the provided
|
|
// file creation mask instead of calling syscall.Umask.
|
|
func ParseWithUmask(s string, umask uint) (Set, error) {
|
|
var m Set
|
|
if s == "" {
|
|
return m, ErrSyntax
|
|
}
|
|
|
|
// If an absolute number, get it and return;
|
|
// disallow non-octal digits or illegal bits.
|
|
if d := s[0]; '0' <= d && d <= '9' {
|
|
v, err := strconv.ParseInt(s, 8, 16)
|
|
if err != nil {
|
|
return m, err
|
|
}
|
|
if v&^(standardBits|isTXT) != 0 {
|
|
return m, ErrSyntax
|
|
}
|
|
// We know this takes exactly two bitcmds.
|
|
m.cmds = make([]bitcmd, 0, 2)
|
|
m.addcmd('=', standardBits|isTXT, modet(v), 0)
|
|
return m, nil
|
|
}
|
|
|
|
// Get a copy of the mask for the permissions that are mask relative.
|
|
// Flip the bits, we want what's not set.
|
|
var mask modet = ^modet(umask)
|
|
|
|
// Pre-allocate room for several commands.
|
|
//m.cmds = make([]bitcmd, 0, 8)
|
|
|
|
// Build list of bitcmd structs to set/clear/copy bits as described by
|
|
// each clause of the symbolic mode.
|
|
equalOpDone := false
|
|
for {
|
|
// First, find out which bits might be modified.
|
|
var who modet
|
|
whoLoop:
|
|
for {
|
|
if len(s) == 0 {
|
|
return Set{}, ErrSyntax
|
|
}
|
|
switch s[0] {
|
|
case 'a':
|
|
who |= standardBits
|
|
case 'u':
|
|
who |= isUID | iRWXU
|
|
case 'g':
|
|
who |= isGID | iRWXG
|
|
case 'o':
|
|
who |= iRWXO
|
|
default:
|
|
break whoLoop
|
|
}
|
|
s = s[1:]
|
|
}
|
|
|
|
var op byte
|
|
getop:
|
|
op, s = s[0], s[1:]
|
|
switch op {
|
|
case '+', '-':
|
|
// Nothing.
|
|
case '=':
|
|
equalOpDone = false
|
|
default:
|
|
return Set{}, ErrSyntax
|
|
}
|
|
|
|
who &^= isTXT
|
|
permLoop:
|
|
for perm, permX := modet(0), modet(0); ; s = s[1:] {
|
|
var b byte
|
|
if len(s) > 0 {
|
|
b = s[0]
|
|
}
|
|
switch b {
|
|
case 'r':
|
|
perm |= iRUser | iRGroup | iROther
|
|
case 's':
|
|
// If only "other" bits ignore set-id.
|
|
if who == 0 || who&^iRWXO != 0 {
|
|
perm |= isUID | isGID
|
|
}
|
|
case 't':
|
|
// If only "other bits ignore sticky.
|
|
if who == 0 || who&^iRWXO != 0 {
|
|
who |= isTXT
|
|
perm |= isTXT
|
|
}
|
|
case 'w':
|
|
perm |= iWUser | iWGroup | iWOther
|
|
case 'X':
|
|
if op != '-' {
|
|
permX = iXUser | iXGroup | iXOther
|
|
} else {
|
|
perm |= iXUser | iXGroup | iXOther
|
|
}
|
|
case 'x':
|
|
perm |= iXUser | iXGroup | iXOther
|
|
case 'u', 'g', 'o':
|
|
// Whenever we hit 'u', 'g', or 'o', we have
|
|
// to flush out any partial mode that we have,
|
|
// and then do the copying of the mode bits.
|
|
if perm != 0 {
|
|
m.addcmd(op, who, perm, mask)
|
|
perm = 0
|
|
}
|
|
if op == '=' {
|
|
equalOpDone = true
|
|
}
|
|
if permX != 0 {
|
|
m.addcmd('X', who, permX, mask)
|
|
permX = 0
|
|
}
|
|
m.addcmd(b, who, modet(op), mask)
|
|
default:
|
|
// Add any permissions that we haven't alread done.
|
|
if perm != 0 || op == '=' && !equalOpDone {
|
|
if op == '=' {
|
|
equalOpDone = true
|
|
}
|
|
m.addcmd(op, who, perm, mask)
|
|
//perm = 0
|
|
}
|
|
if permX != 0 {
|
|
m.addcmd('X', who, permX, mask)
|
|
//permX = 0
|
|
}
|
|
break permLoop
|
|
}
|
|
}
|
|
|
|
if s == "" {
|
|
break
|
|
}
|
|
if s[0] != ',' {
|
|
goto getop
|
|
}
|
|
s = s[1:]
|
|
}
|
|
|
|
m.compress()
|
|
return m, nil
|
|
}
|
|
|
|
// Apply returns the os.FileMode after applying the set of changes.
|
|
func (s Set) Apply(perm os.FileMode) os.FileMode {
|
|
omode := fileModeToBits(perm)
|
|
newmode := omode
|
|
|
|
// When copying the user, group or other bits around, we "know"
|
|
// where the bits are in the mode so that we can do shifts to
|
|
// copy them around. If we don't use shifts, it gets real
|
|
// grundgy with lots of single bit checks and bit sets.
|
|
common := func(c bitcmd, value modet) {
|
|
if c.cmd2&cmd2Clear != 0 {
|
|
var clrval modet
|
|
if c.cmd2&cmd2Set != 0 {
|
|
clrval = iRWXO
|
|
} else {
|
|
clrval = value
|
|
}
|
|
if c.cmd2&cmd2UBits != 0 {
|
|
newmode &^= clrval << 6 & c.bits
|
|
}
|
|
if c.cmd2&cmd2GBits != 0 {
|
|
newmode &^= clrval << 3 & c.bits
|
|
}
|
|
if c.cmd2&cmd2OBits != 0 {
|
|
newmode &^= clrval & c.bits
|
|
}
|
|
}
|
|
if c.cmd2&cmd2Set != 0 {
|
|
if c.cmd2&cmd2UBits != 0 {
|
|
newmode |= value << 6 & c.bits
|
|
}
|
|
if c.cmd2&cmd2GBits != 0 {
|
|
newmode |= value << 3 & c.bits
|
|
}
|
|
if c.cmd2&cmd2OBits != 0 {
|
|
newmode |= value & c.bits
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, c := range s.cmds {
|
|
switch c.cmd {
|
|
case 'u':
|
|
common(c, newmode&iRWXU>>6)
|
|
case 'g':
|
|
common(c, newmode&iRWXG>>3)
|
|
case 'o':
|
|
common(c, newmode&iRWXO)
|
|
|
|
case '+':
|
|
newmode |= c.bits
|
|
case '-':
|
|
newmode &^= c.bits
|
|
|
|
case 'X':
|
|
if omode&(iXUser|iXGroup|iXOther) != 0 || perm.IsDir() {
|
|
newmode |= c.bits
|
|
}
|
|
}
|
|
}
|
|
|
|
return bitsToFileMode(perm, newmode)
|
|
}
|
|
|
|
// Chmod is a convience routine that applies the changes in
|
|
// Set to the named file. To avoid some race conditions,
|
|
// it opens the file and uses os.File.Stat and
|
|
// os.File.Chmod rather than os.Stat and os.Chmod if possible.
|
|
func (s *Set) Chmod(name string) (old, new os.FileMode, err error) {
|
|
if f, err := os.Open(name); err == nil { // nolint: vetshadow
|
|
defer f.Close() // nolint: errcheck
|
|
return s.ChmodFile(f)
|
|
}
|
|
// Fallback to os.Stat and os.Chmod if we
|
|
// don't have permission to open the file.
|
|
fi, err := os.Stat(name)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
old = fi.Mode()
|
|
new = s.Apply(old)
|
|
if new != old {
|
|
err = os.Chmod(name, new)
|
|
}
|
|
return old, new, err
|
|
|
|
}
|
|
|
|
// ChmodFile is a convience routine that applies
|
|
// the changes in Set to the open file f.
|
|
func (s *Set) ChmodFile(f *os.File) (old, new os.FileMode, err error) {
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
old = fi.Mode()
|
|
new = s.Apply(old)
|
|
if new != old {
|
|
err = f.Chmod(new)
|
|
}
|
|
return old, new, err
|
|
}
|
|
|
|
func (s *Set) addcmd(op byte, who, oparg, mask modet) {
|
|
c := bitcmd{}
|
|
switch op {
|
|
case '=':
|
|
c.cmd = '-'
|
|
if who != 0 {
|
|
c.bits = who
|
|
} else {
|
|
c.bits = standardBits
|
|
}
|
|
|
|
s.cmds = append(s.cmds, c)
|
|
//c = bitcmd{} // reset, not actually needed
|
|
op = '+'
|
|
fallthrough
|
|
case '+', '-', 'X':
|
|
c.cmd = op
|
|
if who != 0 {
|
|
c.bits = who & oparg
|
|
} else {
|
|
c.bits = mask & oparg
|
|
}
|
|
|
|
case 'u', 'g', 'o':
|
|
c.cmd = op
|
|
if who != 0 {
|
|
if who&iRUser != 0 {
|
|
c.cmd2 |= cmd2UBits
|
|
}
|
|
if who&iRGroup != 0 {
|
|
c.cmd2 |= cmd2GBits
|
|
}
|
|
if who&iROther != 0 {
|
|
c.cmd2 |= cmd2OBits
|
|
}
|
|
c.bits = ^modet(0)
|
|
} else {
|
|
c.cmd2 = cmd2UBits | cmd2GBits | cmd2OBits
|
|
c.bits = mask
|
|
}
|
|
|
|
switch oparg {
|
|
case '+':
|
|
c.cmd2 |= cmd2Set
|
|
case '-':
|
|
c.cmd2 |= cmd2Clear
|
|
case '=':
|
|
c.cmd2 |= cmd2Set | cmd2Clear
|
|
}
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
s.cmds = append(s.cmds, c)
|
|
}
|
|
|
|
// compress by compacting consecutive '+', '-' and 'X'
|
|
// commands into at most 3 commands, one of each. The 'u',
|
|
// 'g' and 'o' commands continue to be separate. They could
|
|
// probably be compacted, but it's not worth the effort.
|
|
func (s *Set) compress() {
|
|
//log.Println("before:", *m)
|
|
//log.Println("Start compress:")
|
|
j := 0
|
|
for i := 0; i < len(s.cmds); i++ {
|
|
c := s.cmds[i]
|
|
//log.Println(" read", i, c)
|
|
if strings.IndexByte("+-X", c.cmd) < 0 {
|
|
// Copy over any 'u', 'g', and 'o' commands.
|
|
if i != j {
|
|
s.cmds[j] = c
|
|
}
|
|
//log.Println(" wrote", j, "from", i)
|
|
j++
|
|
continue
|
|
}
|
|
var setbits, clrbits, Xbits modet
|
|
for ; i < len(s.cmds); i++ {
|
|
c = s.cmds[i]
|
|
//log.Println(" scan", i, c)
|
|
switch c.cmd {
|
|
case '-':
|
|
clrbits |= c.bits
|
|
setbits &^= c.bits
|
|
Xbits &^= c.bits
|
|
continue
|
|
case '+':
|
|
setbits |= c.bits
|
|
clrbits &^= c.bits
|
|
Xbits &^= c.bits
|
|
continue
|
|
case 'X':
|
|
Xbits |= c.bits &^ setbits
|
|
continue
|
|
default:
|
|
i--
|
|
}
|
|
break
|
|
}
|
|
if clrbits != 0 {
|
|
s.cmds[j].cmd = '-'
|
|
s.cmds[j].cmd2 = 0
|
|
s.cmds[j].bits = clrbits
|
|
//log.Println(" wrote", j, "clrbits")
|
|
j++
|
|
}
|
|
if setbits != 0 {
|
|
s.cmds[j].cmd = '+'
|
|
s.cmds[j].cmd2 = 0
|
|
s.cmds[j].bits = setbits
|
|
//log.Println(" wrote", j, "setbits")
|
|
j++
|
|
}
|
|
if Xbits != 0 {
|
|
s.cmds[j].cmd = 'X'
|
|
s.cmds[j].cmd2 = 0
|
|
s.cmds[j].bits = Xbits
|
|
//log.Println(" wrote", j, "Xbits")
|
|
j++
|
|
}
|
|
}
|
|
/*
|
|
if len(m.cmds) != j {
|
|
log.Println("compressed", len(m.cmds), "down to", j)
|
|
}
|
|
*/
|
|
s.cmds = s.cmds[:j]
|
|
//log.Println("after:", *m)
|
|
}
|