1// Copyright 2024 Google Inc. All rights reserved. 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 15package elf 16 17import ( 18 "io/fs" 19 "os" 20 "path/filepath" 21 "strings" 22 "sync" 23 "time" 24) 25 26func UpdateBuildIdDir(path string) error { 27 path = filepath.Clean(path) 28 buildIdPath := path + "/.build-id" 29 30 // Collect the list of files and build-id symlinks. If the symlinks are 31 // up to date (newer than the symbol files), there is nothing to do. 32 var buildIdFiles, symbolFiles []string 33 var buildIdMtime, symbolsMtime time.Time 34 filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error { 35 if entry == nil || entry.IsDir() { 36 return nil 37 } 38 info, err := entry.Info() 39 if err != nil { 40 return err 41 } 42 mtime := info.ModTime() 43 if strings.HasPrefix(path, buildIdPath) { 44 if buildIdMtime.Compare(mtime) < 0 { 45 buildIdMtime = mtime 46 } 47 buildIdFiles = append(buildIdFiles, path) 48 } else { 49 if symbolsMtime.Compare(mtime) < 0 { 50 symbolsMtime = mtime 51 } 52 symbolFiles = append(symbolFiles, path) 53 } 54 return nil 55 }) 56 if symbolsMtime.Compare(buildIdMtime) < 0 { 57 return nil 58 } 59 60 // Collect build-id -> file mapping from ELF files in the symbols directory. 61 concurrency := 8 62 done := make(chan error) 63 buildIdToFile := make(map[string]string) 64 var mu sync.Mutex 65 for i := 0; i != concurrency; i++ { 66 go func(paths []string) { 67 for _, path := range paths { 68 id, err := Identifier(path, true) 69 if err != nil { 70 done <- err 71 return 72 } 73 if id == "" { 74 continue 75 } 76 mu.Lock() 77 oldPath := buildIdToFile[id] 78 if oldPath == "" || oldPath > path { 79 buildIdToFile[id] = path 80 } 81 mu.Unlock() 82 } 83 done <- nil 84 }(symbolFiles[len(symbolFiles)*i/concurrency : len(symbolFiles)*(i+1)/concurrency]) 85 } 86 87 // Collect previously generated build-id -> file mapping from the .build-id directory. 88 // We will use this for incremental updates. If we see anything in the .build-id 89 // directory that we did not expect, we'll delete it and start over. 90 prevBuildIdToFile := make(map[string]string) 91out: 92 for _, buildIdFile := range buildIdFiles { 93 if !strings.HasSuffix(buildIdFile, ".debug") { 94 prevBuildIdToFile = nil 95 break 96 } 97 buildId := buildIdFile[len(buildIdPath)+1 : len(buildIdFile)-6] 98 for i, ch := range buildId { 99 if i == 2 { 100 if ch != '/' { 101 prevBuildIdToFile = nil 102 break out 103 } 104 } else { 105 if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') { 106 prevBuildIdToFile = nil 107 break out 108 } 109 } 110 } 111 target, err := os.Readlink(buildIdFile) 112 if err != nil || !strings.HasPrefix(target, "../../") { 113 prevBuildIdToFile = nil 114 break 115 } 116 prevBuildIdToFile[buildId[0:2]+buildId[3:]] = path + target[5:] 117 } 118 if prevBuildIdToFile == nil { 119 err := os.RemoveAll(buildIdPath) 120 if err != nil { 121 return err 122 } 123 prevBuildIdToFile = make(map[string]string) 124 } 125 126 // Wait for build-id collection from ELF files to finish. 127 for i := 0; i != concurrency; i++ { 128 err := <-done 129 if err != nil { 130 return err 131 } 132 } 133 134 // Delete old symlinks. 135 for id, _ := range prevBuildIdToFile { 136 if buildIdToFile[id] == "" { 137 symlinkDir := buildIdPath + "/" + id[:2] 138 symlinkPath := symlinkDir + "/" + id[2:] + ".debug" 139 if err := os.Remove(symlinkPath); err != nil { 140 return err 141 } 142 } 143 } 144 145 // Add new symlinks and update changed symlinks. 146 for id, path := range buildIdToFile { 147 prevPath := prevBuildIdToFile[id] 148 if prevPath == path { 149 continue 150 } 151 symlinkDir := buildIdPath + "/" + id[:2] 152 symlinkPath := symlinkDir + "/" + id[2:] + ".debug" 153 if prevPath == "" { 154 if err := os.MkdirAll(symlinkDir, 0755); err != nil { 155 return err 156 } 157 } else { 158 if err := os.Remove(symlinkPath); err != nil { 159 return err 160 } 161 } 162 163 target, err := filepath.Rel(symlinkDir, path) 164 if err != nil { 165 return err 166 } 167 if err := os.Symlink(target, symlinkPath); err != nil { 168 return err 169 } 170 } 171 return nil 172} 173