...

Source file src/golang.conradwood.net/go-easyops/utils/fileutils.go

Documentation: golang.conradwood.net/go-easyops/utils

     1  package utils
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"golang.org/x/sys/unix"
     7  	"io/fs"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/user"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  )
    15  
    16  var (
    17  	safely_write_lock sync.Mutex
    18  	extra_dir         = flag.String("ge_findfile_additional_dir", "", "if set, findfile will search this directory as well")
    19  	debug_find_file   = flag.Bool("ge_debug_find_file", false, "debug fuzzy filename matches")
    20  	find_file_cache   = make(map[string]string)
    21  	ffclock           sync.Mutex
    22  	workingdir        string
    23  )
    24  
    25  func init() {
    26  	var err error
    27  	workingdir, err = os.Getwd()
    28  	if err != nil {
    29  		fmt.Printf("cannot get current working directory: %s\n", err)
    30  	}
    31  }
    32  
    33  // given an arbitrary string, will remove all unsafe characters. result may be safely used as a filename
    34  func MakeSafeFilename(name string) string {
    35  	// allowed chars
    36  	foo := ".0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_=-"
    37  	res := ""
    38  	isdup := false
    39  	for _, x := range name {
    40  		isgood := false
    41  		for _, allowed := range foo {
    42  			if allowed == x {
    43  				isgood = true
    44  				break
    45  			}
    46  		}
    47  		if isgood {
    48  			isdup = false
    49  		} else {
    50  			if isdup {
    51  				continue
    52  			}
    53  			isdup = true
    54  			x = '_'
    55  		}
    56  		res = res + fmt.Sprintf("%c", x)
    57  	}
    58  	return res
    59  }
    60  
    61  // write a file to a temporary file, then rename it afterwards
    62  func SafelyWriteFile(filename string, content []byte) error {
    63  	unix.Umask(000)
    64  	safely_write_lock.Lock()
    65  	defer safely_write_lock.Unlock()
    66  	tmpfile := ""
    67  	i := 0
    68  	for {
    69  		tmpfile = filename + fmt.Sprintf("_tmp_%d", i)
    70  		if !FileExists(tmpfile) {
    71  			break
    72  		}
    73  		i++
    74  	}
    75  	err := ioutil.WriteFile(tmpfile, content, 0666)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	err = os.Rename(tmpfile, filename)
    80  	if err != nil {
    81  		os.Remove(tmpfile)
    82  		return err
    83  	}
    84  	return nil
    85  }
    86  
    87  // like ioutil - but with open permissions to share
    88  func WriteFile(filename string, content []byte) error {
    89  	unix.Umask(000)
    90  	err := ioutil.WriteFile(filename, content, 0666)
    91  	return err
    92  }
    93  
    94  // like ioutil - but with open permissions to share. if necessary creates directories
    95  func WriteFileCreateDir(filename string, content []byte) error {
    96  	unix.Umask(000)
    97  	if strings.Contains(filename, "/") {
    98  		dir := filepath.Dir(filename)
    99  		os.MkdirAll(dir, 0777)
   100  	}
   101  	err := ioutil.WriteFile(filename, content, 0666)
   102  	return err
   103  }
   104  
   105  // like ioutil - but with open permissions to share
   106  func OpenWriteFile(filename string) (*os.File, error) {
   107  	unix.Umask(000)
   108  	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
   109  	return file, err
   110  }
   111  
   112  // I can never remember how to do this, so here's a helper:
   113  // return true if file exists, false otherwise
   114  func FileExists(filename string) bool {
   115  	_, err := os.Stat(filename)
   116  	if err != nil {
   117  		return false
   118  	}
   119  	return true
   120  }
   121  func debugFind(name, nname string) {
   122  	if !*debug_find_file {
   123  		return
   124  	}
   125  	fmt.Printf("File: %s resolved to %s\n", name, nname)
   126  	return
   127  }
   128  
   129  // search for a file in git repo and above. error if not found
   130  func FindFile(name string) (string, error) {
   131  	ffclock.Lock()
   132  	nname, foo := find_file_cache[name]
   133  	ffclock.Unlock()
   134  	if foo {
   135  		return nname, nil
   136  	}
   137  	nname = name
   138  	for i := 0; i < 20; i++ {
   139  		if FileExists(nname) {
   140  			debugFind(name, nname)
   141  			ffclock.Lock()
   142  			find_file_cache[name] = nname
   143  			ffclock.Unlock()
   144  			nname, err := filepath.Abs(nname)
   145  			if err != nil {
   146  				return "", err
   147  			}
   148  			return nname, nil
   149  		}
   150  		nname = "../" + nname
   151  	}
   152  	if *extra_dir != "" {
   153  		nname := fmt.Sprintf("%s/%s", *extra_dir, name)
   154  		nname, err := filepath.Abs(nname)
   155  		if err != nil {
   156  			return "", err
   157  		}
   158  		if FileExists(nname) {
   159  			return nname, nil
   160  		}
   161  	}
   162  	debugFind(name, "[not found]")
   163  	return "", fmt.Errorf("File not found: %s", name)
   164  }
   165  
   166  // this will find a file or directory in the given working directory by traversing up the directory
   167  // hierarchy but only upto the workingdir (not higher).
   168  // the returned file is guaranteed to be within the workingdir.
   169  // if it cannot be found, an error is returned.
   170  func FindFileInWorkingDir(name string) (string, error) {
   171  	name, err := FindFile(name)
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  	name, err = filepath.Abs(name)
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  	if !strings.HasPrefix(name, WorkingDir()) {
   180  		return "", fmt.Errorf("file %s not in working dir %s", name, WorkingDir())
   181  	}
   182  	return name, nil
   183  }
   184  
   185  // read file (uses some magic to find it too)
   186  func ReadFile(filename string) ([]byte, error) {
   187  	fn, err := FindFile(filename)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	b, err := ioutil.ReadFile(fn)
   192  	return b, err
   193  }
   194  
   195  // return the "workingdir" (the one we were started in)
   196  func WorkingDir() string {
   197  	if workingdir == "" {
   198  		var err error
   199  		workingdir, err = os.Getwd()
   200  		// this is critical, user wanted workingdir, but we do not
   201  		// have one. how did this even happen?
   202  		Bail("failed to get workingdir", err)
   203  	}
   204  	return workingdir
   205  }
   206  
   207  // get current home dir (from environment variable HOME, if that fails user.Current())
   208  func HomeDir() (string, error) {
   209  	he := os.Getenv("HOME")
   210  	if he != "" && FileExists(he) {
   211  		return he, nil
   212  	}
   213  	u, err := user.Current()
   214  	if err != nil {
   215  		return "", fmt.Errorf("no current user: %s", err)
   216  	}
   217  	if u == nil {
   218  		return "", fmt.Errorf("No current user")
   219  	}
   220  	return u.HomeDir, nil
   221  }
   222  
   223  // removes dir, changes permissions if needs to
   224  func RemoveAll(dir string) error {
   225  	var err error
   226  
   227  	err = os.RemoveAll(dir)
   228  	if err == nil {
   229  		return nil
   230  	}
   231  
   232  	// reset permissions and try again
   233  	err = ChmodR(dir, 0777, 0777)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	err = os.RemoveAll(dir)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	return err
   243  }
   244  
   245  // recursively chmod a dir and files and subdirectories. applies 'dirmask' to dirs and 'filemask' to files
   246  func ChmodR(dir string, dirmask, filemask fs.FileMode) error {
   247  	err := os.Chmod(dir, dirmask)
   248  	if err != nil {
   249  		return err
   250  	}
   251  	files, err := ioutil.ReadDir(dir)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	for _, f := range files {
   256  		if f.IsDir() {
   257  			err = ChmodR(dir+"/"+f.Name(), dirmask, filemask)
   258  			if err != nil {
   259  				return err
   260  			}
   261  			continue
   262  		}
   263  		err = os.Chmod(dir+"/"+f.Name(), filemask)
   264  		if err != nil {
   265  			return err
   266  		}
   267  	}
   268  	return nil
   269  }
   270  
   271  // return true if file exists and is a directory (uses findfile)
   272  func IsDir(name string) (bool, error) {
   273  	fname, err := FindFile(name)
   274  	if err != nil {
   275  		return false, err
   276  	}
   277  	// This returns an *os.FileInfo type
   278  	fileInfo, err := os.Stat(fname)
   279  	if err != nil {
   280  		return false, err
   281  	}
   282  
   283  	// IsDir is short for fileInfo.Mode().IsDir()
   284  	if fileInfo.IsDir() {
   285  		return true, nil
   286  	}
   287  	return false, nil
   288  }
   289  

View as plain text