summarylogtreecommitdiffstats
path: root/findbrokenpkgs-1.1.sh
blob: ee596285997e0c39f699f344d3835e871eed765f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#! /bin/bash
# Distributed under the terms of the GNU General Public License v2

# Shamelessly copied mostly from Gentoo's revdep-rebuild utility.
# http://sources.gentoo.org/viewcvs.py/gentoo-x86/app-portage/gentoolkit/
# $ equery belongs /usr/bin/revdep-rebuild
# app-portage/gentoolkit-0.2.3-r1
# $ cat /usr/portage/app-portage/gentoolkit/gentoolkit-0.2.3-r1.ebuild | grep LICENSE
# LICENSE="GPL-2"

# findbrokenpkgs for Arch Linux.
# Converted to use pacman instead of emerge by Paul Bredbury <brebs@sent.com>
#
# v1.1 - files now in $HOME/.findbrokenpkgs/ directory - based on changes from
# Stefan Husmann, repackaged by Jaroslav Lichtblau

# Customizable variables:
#
# LD_LIBRARY_MASK - Mask of specially evaluated libraries
# SEARCH_DIRS - List of directories to search for executables and libraries
# SEARCH_DIRS_MASK - List of directories to not search
#
# These variables can be prepended to by setting the variable in
# your environment prior to execution.
#
# An entry of "-*" means to clear the variable from that point forward.
# Example: env SEARCH_DIRS="/usr/bin -*" findbrokenpkgs will set SEARCH_DIRS
# to contain only /usr/bin

if [ "$1" = "-h" -o "$1" = "-help" -o "$1" = "--help" ] ; then
	echo "Broken package identifier, version 1.0"
	echo "Checks dynamic library linking."
	echo
	echo "Usage: $0 [OPTIONS]"
	echo
	echo " -nc, --no-color       Turn off colored output"
	echo " -nw, --no-warning     Disable newbie-friendly warning"
	echo "  -q, --quiet          Be less verbose"
	echo
	echo "Report bugs to http://bbs.archlinux.org/viewtopic.php?id=13882"
	exit 0
fi

# Update the incremental variables using /etc/profile.env, /etc/ld.so.conf,
# and the environment.

# Read the incremental variables from environment
PRELIMINARY_SEARCH_DIRS="$SEARCH_DIRS"
PRELIMINARY_SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK"
PRELIMINARY_LD_LIBRARY_MASK="$LD_LIBRARY_MASK"
SONAME_SEARCH="not found"
SONAME_GREP=grep

# Add the defaults
if [ -d /etc/findbrokenpkgs ] ; then
	for file in $(ls /etc/findbrokenpkgs) ; do
		PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(. /etc/findbrokenpkgs/${file}; echo $SEARCH_DIRS)"
		PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK $(. /etc/findbrokenpkgs/${file}; echo $SEARCH_DIRS_MASK)"
		PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK $(. /etc/findbrokenpkgs/${file}; echo $LD_LIBRARY_MASK)"
	done
else
	PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*"
	# openoffice is a binary, and we don't want to check that monster
	PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK /opt/openoffice"
	# Binary libraries
	PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK libodbcinst.so libodbc.so libjava.so libjvm.so"
fi

# Get the ROOTPATH and PATH from /etc/profile.env
if [ -e "/etc/profile.env" ] ; then
	PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $((. /etc/profile.env; echo ${ROOTPATH}:${PATH}) | tr ':' ' ')"
fi

# Get the directories from /etc/ld.so.conf
if [ -e /etc/ld.so.conf ] ; then
	PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(grep -v "^#" /etc/ld.so.conf | tr '\n' ' ')"
fi

# Set the final variables
# Note: Using $(echo $variable) removes extraneous spaces from variable assignment
unset SEARCH_DIRS
for i in $(echo $PRELIMINARY_SEARCH_DIRS) ; do
	[ "$i" = "-*" ] && break
	# Append a / at the end so that links and directories are treated the same by find
	# Remove any existing trailing slashes to prevent double-slashes
	SEARCH_DIRS="$(echo $SEARCH_DIRS ${i/%\//}/)"
done
# Remove any double-slashes from the path
SEARCH_DIRS="$(echo $SEARCH_DIRS | sed 's:/\+:/:g')"

unset SEARCH_DIRS_MASK
for i in $(echo $PRELIMINARY_SEARCH_DIRS_MASK) ; do
	[ "$i" = "-*" ] && break
	SEARCH_DIRS_MASK="$(echo $SEARCH_DIRS_MASK $i)"
done

unset LD_LIBRARY_MASK
for i in $(echo $PRELIMINARY_LD_LIBRARY_MASK) ; do
	[ "$i" = "-*" ] && break
	LD_LIBRARY_MASK="$(echo $LD_LIBRARY_MASK $i)"
done

# Base of temporary files names.
[ -d ${HOME}/.findbrokenpkgs ] || mkdir ${HOME}/.findbrokenpkgs
touch ${HOME}/.findbrokenpkgs/findbrokenpkgs_0.test 2>/dev/null
if [ $? -eq 0 ] ; then
	LIST="${HOME}/.findbrokenpkgs/findbrokenpkgs"
	rm ~/.findbrokenpkgs/findbrokenpkgs_0.test
else
	# Try to use /var/tmp since $HOME is not available
	touch /var/tmp/.findbrokenpkgs/findbrokenpkgs_0.test 2>/dev/null
	if [ $? -eq 0 ] ; then
	LIST="/var/tmp/.findbrokenpkgs/findbrokenpkgs"
	rm /var/tmp/.findbrokenpkgs/findbrokenpkgs_0.test
	else
		echo
		echo "!!! Unable to write temporary files to either $HOME or /var/tmp !!!"
		echo
		exit 1
	fi
fi

shopt -s nullglob
shopt -s expand_aliases
unalias -a
alias echo_v=echo

while [ ! -z "$1" ] ; do
	case "$1" in
	-q | --quiet )
		alias echo_v=:
		shift
		;;
	-nc | --no-color )
		NOCOLOR=true
		shift
		;;
	-nw | --no-warning )
		NOWARNING=true
		shift
		;;
	* )
		echo "Unknown option:  $1"
		exit 1
		;;
	esac
done

# Color Definitions
if [ "$NOCOLOR" = "yes" -o "$NOCOLOR" = "true" ] ; then
	NO=""
	BR=""
	CY=""
	GR=""
	RD=""
	YL=""
	BL=""
else
	NO="\x1b[0m"
	BR="\x1b[0;01m"
	CY="\x1b[36;01m"
	GR="\x1b[32;01m"
	RD="\x1b[31;01m"
	YL="\x1b[33;01m"
	BL="\x1b[34;01m"
fi

function set_trap () {
	trap "rm_temp $1" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
}

function rm_temp () {
	echo " terminated."
	echo "Removing incomplete $1."
	rm $1
	echo
	exit 1
}

# Want program results (especially from pacman) in English
export LC_ALL=C

# Always delete the temporary files from a previous run
rm -f ${LIST}.[0-9]_*

# Clean up no longer needed environment variables
unset PREVIOUS_SEARCH_DIRS PREVIOUS_SEARCH_DIRS_MASK PREVIOUS_LD_LIBRARY_MASK PREVIOUS_PREVIOUS_OPTIONS
unset PRELIMINARY_SEARCH_DIRS PRELIMINARY_SEARCH_DIRS_MASK PRELIMINARY_LD_LIBRARY_MASK

# Log our environment
echo "SEARCH_DIRS=\"$SEARCH_DIRS\"" > $LIST.0_env
echo "SEARCH_DIRS_MASK=\"$SEARCH_DIRS_MASK\"" >> $LIST.0_env
echo "LD_LIBRARY_MASK=\"$LD_LIBRARY_MASK\"" >> $LIST.0_env

echo_v
echo_v "Checking reverse dependencies..."
echo_v
echo_v -n -e "${GR}Collecting system binaries and libraries...${NO}"

set_trap "$LIST.1_*"

# Be extra paranoid and pipe results through sed to remove multiple slashes
# using -perm /u+x for find command
find $SEARCH_DIRS -type f \( -perm /u+x -o -name '*.so' -o -name '*.so.*' -o -name '*.la' \) 2>/dev/null | sort | uniq | sed 's:/\+:/:g' >$LIST.0_files

# Remove files that match SEARCH_DIR_MASK
for dir in $SEARCH_DIRS_MASK ; do
	grep -v "^$dir" $LIST.0_files > $LIST.1_files
	mv $LIST.1_files $LIST.0_files
done

mv $LIST.0_files $LIST.1_files
echo_v -e " ${GR}done.${NO}\n  ($LIST.1_files)"

echo_v
echo_v -n -e "${GR}Collecting complete LD_LIBRARY_PATH...${NO}"
set_trap "$LIST.2_ldpath"
# Ensure that the "trusted" lib directories are at the start of the path
(
	echo /lib* /usr/lib* | sed 's/ /:/g'
	sed '/^#/d;s/#.*$//' </etc/ld.so.conf
	sed 's:/[^/]*$::' <$LIST.1_files | sort -ru
) | tr '\n' : | tr -d '\r' | sed 's/:$//' >$LIST.2_ldpath
echo_v -e " ${GR}done.${NO}\n  ($LIST.2_ldpath)"
COMPLETE_LD_LIBRARY_PATH="$(cat $LIST.2_ldpath)"

echo_v
echo_v -e "${GR}Checking dynamic linking consistency...${NO}"
set_trap "$LIST.3_rebuild"
LD_MASK="\\(	$(echo "$LD_LIBRARY_MASK" | sed 's/\./\\./g;s/ / \\|	/g') \\)"
echo -n > $LIST.3_rebuild
cat $LIST.1_files | egrep -v '*\.la$' | while read FILE ; do
	# Note: double checking seems to be faster than single
	# with complete path (special add-ons are rare).
	if ldd "$FILE" 2>/dev/null | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then
		if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$FILE" 2>/dev/null | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then
			# Only build missing direct dependencies
			ALL_MISSING_LIBS=$(ldd "$FILE" 2>/dev/null | sort -u | sed -n 's/	\(.*\) => not found/\1/p' | tr '\n' ' ' | sed 's/ $//' )
			REQUIRED_LIBS=$(objdump -x $FILE | grep NEEDED | awk '{print $2}' | tr '\n' ' ' | sed 's/ $//')
			MISSING_LIBS=""
			for lib in $ALL_MISSING_LIBS ; do
				if echo $REQUIRED_LIBS | grep -q $lib ; then
					MISSING_LIBS="$MISSING_LIBS $lib"
				fi
			done
			if [ "$MISSING_LIBS" != "" ] ; then
				echo "$FILE" >> $LIST.3_rebuild
				# MISSING_LIBS already starts with a space
				echo_v -e "  $FILE ${RD}needs missing${NO}${MISSING_LIBS}"
			fi
		fi
	fi
done
# Not sure if *.la files should even be checked
cat $LIST.1_files | egrep '*\.la$' | while read FILE ; do
	for depend in $(grep '^dependency_libs' $FILE | awk -F'=' '{print $2}' | sed "s/'//g") ; do
		[ ${depend:0:1} != '/' ] && continue
		if [ ! -e $depend ] ; then
			echo "$FILE" >> $LIST.3_rebuild
			echo_v -e "  $FILE ${RD}needs missing${NO} ${depend}"
		fi
	done
done
echo_v -e " ${GR}done${NO}.\n  ($LIST.3_rebuild)"

echo_v
echo_v -n -e "${GR}Assigning files to packages...${NO}"
set_trap "$LIST.4_*"
echo -n > $LIST.4_package_owners
echo -n > $LIST.4_packages_raw
echo -n > $LIST.4_orphans

cat $LIST.3_rebuild | while read FILE ; do
	EXACT_PKG=$(pacman -Qo $FILE | awk '{print $5 " " $6}')
	PKG=$(echo $EXACT_PKG | awk '{print $1}')
	if [ -z "$PKG" ] ; then
		echo_v -n -e "\n  ${RD}*** $FILE is orphan & broken! ***${NO}"
		echo "$FILE -> (none)" >> $LIST.4_package_owners
		echo "$FILE" >> $LIST.4_orphans
		echo_v -n -e "\n  $FILE ${RD}-> (none)${NO}"
	else
		echo "$PKG" >> $LIST.4_packages_raw
		echo "$FILE -> $EXACT_PKG" >> $LIST.4_package_owners
		echo_v -n -e "\n  $FILE ${CY}->${NO} ${BR}$PKG${NO}"
	fi
done
echo_v
echo_v -e " ${GR}done.${NO}\n  ($LIST.4_*)"

echo_v
echo_v -n -e "${GR}Cleaning list of packages to rebuild...${NO}"
set_trap "$LIST.5_packages"
sort -u $LIST.4_packages_raw >$LIST.5_packages
echo_v -e " ${GR}done.${NO}\n  ($LIST.5_packages)"

REBUILD_LIST="$(cat $LIST.5_packages | tr '\n' ' ')"
ORPHAN_LIST="$(cat $LIST.4_orphans)"

# Clean up no longer needed environment variables
unset COMPLETE_LD_LIBRARY_PATH SEARCH_DIRS SEARCH_DIRS_MASK LD_LIBRARY_MASK

trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

if [ -z "$REBUILD_LIST" ] && [ -z "$ORPHAN_LIST" ] ; then
	echo_v -e "\n${GR}Dynamic linking on your system is consistent.${NO}"
	# All OK, so delete temporary files
	rm -f ${LIST}.[0-9]_*
else
	# Show broken files & packages
	if [ -n "$ORPHAN_LIST" ] ; then
		echo -e "\n${RD}Orphaned broken files:${NO}"
		echo "$ORPHAN_LIST"
		echo_v -e "\n${GR}This list of orphaned broken files is in $LIST.4_orphans${NO}"
	fi
	if [ -n "$REBUILD_LIST" ] ; then
		echo -e "\n${RD}Recompile these packages:${NO}"
		echo -e "${BR}$REBUILD_LIST${NO}"
		if ! [ "$NOWARNING" = "yes" -o "$NOWARNING" = "true" ] ; then
			echo_v -e "\nSome/all breakages may be ${GR}OK${NO} - this program cannot distinguish between ${RD}required${NO}"
			echo_v -e "and ${GR}optional${NO} dependencies. See http://bbs.archlinux.org/viewtopic.php?id=13882"
		fi
	fi
	# The temporary files are deliberately not deleted, as a source of info
fi

exit 0