1 /* Recursively find all shared libraries referenced by the executables and
2  * shared libraries given on the command line.
3  * The output list must be run through sort -u to remove duplicates.
4  * An example command line might be
5  *
6  * otoolr <builddir> /opt/local/bin/ffmpeg /opt/local/bin/gm ... | sort -u > t.lis
7  *
8  * The <builddir> is prepended if any of the executables or dylib files have relative paths,
9  * otherwise not used.
10  *
11  * P. Wessel, August 2020
12  */
13 
14 #include <stdio.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <stdlib.h>
18 
19 struct LINK {
20 	/* Struct with shared library name and pointer to next */
21 	char name[256];
22 	struct LINK *next;
23 };
24 
used(struct LINK * list,char * name)25 int used (struct LINK *list, char *name) {
26 	/* Determine if this library has already been processed */
27 	struct LINK *this = list;
28 	while (this && strcmp (name, this->name))
29 		this = this->next;
30 	return (this ? 1 : 0);
31 }
32 
last(struct LINK * list)33 struct LINK * last (struct LINK *list) {
34 	/* Find the last link in the chain so we can append to it */
35 	struct LINK *this = list;
36 	while (this->next)
37 		this = this->next;
38 	return (this);
39 }
40 
printlist(struct LINK * list)41 void printlist (struct LINK *list) {
42 	/* March through and print the entries except anything in /Users, free up along the way */
43 	struct LINK *this = list, *prt;
44 	while (this->next) {
45 		prt = this->next;
46 		if (!strstr (prt->name, "/Users")) printf ("%s\n", prt->name);
47 		free (this);
48 		this = prt;
49 	}
50 }
51 
52 /* Recursive function. Set verb = 1 for debugging */
53 
get_list(char * top,char * name,int level,struct LINK * list)54 void get_list (char *top, char *name, int level, struct LINK *list) {
55 	/* Recursive scanner of entries returned by otool -L */
56 	char process[256] = {""}, next[256] = {""}, line[256] = {""}, dir[256] = {""}, *c = NULL;
57 	int k, library = (strstr (name, ".dylib") != NULL), verb = 0;
58 	FILE *fp = NULL;
59 	struct LINK *this = NULL;
60 
61 	strcpy (dir, top);
62 	c = strrchr (dir, '/');	c[0] = '\0';
63 	if (name[0] == '/')	/* Full path */
64 		sprintf (process, "otool -L %s", name);
65 	else if (library)	/* In the build dirs path */
66 		sprintf (process, "otool -L %s/%s", dir, name);
67 	else	/* Executable */	/* In the bin dir */
68 		sprintf (process, "otool -L %s/bin/%s", top, name);
69 	if (verb) fprintf (stderr, "[%d-%d] Running: %s\n", level, library, process);
70 	if ((fp = popen (process, "r")) == NULL) return;
71 	fgets (line, 256U, fp);		/* Skip header record */
72 	while (fgets (line, 256U, fp)) {
73 		k = 0; while (isspace (line[k])) k++;	/* Scan to start of library name */
74 		sscanf (&line[k], "%s", next);	/* Get the shared library file name */
75 		if (strstr (line, "/usr/lib") || strstr (line, "/System/Library") || (library && strstr (line, name))) {
76 			/* Skip system files or itself */
77 			if (verb) fprintf (stderr, "[%d-%d] Skip: %s\n", level, library, next);
78 			continue;
79 		}
80 
81 		if (used (list, next)) {	/* Already processed this library */
82 			if (verb) fprintf (stderr, "[%d-%d] Used: %s\n", level, library, next);
83 			continue;
84 		}
85 		if (next[0] == '/') {
86 			if (verb) fprintf (stderr, "[%d] Test: %s\n", level, next);
87 			this = last (list);
88 			this->next = calloc (1, sizeof (struct LINK));
89 			strcpy (this->next->name, next);
90 			get_list (top, next, level + 1, list);
91 		}
92 	}
93 	pclose (fp);
94 }
95 
main(int argc,char ** argv)96 int main (int argc, char **argv) {
97 	int k;
98 	struct LINK *list = calloc (1, sizeof (struct LINK));
99 	strcpy (list->name, "HEAD");	/* Unused head of the linked list */
100 	for (k = 2; k < argc; k++)	/* Try all the arguments (argv[1] is the builddir) */
101 		get_list (argv[1], argv[k], 0, list);
102 	printlist (list);
103 }
104