github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/logs/logs.go (about)

     1  // Copyright 2017 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  // Package logs is used on-device to collect updates to system logs.
     6  package logs
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"syscall"
    16  
    17  	"go.chromium.org/tast/core/internal/logging"
    18  )
    19  
    20  // InodeSizes maps from inode to file size.
    21  type InodeSizes map[uint64]int64
    22  
    23  // GetLogInodeSizes recursively walks dir and returns a map from inode
    24  // to size in bytes for all regular files. warnings contains non-fatal errors
    25  // that were accounted, keyed by path. Exclude lists relative paths of directories
    26  // and files to skip.
    27  func GetLogInodeSizes(ctx context.Context, dir string, exclude []string) (InodeSizes, error) {
    28  	inodes := make(InodeSizes)
    29  
    30  	wf := func(p string, info os.FileInfo, err error) error {
    31  		if err != nil {
    32  			logging.Infof(ctx, "%s: %v", p, err)
    33  			return nil
    34  		}
    35  		if skip, walkErr := shouldSkip(p, dir, info, exclude); skip {
    36  			return walkErr
    37  		}
    38  		if !info.Mode().IsRegular() {
    39  			return nil
    40  		}
    41  
    42  		stat, ok := info.Sys().(*syscall.Stat_t)
    43  		if !ok {
    44  			logging.Infof(ctx, "Can't get inode for %s", p)
    45  			return nil
    46  		}
    47  		inodes[stat.Ino] = info.Size()
    48  		return nil
    49  	}
    50  	if err := filepath.Walk(dir, wf); err != nil {
    51  		return nil, err
    52  	}
    53  	return inodes, nil
    54  }
    55  
    56  // CopyLogFileUpdates takes origSizes, the result of an earlier call to
    57  // GetLogInodeSizes, and copies new parts of files under directory src to
    58  // directory dst, creating it if necessary. The exclude arg lists relative
    59  // paths of directories and files to skip. A nil or empty size map may be
    60  // passed to copy all files in their entirety. warnings contains non-fatal
    61  // errors that were accounted, keyed by path.
    62  func CopyLogFileUpdates(ctx context.Context, src, dst string, exclude []string, origSizes InodeSizes) error {
    63  	if err := os.MkdirAll(dst, 0755); err != nil {
    64  		return err
    65  	}
    66  
    67  	return filepath.Walk(src, func(sp string, info os.FileInfo, err error) error {
    68  		if err != nil {
    69  			logging.Infof(ctx, "%s: %v", sp, err)
    70  			return nil
    71  		}
    72  		if skip, walkErr := shouldSkip(sp, src, info, exclude); skip {
    73  			return walkErr
    74  		}
    75  		if !info.Mode().IsRegular() {
    76  			return nil
    77  		}
    78  
    79  		stat, ok := info.Sys().(*syscall.Stat_t)
    80  		if !ok {
    81  			logging.Infof(ctx, "Can't get inode for %s", sp)
    82  			return nil
    83  		}
    84  		var origSize int64
    85  		if origSizes != nil {
    86  			origSize = origSizes[stat.Ino]
    87  		}
    88  		if info.Size() == origSize {
    89  			return nil
    90  		}
    91  		if info.Size() < origSize {
    92  			logging.Infof(ctx, "%s is shorter than original (now %d, original %d), copying all instead of diff", sp, info.Size(), origSize)
    93  			origSize = 0
    94  		}
    95  
    96  		dp := filepath.Join(dst, sp[len(src):])
    97  		if err = os.MkdirAll(filepath.Dir(dp), 0755); err != nil {
    98  			return err
    99  		}
   100  
   101  		sf, err := os.Open(sp)
   102  		if err != nil {
   103  			logging.Infof(ctx, "%s: %v", sp, err)
   104  			return nil
   105  		}
   106  		defer sf.Close()
   107  
   108  		if _, err = sf.Seek(origSize, 0); err != nil {
   109  			logging.Infof(ctx, "%s: %v", sp, err)
   110  			return nil
   111  		}
   112  
   113  		df, err := os.Create(dp)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		defer df.Close()
   118  
   119  		if _, err = io.Copy(df, sf); err != nil {
   120  			logging.Infof(ctx, "%s: %v", sp, err)
   121  		}
   122  		return nil
   123  	})
   124  }
   125  
   126  // shouldSkip is a helper function called from a filepath.WalkFunc to check if the supplied absolute
   127  // path should be skipped. root is the root path that was previously passed to filepath.Walk, fi
   128  // corresponds to path, and exclude contains a list of paths relative to root to skip.
   129  // If the returned skip value is true, then the calling filepath.WalkFunc should return walkErr.
   130  func shouldSkip(path, root string, fi os.FileInfo, exclude []string) (skip bool, walkErr error) {
   131  	if path == root {
   132  		return false, nil
   133  	}
   134  
   135  	if !strings.HasPrefix(path, root+"/") {
   136  		return true, fmt.Errorf("path %v not in root %v", path, root)
   137  	}
   138  
   139  	rel := path[len(root)+1:]
   140  	for _, e := range exclude {
   141  		if e == rel {
   142  			if fi.IsDir() {
   143  				return true, filepath.SkipDir
   144  			}
   145  			return true, nil
   146  		}
   147  	}
   148  
   149  	return false, nil
   150  }