istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/docker-builder/builder/tar.go (about)

     1  // Copyright Istio Authors
     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 builder
    16  
    17  import (
    18  	"archive/tar"
    19  	"io"
    20  	"io/fs"
    21  	"os"
    22  	"path/filepath"
    23  	"time"
    24  )
    25  
    26  func WriteArchiveFromFiles(base string, files map[string]string, out io.Writer) error {
    27  	tw := tar.NewWriter(out)
    28  	defer tw.Close()
    29  
    30  	for dest, srcRel := range files {
    31  		src := srcRel
    32  		if !filepath.IsAbs(src) {
    33  			src = filepath.Join(base, srcRel)
    34  		}
    35  		i, err := os.Stat(src)
    36  		if err != nil {
    37  			return err
    38  		}
    39  		isDir := i.IsDir()
    40  		ts := src
    41  		write := func(src string) error {
    42  			rel, _ := filepath.Rel(ts, src)
    43  			info, err := os.Stat(src)
    44  			if err != nil {
    45  				return err
    46  			}
    47  
    48  			var link string
    49  			if info.Mode()&os.ModeSymlink == os.ModeSymlink {
    50  				// fs.FS does not implement readlink, so we have this hack for now.
    51  				if link, err = os.Readlink(src); err != nil {
    52  					return err
    53  				}
    54  			}
    55  
    56  			header, err := tar.FileInfoHeader(info, link)
    57  			if err != nil {
    58  				return err
    59  			}
    60  			// work around some weirdness, without this we wind up with just the basename
    61  			header.Name = dest
    62  			if isDir {
    63  				header.Name = filepath.Join(dest, rel)
    64  			}
    65  
    66  			if IsExecOwner(info.Mode()) {
    67  				header.Mode = 0o755
    68  			} else {
    69  				header.Mode = 0o644
    70  			}
    71  			header.Uid = 0
    72  			header.Gid = 0
    73  
    74  			// TODO: if we want reproducible builds we can fake the timestamps here
    75  
    76  			if err := tw.WriteHeader(header); err != nil {
    77  				return err
    78  			}
    79  
    80  			if info.Mode().IsRegular() {
    81  				data, err := os.Open(src)
    82  				if err != nil {
    83  					return err
    84  				}
    85  
    86  				defer data.Close()
    87  
    88  				if _, err := io.Copy(tw, data); err != nil {
    89  					return err
    90  				}
    91  			}
    92  			return nil
    93  		}
    94  
    95  		if isDir {
    96  			if err := filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
    97  				if err != nil {
    98  					return err
    99  				}
   100  				return write(path)
   101  			}); err != nil {
   102  				return err
   103  			}
   104  		} else {
   105  			if err := write(src); err != nil {
   106  				return err
   107  			}
   108  		}
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  var WriteTime = time.Time{}
   115  
   116  // Writes a raw TAR archive to out, given an fs.FS.
   117  func WriteArchiveFromFS(base string, fsys fs.FS, out io.Writer, sourceDateEpoch time.Time) error {
   118  	tw := tar.NewWriter(out)
   119  	defer tw.Close()
   120  
   121  	if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
   122  		if err != nil {
   123  			return err
   124  		}
   125  
   126  		info, err := d.Info()
   127  		if err != nil {
   128  			return err
   129  		}
   130  
   131  		if info.Mode()&os.ModeSymlink == os.ModeSymlink {
   132  			var link string
   133  			// fs.FS does not implement readlink, so we have this hack for now.
   134  			if link, err = os.Readlink(filepath.Join(base, path)); err != nil {
   135  				return err
   136  			}
   137  			// Resolve the link
   138  			if info, err = os.Stat(link); err != nil {
   139  				return err
   140  			}
   141  		}
   142  
   143  		header, err := tar.FileInfoHeader(info, "")
   144  		if err != nil {
   145  			return err
   146  		}
   147  		// work around some weirdness, without this we wind up with just the basename
   148  		header.Name = path
   149  
   150  		if err := tw.WriteHeader(header); err != nil {
   151  			return err
   152  		}
   153  
   154  		if info.Mode().IsRegular() {
   155  			data, err := fsys.Open(path)
   156  			if err != nil {
   157  				return err
   158  			}
   159  
   160  			defer data.Close()
   161  
   162  			if _, err := io.Copy(tw, data); err != nil {
   163  				return err
   164  			}
   165  		}
   166  
   167  		return nil
   168  	}); err != nil {
   169  		return err
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func IsExecOwner(mode os.FileMode) bool {
   176  	return mode&0o100 != 0
   177  }