github.com/pelicanplatform/pelican@v1.0.5/config/mkdirall.go (about)

     1  /***************************************************************
     2   *
     3   * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License"); you
     6   * may not use this file except in compliance with the License.  You may
     7   * obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   ***************************************************************/
    18  
    19  package config
    20  
    21  import (
    22  	"os"
    23  	"os/exec"
    24  	"runtime"
    25  	"syscall"
    26  
    27  	"github.com/pkg/errors"
    28  )
    29  
    30  // This is the pelican version of `MkdirAll`; ensures that any created directory
    31  // is owned by a given uid/gid.  This allows the created directory to be owned by
    32  // the xrootd user.
    33  // The base implementation is taken from the go std library, here:
    34  // - https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/os/path.go;l=18
    35  // The BSD license for go is compatible with pelican's
    36  func MkdirAll(path string, perm os.FileMode, uid int, gid int) error {
    37  	// Fast path: if we can tell whether path is a directory or file, stop with success or error.
    38  	dir, err := os.Stat(path)
    39  	if err == nil {
    40  		if dir.IsDir() {
    41  			return nil
    42  		}
    43  		return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
    44  	}
    45  
    46  	// Slow path: make sure parent exists and then call Mkdir for path.
    47  	i := len(path)
    48  	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
    49  		i--
    50  	}
    51  
    52  	j := i
    53  	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
    54  		j--
    55  	}
    56  
    57  	if j > 1 {
    58  		// Create parent.
    59  		err = MkdirAll(fixRootDirectory(path[:j-1]), perm, uid, gid)
    60  		if err != nil {
    61  			return err
    62  		}
    63  	}
    64  
    65  	// Parent now exists; invoke Mkdir and use its result.
    66  	err = os.Mkdir(path, perm)
    67  	if err != nil {
    68  		// Handle arguments like "foo/." by
    69  		// double-checking that directory doesn't exist.
    70  		dir, err1 := os.Lstat(path)
    71  		if err1 == nil && dir.IsDir() {
    72  			return nil
    73  		}
    74  		return err
    75  	}
    76  
    77  	user, err := GetDaemonUser()
    78  	if err != nil {
    79  		return err
    80  	}
    81  	groupname, err := GetDaemonGroup()
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	// Windows does not have "chown", has to work differently
    87  	currentOS := runtime.GOOS
    88  	if currentOS == "windows" {
    89  		cmd := exec.Command("icacls", path, "/grant", user+":F")
    90  		output, err := cmd.CombinedOutput()
    91  		if err != nil {
    92  			return errors.Wrapf(err, "Failed to chown directory %v to groupname %v: %s",
    93  				path, groupname, string(output))
    94  		}
    95  		return nil
    96  	} else { // Else we are running on linux/mac
    97  		if err = os.Chown(path, uid, gid); err != nil {
    98  			return errors.Wrapf(err, "Failed to chown directory %v to groupname %v",
    99  				path, groupname)
   100  		}
   101  	}
   102  	return nil
   103  }