github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/mkdirallchown.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 osutil
    21  
    22  import (
    23  	"os"
    24  	"path/filepath"
    25  	"sync"
    26  	"syscall"
    27  
    28  	"github.com/snapcore/snapd/osutil/sys"
    29  )
    30  
    31  // XXX: we need to come back and fix this; this is a hack to unblock us.
    32  //      As part of the fixing we should unify with the similar code in
    33  //      cmd/snap-update-ns/utils.(*Secure).MkdirAll
    34  var mu sync.Mutex
    35  
    36  // MkdirAllChown is like os.MkdirAll but it calls os.Chown on any
    37  // directories it creates.
    38  func MkdirAllChown(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID) error {
    39  	mu.Lock()
    40  	defer mu.Unlock()
    41  	return mkdirAllChown(filepath.Clean(path), perm, uid, gid)
    42  }
    43  
    44  func mkdirAllChown(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID) error {
    45  	// split out so filepath.Clean isn't called twice for each inner path
    46  	if s, err := os.Stat(path); err == nil {
    47  		if s.IsDir() {
    48  			return nil
    49  		}
    50  
    51  		// emulate os.MkdirAll
    52  		return &os.PathError{
    53  			Op:   "mkdir",
    54  			Path: path,
    55  			Err:  syscall.ENOTDIR,
    56  		}
    57  	}
    58  
    59  	dir := filepath.Dir(path)
    60  	if dir != "/" {
    61  		if err := mkdirAllChown(dir, perm, uid, gid); err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	cand := path + ".mkdir-new"
    67  
    68  	if err := os.Mkdir(cand, perm); err != nil && !os.IsExist(err) {
    69  		return err
    70  	}
    71  
    72  	if err := sys.ChownPath(cand, uid, gid); err != nil {
    73  		return err
    74  	}
    75  
    76  	if err := os.Rename(cand, path); err != nil {
    77  		return err
    78  	}
    79  
    80  	fd, err := os.Open(dir)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	defer fd.Close()
    85  
    86  	return fd.Sync()
    87  }