mirror of
https://github.com/roddhjav/apparmor.d.git
synced 2024-11-16 00:13:48 +01:00
566 lines
16 KiB
Go
566 lines
16 KiB
Go
|
/*
|
||
|
* This file is part of PathsHelper library.
|
||
|
*
|
||
|
* Copyright 2018 Arduino AG (http://www.arduino.cc/)
|
||
|
*
|
||
|
* PathsHelper library is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
*
|
||
|
* As a special exception, you may use this file as part of a free software
|
||
|
* library without restriction. Specifically, if other files instantiate
|
||
|
* templates or use macros or inline functions from this file, or you compile
|
||
|
* this file and link it with other files to produce an executable, this
|
||
|
* file does not by itself cause the resulting executable to be covered by
|
||
|
* the GNU General Public License. This exception does not however
|
||
|
* invalidate any other reasons why the executable file might be covered by
|
||
|
* the GNU General Public License.
|
||
|
*/
|
||
|
|
||
|
package paths
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Path represents a path
|
||
|
type Path struct {
|
||
|
path string
|
||
|
}
|
||
|
|
||
|
// New creates a new Path object. If path is the empty string
|
||
|
// then nil is returned.
|
||
|
func New(path ...string) *Path {
|
||
|
if len(path) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
if len(path) == 1 && path[0] == "" {
|
||
|
return nil
|
||
|
}
|
||
|
res := &Path{path: path[0]}
|
||
|
if len(path) > 1 {
|
||
|
return res.Join(path[1:]...)
|
||
|
}
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
// NewFromFile creates a new Path object using the path name
|
||
|
// obtained from the File object (see os.File.Name function).
|
||
|
func NewFromFile(file *os.File) *Path {
|
||
|
return New(file.Name())
|
||
|
}
|
||
|
|
||
|
// Stat returns a FileInfo describing the named file. The result is
|
||
|
// cached internally for next queries. To ensure that the cached
|
||
|
// FileInfo entry is updated just call Stat again.
|
||
|
func (p *Path) Stat() (fs.FileInfo, error) {
|
||
|
return os.Stat(p.path)
|
||
|
}
|
||
|
|
||
|
// Lstat returns a FileInfo describing the named file. If the file is
|
||
|
// a symbolic link, the returned FileInfo describes the symbolic link.
|
||
|
// Lstat makes no attempt to follow the link. If there is an error, it
|
||
|
// will be of type *PathError.
|
||
|
func (p *Path) Lstat() (fs.FileInfo, error) {
|
||
|
return os.Lstat(p.path)
|
||
|
}
|
||
|
|
||
|
// Clone create a copy of the Path object
|
||
|
func (p *Path) Clone() *Path {
|
||
|
return New(p.path)
|
||
|
}
|
||
|
|
||
|
// Join create a new Path by joining the provided paths
|
||
|
func (p *Path) Join(paths ...string) *Path {
|
||
|
return New(filepath.Join(p.path, filepath.Join(paths...)))
|
||
|
}
|
||
|
|
||
|
// JoinPath create a new Path by joining the provided paths
|
||
|
func (p *Path) JoinPath(paths ...*Path) *Path {
|
||
|
res := p.Clone()
|
||
|
for _, path := range paths {
|
||
|
res = res.Join(path.path)
|
||
|
}
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
// Base returns the last element of path
|
||
|
func (p *Path) Base() string {
|
||
|
return filepath.Base(p.path)
|
||
|
}
|
||
|
|
||
|
// Ext returns the file name extension used by path
|
||
|
func (p *Path) Ext() string {
|
||
|
return filepath.Ext(p.path)
|
||
|
}
|
||
|
|
||
|
// HasPrefix returns true if the file name has one of the
|
||
|
// given prefixes (the Base() method is used to obtain the
|
||
|
// file name used for the comparison)
|
||
|
func (p *Path) HasPrefix(prefixes ...string) bool {
|
||
|
filename := p.Base()
|
||
|
for _, prefix := range prefixes {
|
||
|
if strings.HasPrefix(filename, prefix) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// HasSuffix returns true if the file name has one of the
|
||
|
// given suffixies
|
||
|
func (p *Path) HasSuffix(suffixies ...string) bool {
|
||
|
filename := p.String()
|
||
|
for _, suffix := range suffixies {
|
||
|
if strings.HasSuffix(filename, suffix) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// RelTo returns a relative Path that is lexically equivalent to r when
|
||
|
// joined to the current Path.
|
||
|
//
|
||
|
// For example paths.New("/my/path/ab/cd").RelTo(paths.New("/my/path"))
|
||
|
// returns "../..".
|
||
|
func (p *Path) RelTo(r *Path) (*Path, error) {
|
||
|
rel, err := filepath.Rel(p.path, r.path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return New(rel), nil
|
||
|
}
|
||
|
|
||
|
// RelFrom returns a relative Path that when joined with r is lexically
|
||
|
// equivalent to the current path.
|
||
|
//
|
||
|
// For example paths.New("/my/path/ab/cd").RelFrom(paths.New("/my/path"))
|
||
|
// returns "ab/cd".
|
||
|
func (p *Path) RelFrom(r *Path) (*Path, error) {
|
||
|
rel, err := filepath.Rel(r.path, p.path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return New(rel), nil
|
||
|
}
|
||
|
|
||
|
// Abs returns the absolute path of the current Path
|
||
|
func (p *Path) Abs() (*Path, error) {
|
||
|
abs, err := filepath.Abs(p.path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return New(abs), nil
|
||
|
}
|
||
|
|
||
|
// IsAbs returns true if the Path is absolute
|
||
|
func (p *Path) IsAbs() bool {
|
||
|
return filepath.IsAbs(p.path)
|
||
|
}
|
||
|
|
||
|
// ToAbs transofrm the current Path to the corresponding absolute path
|
||
|
func (p *Path) ToAbs() error {
|
||
|
abs, err := filepath.Abs(p.path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.path = abs
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Clean Clean returns the shortest path name equivalent to path by
|
||
|
// purely lexical processing
|
||
|
func (p *Path) Clean() *Path {
|
||
|
return New(filepath.Clean(p.path))
|
||
|
}
|
||
|
|
||
|
// IsInsideDir returns true if the current path is inside the provided
|
||
|
// dir
|
||
|
func (p *Path) IsInsideDir(dir *Path) (bool, error) {
|
||
|
rel, err := filepath.Rel(dir.path, p.path)
|
||
|
if err != nil {
|
||
|
// If the dir cannot be made relative to this path it means
|
||
|
// that it belong to a different filesystems, so it cannot be
|
||
|
// inside this path.
|
||
|
return false, nil
|
||
|
}
|
||
|
return !strings.Contains(rel, ".."+string(os.PathSeparator)) &&
|
||
|
rel != ".." &&
|
||
|
rel != ".", nil
|
||
|
}
|
||
|
|
||
|
// Parent returns all but the last element of path, typically the path's
|
||
|
// directory or the parent directory if the path is already a directory
|
||
|
func (p *Path) Parent() *Path {
|
||
|
return New(filepath.Dir(p.path))
|
||
|
}
|
||
|
|
||
|
// Mkdir create a directory denoted by the current path
|
||
|
func (p *Path) Mkdir() error {
|
||
|
return os.Mkdir(p.path, 0755)
|
||
|
}
|
||
|
|
||
|
// MkdirAll creates a directory named path, along with any necessary
|
||
|
// parents, and returns nil, or else returns an error
|
||
|
func (p *Path) MkdirAll() error {
|
||
|
return os.MkdirAll(p.path, os.FileMode(0755))
|
||
|
}
|
||
|
|
||
|
// Remove removes the named file or directory
|
||
|
func (p *Path) Remove() error {
|
||
|
return os.Remove(p.path)
|
||
|
}
|
||
|
|
||
|
// RemoveAll removes path and any children it contains. It removes
|
||
|
// everything it can but returns the first error it encounters. If
|
||
|
// the path does not exist, RemoveAll returns nil (no error).
|
||
|
func (p *Path) RemoveAll() error {
|
||
|
return os.RemoveAll(p.path)
|
||
|
}
|
||
|
|
||
|
// Rename renames (moves) the path to newpath. If newpath already exists
|
||
|
// and is not a directory, Rename replaces it. OS-specific restrictions
|
||
|
// may apply when oldpath and newpath are in different directories. If
|
||
|
// there is an error, it will be of type *os.LinkError.
|
||
|
func (p *Path) Rename(newpath *Path) error {
|
||
|
return os.Rename(p.path, newpath.path)
|
||
|
}
|
||
|
|
||
|
// MkTempDir creates a new temporary directory inside the path
|
||
|
// pointed by the Path object with a name beginning with prefix
|
||
|
// and returns the path of the new directory.
|
||
|
func (p *Path) MkTempDir(prefix string) (*Path, error) {
|
||
|
return MkTempDir(p.path, prefix)
|
||
|
}
|
||
|
|
||
|
// FollowSymLink transforms the current path to the path pointed by the
|
||
|
// symlink if path is a symlink, otherwise it does nothing
|
||
|
func (p *Path) FollowSymLink() error {
|
||
|
resolvedPath, err := filepath.EvalSymlinks(p.path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.path = resolvedPath
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Exist return true if the file denoted by this path exists, false
|
||
|
// in any other case (also in case of error).
|
||
|
func (p *Path) Exist() bool {
|
||
|
exist, err := p.ExistCheck()
|
||
|
return exist && err == nil
|
||
|
}
|
||
|
|
||
|
// NotExist return true if the file denoted by this path DO NOT exists, false
|
||
|
// in any other case (also in case of error).
|
||
|
func (p *Path) NotExist() bool {
|
||
|
exist, err := p.ExistCheck()
|
||
|
return !exist && err == nil
|
||
|
}
|
||
|
|
||
|
// ExistCheck return true if the path exists or false if the path doesn't exists.
|
||
|
// In case the check fails false is returned together with the corresponding error.
|
||
|
func (p *Path) ExistCheck() (bool, error) {
|
||
|
_, err := p.Stat()
|
||
|
if err == nil {
|
||
|
return true, nil
|
||
|
}
|
||
|
if os.IsNotExist(err) {
|
||
|
return false, nil
|
||
|
}
|
||
|
if err.(*os.PathError).Err == syscall.ENOTDIR {
|
||
|
return false, nil
|
||
|
}
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
// IsDir returns true if the path exists and is a directory. In all the other
|
||
|
// cases (and also in case of any error) false is returned.
|
||
|
func (p *Path) IsDir() bool {
|
||
|
isdir, err := p.IsDirCheck()
|
||
|
return isdir && err == nil
|
||
|
}
|
||
|
|
||
|
// IsNotDir returns true if the path exists and is NOT a directory. In all the other
|
||
|
// cases (and also in case of any error) false is returned.
|
||
|
func (p *Path) IsNotDir() bool {
|
||
|
isdir, err := p.IsDirCheck()
|
||
|
return !isdir && err == nil
|
||
|
}
|
||
|
|
||
|
// IsDirCheck return true if the path exists and is a directory or false
|
||
|
// if the path exists and is not a directory. In all the other case false and
|
||
|
// the corresponding error is returned.
|
||
|
func (p *Path) IsDirCheck() (bool, error) {
|
||
|
info, err := p.Stat()
|
||
|
if err == nil {
|
||
|
return info.IsDir(), nil
|
||
|
}
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
// CopyTo copies the contents of the file named src to the file named
|
||
|
// by dst. The file will be created if it does not already exist. If the
|
||
|
// destination file exists, all it's contents will be replaced by the contents
|
||
|
// of the source file. The file mode will be copied from the source and
|
||
|
// the copied data is synced/flushed to stable storage.
|
||
|
func (p *Path) CopyTo(dst *Path) error {
|
||
|
if p.EqualsTo(dst) {
|
||
|
return fmt.Errorf("%s and %s are the same file", p.path, dst.path)
|
||
|
}
|
||
|
|
||
|
in, err := os.Open(p.path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer in.Close()
|
||
|
|
||
|
out, err := os.Create(dst.path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer out.Close()
|
||
|
|
||
|
if _, err := io.Copy(out, in); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := out.Sync(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
si, err := p.Stat()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = os.Chmod(dst.path, si.Mode())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// CopyDirTo recursively copies the directory denoted by the current path to
|
||
|
// the destination path. The source directory must exist and the destination
|
||
|
// directory must NOT exist (no implicit destination name allowed).
|
||
|
// Symlinks are not copied, they will be supported in future versions.
|
||
|
func (p *Path) CopyDirTo(dst *Path) error {
|
||
|
src := p.Clean()
|
||
|
dst = dst.Clean()
|
||
|
|
||
|
srcFiles, err := src.ReadDir()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("error reading source dir %s: %s", src, err)
|
||
|
}
|
||
|
|
||
|
if exist, err := dst.ExistCheck(); exist {
|
||
|
return fmt.Errorf("destination %s already exists", dst)
|
||
|
} else if err != nil {
|
||
|
return fmt.Errorf("checking if %s exists: %s", dst, err)
|
||
|
}
|
||
|
|
||
|
if err := dst.MkdirAll(); err != nil {
|
||
|
return fmt.Errorf("creating destination dir %s: %s", dst, err)
|
||
|
}
|
||
|
|
||
|
srcInfo, err := src.Stat()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting stat info for %s: %s", src, err)
|
||
|
}
|
||
|
if err := os.Chmod(dst.path, srcInfo.Mode()); err != nil {
|
||
|
return fmt.Errorf("setting permission for dir %s: %s", dst, err)
|
||
|
}
|
||
|
|
||
|
for _, srcPath := range srcFiles {
|
||
|
srcPathInfo, err := srcPath.Stat()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting stat info for %s: %s", srcPath, err)
|
||
|
}
|
||
|
dstPath := dst.Join(srcPath.Base())
|
||
|
|
||
|
if srcPathInfo.IsDir() {
|
||
|
if err := srcPath.CopyDirTo(dstPath); err != nil {
|
||
|
return fmt.Errorf("copying %s to %s: %s", srcPath, dstPath, err)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Skip symlinks.
|
||
|
if srcPathInfo.Mode()&os.ModeSymlink != 0 {
|
||
|
// TODO
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if err := srcPath.CopyTo(dstPath); err != nil {
|
||
|
return fmt.Errorf("copying %s to %s: %s", srcPath, dstPath, err)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Chmod changes the mode of the named file to mode. If the file is a
|
||
|
// symbolic link, it changes the mode of the link's target. If there
|
||
|
// is an error, it will be of type *os.PathError.
|
||
|
func (p *Path) Chmod(mode fs.FileMode) error {
|
||
|
return os.Chmod(p.path, mode)
|
||
|
}
|
||
|
|
||
|
// Chtimes changes the access and modification times of the named file,
|
||
|
// similar to the Unix utime() or utimes() functions.
|
||
|
func (p *Path) Chtimes(atime, mtime time.Time) error {
|
||
|
return os.Chtimes(p.path, atime, mtime)
|
||
|
}
|
||
|
|
||
|
// ReadFile reads the file named by filename and returns the contents
|
||
|
func (p *Path) ReadFile() ([]byte, error) {
|
||
|
return os.ReadFile(p.path)
|
||
|
}
|
||
|
|
||
|
// WriteFile writes data to a file named by filename. If the file
|
||
|
// does not exist, WriteFile creates it otherwise WriteFile truncates
|
||
|
// it before writing.
|
||
|
func (p *Path) WriteFile(data []byte) error {
|
||
|
return os.WriteFile(p.path, data, os.FileMode(0644))
|
||
|
}
|
||
|
|
||
|
// WriteToTempFile writes data to a newly generated temporary file.
|
||
|
// dir and prefix have the same meaning for MkTempFile.
|
||
|
// In case of success the Path to the temp file is returned.
|
||
|
func WriteToTempFile(data []byte, dir *Path, prefix string) (res *Path, err error) {
|
||
|
f, err := MkTempFile(dir, prefix)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
if n, err := f.Write(data); err != nil {
|
||
|
return nil, err
|
||
|
} else if n < len(data) {
|
||
|
return nil, fmt.Errorf("could not write all data (written %d bytes out of %d)", n, len(data))
|
||
|
}
|
||
|
return New(f.Name()), nil
|
||
|
}
|
||
|
|
||
|
// ReadFileAsLines reads the file named by filename and returns it as an
|
||
|
// array of lines. This function takes care of the newline encoding
|
||
|
// differences between different OS
|
||
|
func (p *Path) ReadFileAsLines() ([]string, error) {
|
||
|
data, err := p.ReadFile()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
txt := string(data)
|
||
|
txt = strings.Replace(txt, "\r\n", "\n", -1)
|
||
|
return strings.Split(txt, "\n"), nil
|
||
|
}
|
||
|
|
||
|
// Truncate create an empty file named by path or if the file already
|
||
|
// exist it truncates it (delete all contents)
|
||
|
func (p *Path) Truncate() error {
|
||
|
return p.WriteFile([]byte{})
|
||
|
}
|
||
|
|
||
|
// Open opens a file for reading. It calls os.Open on the
|
||
|
// underlying path.
|
||
|
func (p *Path) Open() (*os.File, error) {
|
||
|
return os.Open(p.path)
|
||
|
}
|
||
|
|
||
|
// Create creates or truncates a file. It calls os.Create
|
||
|
// on the underlying path.
|
||
|
func (p *Path) Create() (*os.File, error) {
|
||
|
return os.Create(p.path)
|
||
|
}
|
||
|
|
||
|
// Append opens a file for append or creates it if the file doesn't exist.
|
||
|
func (p *Path) Append() (*os.File, error) {
|
||
|
return os.OpenFile(p.path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
|
||
|
}
|
||
|
|
||
|
// EqualsTo return true if both paths are equal
|
||
|
func (p *Path) EqualsTo(other *Path) bool {
|
||
|
return p.path == other.path
|
||
|
}
|
||
|
|
||
|
// EquivalentTo return true if both paths are equivalent (they points to the
|
||
|
// same file even if they are lexicographically different) based on the current
|
||
|
// working directory.
|
||
|
func (p *Path) EquivalentTo(other *Path) bool {
|
||
|
if p.Clean().path == other.Clean().path {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if infoP, err := p.Stat(); err != nil {
|
||
|
// go ahead with the next test...
|
||
|
} else if infoOther, err := other.Stat(); err != nil {
|
||
|
// go ahead with the next test...
|
||
|
} else if os.SameFile(infoP, infoOther) {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if absP, err := p.Abs(); err != nil {
|
||
|
return false
|
||
|
} else if absOther, err := other.Abs(); err != nil {
|
||
|
return false
|
||
|
} else {
|
||
|
return absP.path == absOther.path
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Parents returns all the parents directories of the current path. If the path is absolute
|
||
|
// it starts from the current path to the root, if the path is relative is starts from the
|
||
|
// current path to the current directory.
|
||
|
// The path should be clean for this method to work properly (no .. or . or other shortcuts).
|
||
|
// This function does not performs any check on the returned paths.
|
||
|
func (p *Path) Parents() []*Path {
|
||
|
res := []*Path{}
|
||
|
dir := p
|
||
|
for {
|
||
|
res = append(res, dir)
|
||
|
parent := dir.Parent()
|
||
|
if parent.EquivalentTo(dir) {
|
||
|
break
|
||
|
}
|
||
|
dir = parent
|
||
|
}
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func (p *Path) String() string {
|
||
|
return p.path
|
||
|
}
|
||
|
|
||
|
// Canonical return a "canonical" Path for the given filename.
|
||
|
// The meaning of "canonical" is OS-dependent but the goal of this method
|
||
|
// is to always return the same path for a given file (factoring out all the
|
||
|
// possibile ambiguities including, for example, relative paths traversal,
|
||
|
// symlinks, drive volume letter case, etc).
|
||
|
func (p *Path) Canonical() *Path {
|
||
|
canonical := p.Clone()
|
||
|
// https://github.com/golang/go/issues/17084#issuecomment-246645354
|
||
|
canonical.FollowSymLink()
|
||
|
if absPath, err := canonical.Abs(); err == nil {
|
||
|
canonical = absPath
|
||
|
}
|
||
|
return canonical
|
||
|
}
|