| // Copyright 2024 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package elf |
| |
| import ( |
| "io/fs" |
| "os" |
| "path/filepath" |
| "strings" |
| "sync" |
| "time" |
| ) |
| |
| func UpdateBuildIdDir(path string) error { |
| path = filepath.Clean(path) |
| buildIdPath := path + "/.build-id" |
| |
| // Collect the list of files and build-id symlinks. If the symlinks are |
| // up to date (newer than the symbol files), there is nothing to do. |
| var buildIdFiles, symbolFiles []string |
| var buildIdMtime, symbolsMtime time.Time |
| filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error { |
| if entry == nil || entry.IsDir() { |
| return nil |
| } |
| info, err := entry.Info() |
| if err != nil { |
| return err |
| } |
| mtime := info.ModTime() |
| if strings.HasPrefix(path, buildIdPath) { |
| if buildIdMtime.Compare(mtime) < 0 { |
| buildIdMtime = mtime |
| } |
| buildIdFiles = append(buildIdFiles, path) |
| } else { |
| if symbolsMtime.Compare(mtime) < 0 { |
| symbolsMtime = mtime |
| } |
| symbolFiles = append(symbolFiles, path) |
| } |
| return nil |
| }) |
| if symbolsMtime.Compare(buildIdMtime) < 0 { |
| return nil |
| } |
| |
| // Collect build-id -> file mapping from ELF files in the symbols directory. |
| concurrency := 8 |
| done := make(chan error) |
| buildIdToFile := make(map[string]string) |
| var mu sync.Mutex |
| for i := 0; i != concurrency; i++ { |
| go func(paths []string) { |
| for _, path := range paths { |
| id, err := Identifier(path, true) |
| if err != nil { |
| done <- err |
| return |
| } |
| if id == "" { |
| continue |
| } |
| mu.Lock() |
| oldPath := buildIdToFile[id] |
| if oldPath == "" || oldPath > path { |
| buildIdToFile[id] = path |
| } |
| mu.Unlock() |
| } |
| done <- nil |
| }(symbolFiles[len(symbolFiles)*i/concurrency : len(symbolFiles)*(i+1)/concurrency]) |
| } |
| |
| // Collect previously generated build-id -> file mapping from the .build-id directory. |
| // We will use this for incremental updates. If we see anything in the .build-id |
| // directory that we did not expect, we'll delete it and start over. |
| prevBuildIdToFile := make(map[string]string) |
| out: |
| for _, buildIdFile := range buildIdFiles { |
| if !strings.HasSuffix(buildIdFile, ".debug") { |
| prevBuildIdToFile = nil |
| break |
| } |
| buildId := buildIdFile[len(buildIdPath)+1 : len(buildIdFile)-6] |
| for i, ch := range buildId { |
| if i == 2 { |
| if ch != '/' { |
| prevBuildIdToFile = nil |
| break out |
| } |
| } else { |
| if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') { |
| prevBuildIdToFile = nil |
| break out |
| } |
| } |
| } |
| target, err := os.Readlink(buildIdFile) |
| if err != nil || !strings.HasPrefix(target, "../../") { |
| prevBuildIdToFile = nil |
| break |
| } |
| prevBuildIdToFile[buildId[0:2]+buildId[3:]] = path + target[5:] |
| } |
| if prevBuildIdToFile == nil { |
| err := os.RemoveAll(buildIdPath) |
| if err != nil { |
| return err |
| } |
| prevBuildIdToFile = make(map[string]string) |
| } |
| |
| // Wait for build-id collection from ELF files to finish. |
| for i := 0; i != concurrency; i++ { |
| err := <-done |
| if err != nil { |
| return err |
| } |
| } |
| |
| // Delete old symlinks. |
| for id, _ := range prevBuildIdToFile { |
| if buildIdToFile[id] == "" { |
| symlinkDir := buildIdPath + "/" + id[:2] |
| symlinkPath := symlinkDir + "/" + id[2:] + ".debug" |
| if err := os.Remove(symlinkPath); err != nil { |
| return err |
| } |
| } |
| } |
| |
| // Add new symlinks and update changed symlinks. |
| for id, path := range buildIdToFile { |
| prevPath := prevBuildIdToFile[id] |
| if prevPath == path { |
| continue |
| } |
| symlinkDir := buildIdPath + "/" + id[:2] |
| symlinkPath := symlinkDir + "/" + id[2:] + ".debug" |
| if prevPath == "" { |
| if err := os.MkdirAll(symlinkDir, 0755); err != nil { |
| return err |
| } |
| } else { |
| if err := os.Remove(symlinkPath); err != nil { |
| return err |
| } |
| } |
| |
| target, err := filepath.Rel(symlinkDir, path) |
| if err != nil { |
| return err |
| } |
| if err := os.Symlink(target, symlinkPath); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |