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