github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/gzip/file.go (about)

     1  // Copyright 2017-2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gzip
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/mvdan/u-root-coreutils/pkg/uio"
    13  )
    14  
    15  // File is a file path to be compressed or decompressed.
    16  type File struct {
    17  	Path    string
    18  	Options *Options
    19  }
    20  
    21  // outputPath removes the path suffix on decompress and adds it on compress.
    22  // In the case of when options stdout or test are enabled it returns the path
    23  // as is.
    24  func (f *File) outputPath() string {
    25  	if f.Options.Stdout || f.Options.Test {
    26  		return f.Path
    27  	} else if f.Options.Decompress {
    28  		return f.Path[:len(f.Path)-len(f.Options.Suffix)]
    29  	}
    30  	return f.Path + f.Options.Suffix
    31  }
    32  
    33  // CheckPath validates the input file path. Checks on compression
    34  // if the path has the correct suffix, and on decompression checks
    35  // that it doesn't have the suffix. Allows override by force option.
    36  // Skip if the input is a Stdin.
    37  func (f *File) CheckPath() error {
    38  	if f.Options.Stdin {
    39  		return nil
    40  	}
    41  
    42  	_, err := os.Stat(f.Path)
    43  	if os.IsNotExist(err) {
    44  		return fmt.Errorf("skipping, %s does not exist", f.Path)
    45  	} else if os.IsPermission(err) {
    46  		return fmt.Errorf("skipping, %s permission denied", f.Path)
    47  	}
    48  
    49  	if !f.Options.Force {
    50  		if f.Options.Decompress {
    51  			if !strings.HasSuffix(f.Path, f.Options.Suffix) {
    52  				return fmt.Errorf("skipping, %s does not have %s suffix", f.Path, f.Options.Suffix)
    53  			}
    54  		} else {
    55  			if strings.HasSuffix(f.Path, f.Options.Suffix) {
    56  				return fmt.Errorf("skipping, %s already has %s suffix", f.Path, f.Options.Suffix)
    57  			}
    58  		}
    59  	}
    60  	return nil
    61  }
    62  
    63  // CheckOutputPath checks if output is attempting to write binary to stdout if
    64  // stdout is a device. Also checks if output path already exists. Allow
    65  // override via force option.
    66  func (f *File) CheckOutputPath() error {
    67  	_, err := os.Stat(f.outputPath())
    68  	if !os.IsNotExist(err) && !f.Options.Stdout && !f.Options.Test && !f.Options.Force {
    69  		return fmt.Errorf("skipping, %s already exist", f.outputPath())
    70  	} else if os.IsPermission(err) {
    71  		return fmt.Errorf("skipping, %s permission denied", f.outputPath())
    72  	}
    73  	return nil
    74  }
    75  
    76  // CheckOutputStdout checks if output is attempting to write binary to stdout
    77  // if stdout is a device.
    78  func (f *File) CheckOutputStdout() error {
    79  	if f.Options.Stdout {
    80  		stat, _ := os.Stdout.Stat()
    81  		if !f.Options.Decompress && !f.Options.Force && (stat.Mode()&os.ModeDevice) != 0 {
    82  			return fmt.Errorf("fatal, trying to write compressed data to a terminal/device (use -f to force)")
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  // Cleanup removes input file. Overrided with keep option. Skipped if
    89  // stdout or test option is true.
    90  func (f *File) Cleanup() error {
    91  	if !f.Options.Keep && !f.Options.Stdout && !f.Options.Test {
    92  		return os.Remove(f.Path)
    93  	}
    94  	return nil
    95  }
    96  
    97  // Process either compresses or decompressed the input file based on
    98  // the associated file.options.
    99  func (f *File) Process() error {
   100  	var i *os.File
   101  	var err error
   102  
   103  	if f.Options.Stdin {
   104  		i = os.Stdin
   105  	} else {
   106  		i, err = os.Open(f.Path)
   107  		if err != nil {
   108  			return err
   109  		}
   110  		defer i.Close()
   111  	}
   112  
   113  	// Use the uio.WriteNameCloser interface so both *os.File and
   114  	// uio.WriteNameClose can be assigned to var o without any type casting below.
   115  	var o uio.WriteNameCloser
   116  
   117  	if f.Options.Test {
   118  		o = uio.Discard
   119  	} else if f.Options.Stdout {
   120  		o = os.Stdout
   121  	} else {
   122  		if o, err = os.Create(f.outputPath()); err != nil {
   123  			return err
   124  		}
   125  	}
   126  
   127  	if f.Options.Verbose && !f.Options.Quiet {
   128  		fmt.Fprintf(os.Stderr, "%s to %s\n", i.Name(), o.Name())
   129  	}
   130  
   131  	if f.Options.Decompress {
   132  		if err := Decompress(i, o, f.Options.Blocksize, f.Options.Processes); err != nil {
   133  			if !f.Options.Stdout {
   134  				o.Close()
   135  			}
   136  			return err
   137  		}
   138  	} else {
   139  		if err := Compress(i, o, f.Options.Level, f.Options.Blocksize, f.Options.Processes); err != nil {
   140  			if !f.Options.Stdout {
   141  				o.Close()
   142  			}
   143  			return err
   144  		}
   145  	}
   146  
   147  	if f.Options.Stdout {
   148  		return nil
   149  	}
   150  	return o.Close()
   151  }