Version 1.3.
[autobuildrequires.git] / auto-buildrequires-preload.c
1 /* Automatic generation of BuildRequires dependencies for rpmbuild.
2  * Copyright (C) 2008 Red Hat Inc.
3  * Written by Richard W.M. Jones <rjones@redhat.com>
4  * Patches from Rajeesh K Nambiar <rajeeshknambiar@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 /* This is an LD_PRELOAD extension which logs accesses to any files by
22  * trapping access to any system calls which open files.  Details of
23  * opened files are written to the file named in
24  * the AUTO_BUILDREQUIRES_LOGFILE environment variable.
25  */
26
27 #define _GNU_SOURCE
28
29 #include <stdio.h>
30 #include <limits.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <dlfcn.h>
39 #include <gnu/lib-names.h>
40
41 #define ALIAS(ret,syscall,params,brfunc)                                \
42   extern ret syscall params __attribute__((alias (brfunc)))
43
44 static char *br_path (const char *path);
45 static void br_init (void) __attribute__((constructor));
46 static void br_log (const char *fs, ...) __attribute__((format (printf,1,2)));
47
48 /* These are the real glibc symbols, initialized in br_init. */
49 static int (*glibc_open) (const char *pathname, int flags, mode_t mode);
50 static int (*glibc_execve) (const char *filename, char *const argv[],
51                             char *const envp[]);
52
53 /* Canonicalize a relative or absolute path.  If that isn't possible,
54  * return an absolute path.
55  *
56  * The caller must free the result.
57  */
58 static char *
59 br_path (const char *path)
60 {
61   char *dir, *cat, *rp;
62   int len;
63
64   if (path == NULL) return NULL;
65
66   if (path[0] == '/') {
67     rp = realpath (path, NULL);
68     if (!rp) rp = strdup (path);
69     return rp;
70   }
71
72   dir = get_current_dir_name ();
73   if (dir == NULL) return NULL;
74
75   len = strlen (dir) + 1 + strlen (path) + 1;
76   cat = malloc (len);
77   if (cat == NULL) {
78     perror ("malloc");
79     abort ();
80   }
81
82   strcpy (cat, dir);
83   strcat (cat, "/");
84   strcat (cat, path);
85
86   rp = realpath (cat, NULL);
87   free (dir);
88   if (!rp) rp = cat; else free (cat);
89   return rp;
90 }
91
92 int
93 abr_open (const char *pathname, int flags, mode_t mode)
94 {
95   int fd;
96   char *rp;
97
98   /* Constructors in other libraries can call this function before
99    * br_init has been called.  Detect this and call br_init to avoid a
100    * crash.
101    */
102   if (glibc_open == NULL)
103     br_init ();
104
105   rp = br_path (pathname);
106   if (rp)
107     br_log ("open %s\n", rp);
108   else
109     perror (pathname);
110   free (rp);
111
112   fd = glibc_open (pathname, flags, mode);
113   return fd;
114 }
115
116 ALIAS (int, open, (const char *, int, ...), "abr_open");
117
118 int
119 abr_execve (const char *filename, char *const argv[], char *const envp[])
120 {
121   int r;
122   char *rp;
123
124   /* Constructors in other libraries can call this function before
125    * br_init has been called.  Detect this and call br_init to avoid a
126    * crash.
127    */
128   if (glibc_open == NULL)
129     br_init ();
130
131   rp = br_path (filename);
132   if (rp)
133     br_log ("execve %s\n", rp);
134   else
135     perror (filename);
136   free (rp);
137
138   r = glibc_execve (filename, argv, envp);
139   return r;
140 }
141
142 ALIAS (int, execve, (const char *filename, char *const argv[],
143                      char *const envp[]), "abr_execve");
144
145 /* Other syscalls to consider:
146  * access
147  * statfs
148  * stat, lstat
149  * chdir
150  * mkdir
151  * unlink
152  * readlink
153  * 'connect' params can contain a path in some rare circumstances.
154  */
155
156 /* This is the logging function.  We have to be very cautious in this
157  * function and not disturb any part of the process (so far as that is
158  * possible).  eg. When we open a file descriptor we must close it
159  * afterwards.
160  *
161  * Any errors should either call abort() if they are serious, or
162  * return without harm if not.
163  *
164  * Also remember to call glibc_* for any syscalls that we are
165  * intercepting, otherwise you'll get an infinite loop.
166  */
167 static void
168 br_log (const char *fs, ...)
169 {
170   const char *filename;
171   int fd, len;
172   va_list args;
173   char *msg;
174
175   /* Got the logging environment variable?  Should we cleanse this
176    * variable for security reasons, or is it not a problem because RPM
177    * specfiles can do arbitrary Bad Stuff already? (XXX)
178    */
179   filename = getenv ("AUTO_BUILDREQUIRES_LOGFILE");
180   if (filename == NULL) return;
181
182   fd = glibc_open (filename, O_WRONLY | O_APPEND, 0);
183   if (fd == -1) { perror ("open logfile"); abort (); }
184
185   /* Create the log string. */
186   va_start (args, fs);
187   len = vasprintf (&msg, fs, args);
188   if (len == -1) {
189     perror ("vasprintf");
190     abort ();
191   }
192   va_end (args);
193
194   /* Write it in a single operation.  Should be atomic, right?? (XXX) */
195   if (write (fd, msg, len) != len) {
196     perror ("write");
197     abort ();
198   }
199
200   close (fd);
201   free (msg);
202 }
203
204 static void
205 br_init (void)
206 {
207   _Atomic static void *dl;
208
209   if (dl != NULL) return;
210
211   dl = dlopen ("/lib64/" LIBC_SO, RTLD_LAZY|RTLD_LOCAL);
212   if (dl == NULL)       // Try '/lib/' also
213           dl = dlopen("/lib/" LIBC_SO, RTLD_LAZY|RTLD_LOCAL);
214   if (dl == NULL) {
215     fprintf (stderr, "%s\n", dlerror ());
216     abort ();
217   }
218   glibc_open = dlsym (dl, "open");
219   glibc_execve = dlsym (dl, "execve");
220 }