github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/utils/os/fs/filesystem.go (about)

     1  // Copyright © 2022 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fs
    16  
    17  import (
    18  	"archive/tar"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/sirupsen/logrus"
    25  	"golang.org/x/sys/unix"
    26  )
    27  
    28  var FS = NewFilesystem()
    29  
    30  type Interface interface {
    31  	Stat(name string) (os.FileInfo, error)
    32  	Rename(oldPath, newPath string) error
    33  	MkdirAll(path string) error
    34  	MkTmpdir(path string) (string, error)
    35  	CopyFile(src, dst string) (int64, error)
    36  	CopyDir(srcPath, dstPath string) error
    37  	RemoveAll(path ...string) error
    38  	GetFilesSize(paths []string) (int64, error)
    39  }
    40  
    41  type filesystem struct{}
    42  
    43  func (f filesystem) Stat(name string) (os.FileInfo, error) {
    44  	return os.Stat(name)
    45  }
    46  
    47  func (f filesystem) Rename(oldPath, newPath string) error {
    48  	// remove newPath before mv files to target
    49  	_, err := f.Stat(newPath)
    50  	if err == nil {
    51  		err = f.RemoveAll(newPath)
    52  		if err != nil {
    53  			return err
    54  		}
    55  	}
    56  
    57  	// create dir if filepath.Dir(newPath) not exist
    58  	_, err = f.Stat(filepath.Dir(newPath))
    59  	if err != nil {
    60  		err = f.MkdirAll(filepath.Dir(newPath))
    61  		if err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	return os.Rename(oldPath, newPath)
    67  }
    68  
    69  func (f filesystem) RemoveAll(path ...string) error {
    70  	for _, fi := range path {
    71  		err := os.RemoveAll(fi)
    72  		if err != nil {
    73  			return fmt.Errorf("failed to clean file %s: %v", fi, err)
    74  		}
    75  	}
    76  	return nil
    77  }
    78  
    79  func (f filesystem) MkdirAll(path string) error {
    80  	return os.MkdirAll(path, os.ModePerm)
    81  }
    82  
    83  func (f filesystem) MkTmpdir(path string) (string, error) {
    84  	tempDir, err := os.MkdirTemp(path, ".DTmp-")
    85  	if err != nil {
    86  		return "", err
    87  	}
    88  	return tempDir, os.MkdirAll(tempDir, os.ModePerm)
    89  }
    90  
    91  func (f filesystem) CopyDir(srcPath, dstPath string) error {
    92  	err := f.MkdirAll(dstPath)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	fis, err := os.ReadDir(srcPath)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	for _, fi := range fis {
   102  		src := filepath.Join(srcPath, fi.Name())
   103  		dst := filepath.Join(dstPath, fi.Name())
   104  		if fi.IsDir() {
   105  			err = f.CopyDir(src, dst)
   106  			if err != nil {
   107  				return err
   108  			}
   109  		} else {
   110  			_, err = f.CopyFile(src, dst)
   111  			if err != nil {
   112  				return err
   113  			}
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  func (f filesystem) CopyFile(src, dst string) (int64, error) {
   120  	sourceFileStat, err := os.Stat(src)
   121  	if err != nil {
   122  		return 0, err
   123  	}
   124  
   125  	header, err := tar.FileInfoHeader(sourceFileStat, src)
   126  	if err != nil {
   127  		return 0, fmt.Errorf("failed to get file info header for %s, err: %v", src, err)
   128  	}
   129  
   130  	if sourceFileStat.Mode()&os.ModeCharDevice != 0 && header.Devminor == 0 && header.Devmajor == 0 {
   131  		err = unix.Mknod(dst, unix.S_IFCHR, 0)
   132  		if err != nil {
   133  			return 0, err
   134  		}
   135  		return 0, os.Chown(dst, header.Uid, header.Gid)
   136  	}
   137  
   138  	if !sourceFileStat.Mode().IsRegular() {
   139  		return 0, fmt.Errorf("%s is not a regular file", src)
   140  	}
   141  
   142  	source, err := os.Open(filepath.Clean(src))
   143  	if err != nil {
   144  		return 0, err
   145  	}
   146  	defer func() {
   147  		if err := source.Close(); err != nil {
   148  			logrus.Errorf("failed to close file: %v", err)
   149  		}
   150  	}()
   151  	//will overwrite dst when dst is existed
   152  	destination, err := os.Create(filepath.Clean(dst))
   153  	if err != nil {
   154  		return 0, err
   155  	}
   156  	defer func() {
   157  		if err := destination.Close(); err != nil {
   158  			logrus.Errorf("failed to close file: %v", err)
   159  		}
   160  	}()
   161  	err = destination.Chmod(sourceFileStat.Mode())
   162  	if err != nil {
   163  		return 0, err
   164  	}
   165  
   166  	err = os.Chown(dst, header.Uid, header.Gid)
   167  	if err != nil {
   168  		return 0, err
   169  	}
   170  	nBytes, err := io.Copy(destination, source)
   171  	return nBytes, err
   172  }
   173  
   174  func (f filesystem) GetFilesSize(paths []string) (int64, error) {
   175  	var size int64
   176  	for i := range paths {
   177  		s, err := f.getFileSize(paths[i])
   178  		if err != nil {
   179  			return 0, err
   180  		}
   181  		size += s
   182  	}
   183  	return size, nil
   184  }
   185  
   186  func (f filesystem) getFileSize(path string) (size int64, err error) {
   187  	_, err = os.Stat(path)
   188  	if err != nil {
   189  		return
   190  	}
   191  	err = filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
   192  		if !info.IsDir() {
   193  			size += info.Size()
   194  		}
   195  		return err
   196  	})
   197  	return size, err
   198  }
   199  
   200  func NewFilesystem() Interface {
   201  	return filesystem{}
   202  }