blob: 5fb7dda87663f809e39564357ace66d7387c36ac [file] [log] [blame]
Peter Collingbourneb805c612024-03-14 21:59:57 -07001// 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}