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 }