xref: /aosp_15_r20/build/soong/elf/build_id_dir.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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