| // 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 | 
 | } |