1#!/bin/bash
2#
3# Copyright (C) 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17### Usage: shares_common_elfs.sh [-m] [-a] <path/to/dump/directory/or/elf>
18###
19### This script is used to eliminate copies of the same ELF file(s) across
20### subdirectories of 'offline_files/' by placing common ELF(s) in
21### 'offline_files/common'. The build id is appended to each ELF name to allow
22### for different versions of the same shared library. Unless -a is used, the
23### path must be a valid ELF file.
24###
25### Options:
26### -m
27###    If the ELF(s) does not currently reside in the 'common' directory, move
28###     the elf to 'common' and rename it.
29### -a
30###    Eliminate copies for all ELFs in a directory. The path specified point
31###    to a directory and NOT an individual ELF file.
32
33usage() {
34  grep '^###' $0 | sed -e 's/^###//'
35  exit 1
36}
37
38update_elf() {
39  local elf_path="$1"
40  local move_if_not_in_common="$2"
41
42  local basename=$(basename "$elf_path")
43  local maps_path=$(dirname "$elf_path")"/maps.txt"
44  local build_id=$(readelf -n "$elf_path" | grep -oP 'Build ID: \K([\w\d]+)')
45  local new_elf_name="${basename}_${build_id}"
46  local common_dir="${ANDROID_BUILD_TOP}/system/unwinding/libunwindstack/offline_files/common/"
47
48  if [[ -f "${common_dir}${new_elf_name}" ]]; then
49    # If the ELF is already found in the common directory, delete the local copy.
50    git rm "$elf_path"
51  elif [[ $move_if_not_in_common == true ]]; then
52    # If the ELF is not found in the common directory and the `move_if_not_common`
53    # flag was set, move the local ELF to the common directory.
54    git mv "$elf_path" "${common_dir}${new_elf_name}"
55  else
56    # The ELF was not found in the common directory so exit this function.
57    return 0
58  fi
59
60  # Replace the name of the elf we just deleted/moved to the relative
61  # path to that ELF in the common directory.
62  local elf_dir_path=$(dirname "$elf_path")
63  local rel_path_to_common=$(realpath --relative-to="$elf_dir_path" "$common_dir")
64  sed -i -e "s/${basename}/${rel_path_to_common}\/${new_elf_name}/g" "$maps_path"
65}
66
67is_an_elf() {
68  if [[ $(head -c 4 $1 | cut -c2-) == "ELF" ]]; then
69    echo true
70  else
71    echo false
72  fi
73}
74
75main () {
76  set -e # abort the script if some error occurs.
77  local move_if_not_in_common=false
78  local update_all_elfs=false
79  while getopts ":hma" arg; do
80    case $arg in
81      m)
82        move_if_not_in_common=true
83        ;;
84      a)
85        update_all_elfs=true
86        ;;
87      h | *)
88        usage
89        ;;
90    esac
91  done
92  path=${@:$OPTIND:1}
93  if [[ -z $path ]]; then
94    usage
95  fi
96
97  if [[ $update_all_elfs == true ]]; then
98    if [[ ! -d $path || "${path: -1}" != "/" ]]; then
99      echo "$path is not a valid path to a directory." >&2
100      usage
101    fi
102    for elf_path in "${path}"*; do
103      if [[ $(is_an_elf $elf_path) == true ]]; then
104        update_elf $elf_path $move_if_not_in_common
105      fi
106    done
107  else
108    if [[ ! -f $path ]]; then
109      echo "$path is not a valid path to a file." >&2
110      usage
111    elif [[ $(is_an_elf $path) == false ]]; then
112      echo "$path is not a valid ELF file." >&2
113    else
114      update_elf $path $move_if_not_in_common
115    fi
116  fi
117}
118
119main "$@"