github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/chmod/chmod.go (about)

     1  // Copyright 2016-2020 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // chmod changes mode bits (e.g. permissions) of a file.
     6  //
     7  // Synopsis:
     8  //     chmod MODE FILE...
     9  //
    10  // Desription:
    11  //     MODE is a three character octal value or a string like a=rwx
    12  package main
    13  
    14  import (
    15  	"flag"
    16  	"fmt"
    17  	"log"
    18  	"os"
    19  	"path/filepath"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  )
    24  
    25  const special = 99999
    26  
    27  var (
    28  	recursive bool
    29  	reference string
    30  )
    31  
    32  func init() {
    33  	flag.BoolVar(&recursive,
    34  		"R",
    35  		false,
    36  		"do changes recursively")
    37  
    38  	flag.BoolVar(&recursive,
    39  		"recursive",
    40  		false,
    41  		"do changes recursively")
    42  
    43  	flag.StringVar(&reference,
    44  		"reference",
    45  		"",
    46  		"use mode from reference file")
    47  }
    48  
    49  func changeMode(path string, mode os.FileMode, octval uint64, mask uint64) (err error) {
    50  	// A special value for mask means the mode is fully described
    51  	if mask == special {
    52  		return os.Chmod(path, mode)
    53  	}
    54  
    55  	var info os.FileInfo
    56  	info, err = os.Stat(path)
    57  	if err != nil {
    58  		log.Printf("%v", err)
    59  		return
    60  	}
    61  
    62  	mode = info.Mode() & os.FileMode(mask)
    63  	mode = mode | os.FileMode(octval)
    64  
    65  	return os.Chmod(path, mode)
    66  }
    67  
    68  func calculateMode(modeString string) (mode os.FileMode, octval uint64, mask uint64) {
    69  	var err error
    70  	octval, err = strconv.ParseUint(modeString, 8, 32)
    71  	if err == nil {
    72  		if octval > 0777 {
    73  			log.Fatalf("Invalid octal value %0o. Value should be less than or equal to 0777.", octval)
    74  		}
    75  		// a fully described octal mode was supplied, signal that with a special value for mask
    76  		mask = special
    77  		mode = os.FileMode(octval)
    78  		return
    79  	}
    80  
    81  	reMode := regexp.MustCompile("^([ugoa]+)([-+=])(.*)")
    82  	m := reMode.FindStringSubmatch(modeString)
    83  	// Test for mode strings with invalid characters.
    84  	// This can't be done in the first regexp: if the match for m[3] is restricted to [rwx]*,
    85  	// `a=9` and `a=` would be indistinguishable: m[3] would be empty.
    86  	// `a=` is a valid (but destructive) operation. Do not turn a typo into that.
    87  	reMode = regexp.MustCompile("^[rwx]*$")
    88  	if len(m) < 3 || !reMode.MatchString(m[3]) {
    89  		log.Fatalf("Unable to decode mode %q. Please use an octal value or a valid mode string.", modeString)
    90  	}
    91  
    92  	// m[3] is [rwx]{0,3}
    93  	var octvalDigit uint64
    94  	if strings.Contains(m[3], "r") {
    95  		octvalDigit += 4
    96  	}
    97  	if strings.Contains(m[3], "w") {
    98  		octvalDigit += 2
    99  	}
   100  	if strings.Contains(m[3], "x") {
   101  		octvalDigit++
   102  	}
   103  
   104  	// m[2] is [-+=]
   105  	var operator = m[2]
   106  
   107  	// Use a mask so that we do not overwrite permissions for a user/group that was not specified
   108  	mask = 0777
   109  
   110  	// For "-", invert octvalDigit before applying the mask
   111  	if operator == "-" {
   112  		octvalDigit = 7 - octvalDigit
   113  	}
   114  
   115  	// m[1] is [ugoa]+
   116  	if strings.Contains(m[1], "o") || strings.Contains(m[1], "a") {
   117  		octval += octvalDigit
   118  		mask = mask & 0770
   119  	}
   120  	if strings.Contains(m[1], "g") || strings.Contains(m[1], "a") {
   121  		octval += octvalDigit << 3
   122  		mask = mask & 0707
   123  	}
   124  	if strings.Contains(m[1], "u") || strings.Contains(m[1], "a") {
   125  		octval += octvalDigit << 6
   126  		mask = mask & 0077
   127  	}
   128  
   129  	// For "+" the mask is superfluous, reset it
   130  	if operator == "+" {
   131  		mask = 0777
   132  	}
   133  
   134  	// The mode is fully described, signal that with a special value for mask
   135  	if operator == "=" && strings.Contains(m[1], "a") {
   136  		mask = special
   137  		mode = os.FileMode(octval)
   138  	}
   139  	return
   140  }
   141  
   142  func main() {
   143  	flag.Parse()
   144  	if len(flag.Args()) < 1 {
   145  		fmt.Fprintf(os.Stderr, "Usage of %s: [mode] filepath\n", os.Args[0])
   146  		flag.PrintDefaults()
   147  		os.Exit(1)
   148  	}
   149  
   150  	if len(flag.Args()) < 2 && reference == "" {
   151  		fmt.Fprintf(os.Stderr, "Usage of %s: [mode] filepath\n", os.Args[0])
   152  		flag.PrintDefaults()
   153  		os.Exit(1)
   154  	}
   155  
   156  	var mode os.FileMode
   157  	var octval, mask uint64
   158  	var fileList []string
   159  
   160  	if reference != "" {
   161  		fi, err := os.Stat(reference)
   162  		if err != nil {
   163  			log.Fatalf("bad reference file: %v", err)
   164  
   165  		}
   166  		mask = special
   167  		mode = fi.Mode()
   168  		fileList = flag.Args()
   169  	} else {
   170  		mode, octval, mask = calculateMode(flag.Args()[0])
   171  		fileList = flag.Args()[1:]
   172  	}
   173  
   174  	var exitError bool
   175  	for _, name := range fileList {
   176  		if recursive {
   177  			err := filepath.Walk(name, func(path string,
   178  				info os.FileInfo,
   179  				err error) error {
   180  				return changeMode(path, mode, octval, mask)
   181  			})
   182  			if err != nil {
   183  				log.Printf("%v", err)
   184  				exitError = true
   185  			}
   186  		} else {
   187  			if err := changeMode(name, mode, octval, mask); err != nil {
   188  				log.Printf("%v", err)
   189  				exitError = true
   190  			}
   191  		}
   192  	}
   193  	if exitError {
   194  		os.Exit(1)
   195  	}
   196  }