blob: 3e8bd176312335af102ee3ec869c0769c1b93eb5 [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package filesystem
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"syscall"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
)
func setReadOnly(path string, fi os.FileInfo, readOnly bool) error {
mode := fi.Mode()
if mode&os.ModeSymlink != 0 {
// Skip symlink.
return nil
}
if readOnly {
mode &= syscall.S_IRUSR | syscall.S_IXUSR
} else {
mode |= syscall.S_IRUSR | syscall.S_IWUSR
if runtime.GOOS != "windows" && mode.IsDir() {
mode |= syscall.S_IXUSR
}
}
return os.Chmod(path, mode)
}
// SetReadOnly sets or resets the write bit on a file or directory.
// Zaps out access to 'group' and 'others'.
func SetReadOnly(path string, readOnly bool) error {
fi, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("failed to get lstat for %s: %v", path, err)
}
return setReadOnly(path, fi, readOnly)
}
// MakeTreeReadOnly makes all the files in the directories read only.
// Also makes the directories read only, only if it makes sense on the platform.
// This means no file can be created or deleted.
func MakeTreeReadOnly(ctx context.Context, root string) error {
logging.Debugf(ctx, "MakeTreeReadOnly(%s)", root)
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if runtime.GOOS != "windows" || !info.IsDir() {
return setReadOnly(path, info, true)
}
return nil
})
}
// MakeTreeFilesReadOnly makes all the files in the directories read only but
// not the directories themselves.
// This means files can be created or deleted.
func MakeTreeFilesReadOnly(ctx context.Context, root string) error {
logging.Debugf(ctx, "MakeTreeFilesReadOnly(%s)", root)
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return setReadOnly(path, info, true)
} else if runtime.GOOS != "windows" {
return setReadOnly(path, info, false)
}
return nil
})
}
// MakeTreeWritable makes all the files in the directories writeable.
// Also makes the directories writeable, only if it makes sense on the platform.
func MakeTreeWritable(ctx context.Context, root string) error {
logging.Debugf(ctx, "MakeTreeReadOnly(%s)", root)
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if runtime.GOOS != "windows" || !info.IsDir() {
return setReadOnly(path, info, false)
}
return nil
})
}
// ResolveSymlink recursively resolves simlink and returns absolute path that is
// not symlink with stat.
func ResolveSymlink(path string) (string, os.FileInfo, error) {
var stat os.FileInfo
for {
var err error
stat, err = os.Lstat(path)
if err != nil {
return "", stat, errors.Annotate(err, "failed to call Lstat(%s)", path).Err()
}
if (stat.Mode() & os.ModeSymlink) == 0 {
break
}
link, err := os.Readlink(path)
if err != nil {
return "", stat, errors.Annotate(err, "failed to call Readlink(%s)", path).Err()
}
if filepath.IsAbs(link) {
path = link
} else {
path = filepath.Join(filepath.Dir(path), link)
}
}
return path, stat, nil
}
// GetFilenameNoExt returns the base file name without the extension.
func GetFilenameNoExt(path string) string {
name := filepath.Base(path)
if i := strings.LastIndexByte(name, '.'); i != -1 {
name = name[:i]
}
return name
}