1 /*
2  * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3  * Use of this source code is governed by a BSD-style license that can be
4  * found in the LICENSE file.
5  */
6 
7 #define _GNU_SOURCE /* for RTLD_NEXT in dlfcn.h */
8 
9 #include <glib.h>
10 
11 #include <dlfcn.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <stdarg.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 
21 /* The purpose of this library is to override the open/creat syscalls to
22  * redirect these calls for selected devices. Adding the library file to
23  * LD_PRELOAD is the general way to accomplish this. The arbitrary file mapping
24  * is specified in the environment variable FILE_REDIRECTION_PRELOADS as
25  * follows:
26  *
27  * FILE_REDIRECTIONS_PRELOAD=<path1>=<target1>:<path2>=<target2>
28  *
29  * Here, <path1> etc are the absolute paths to files for which open/close should
30  * be intercepted. <target1> etc are the alternative files to which these calls
31  * should be redirected.
32  *
33  *  - ':' is used to separate file mappings
34  *  - The special character ':' in the paths should be escaped with '\'
35  *
36  *  Example:
37  *    export FILE_REDIRECTIONS_PRELOAD=/tmp/file1=/tmp/file2
38  *    LD_PRELOAD=./libfakesyscalls.so ./write_to_tmp_file1
39  *
40  *  where write_to_tmp_file1 is some executable that opens and writes to
41  *  /tmp/file1. When the program exits, /tmp/file2 would have been created and
42  *  written to, not /tmp/file1.
43  *
44  *  cf: fakesyscalls-exercise.c
45  *
46  *  Thread safety: This library is not thread-safe. If two threads
47  *  simultaneously call open/creat for the first time, internal data-structures
48  *  in the library can be corrupted.
49  *  It is safe to have subsequent calls to open/creat be concurrent.
50  */
51 
52 #ifdef FAKE_SYSCALLS_DEBUG
53 static const char *k_tmp_logging_file_full_path = "/tmp/fake_syscalls.dbg";
54 static FILE *debug_file;
55 
56 #define fake_syscalls_debug_init() \
57   debug_file = fopen (k_tmp_logging_file_full_path, "w")
58 
59 #define fake_syscalls_debug(...) \
60   do { \
61     if (debug_file) { \
62       fprintf (debug_file, __VA_ARGS__); \
63       fprintf (debug_file, "\n"); \
64     } \
65   } while (0)
66 
67 #define fake_syscalls_debug_finish() \
68   do { \
69     if (debug_file) { \
70       fclose (debug_file); \
71       debug_file = NULL; \
72     } \
73   } while (0)
74 
75 #else /* FAKE_SYSCALLS_DEBUG */
76 #define fake_syscalls_debug_init()
77 #define fake_syscalls_debug(...)
78 #define fake_syscalls_debug_finish()
79 #endif  /* FAKE_SYSCALLS_DEBUG */
80 
81 static GHashTable *file_redirection_map;
82 
83 static const char *k_env_file_redirections = "FILE_REDIRECTIONS_PRELOAD";
84 static const char *k_func_open = "open";
85 static const char *k_func_creat = "creat";
86 
87 void __attribute__ ((constructor))
fake_syscalls_init(void)88 fake_syscalls_init (void)
89 {
90   fake_syscalls_debug_init ();
91   fake_syscalls_debug ("Initialized fakesyscalls library.");
92 }
93 
94 void __attribute__ ((destructor))
fake_syscalls_finish(void)95 fake_syscalls_finish (void)
96 {
97   if (file_redirection_map)
98     g_hash_table_unref (file_redirection_map);
99   fake_syscalls_debug ("Quit fakesyscalls library.");
100   fake_syscalls_debug_finish ();
101 }
102 
103 static void
abort_on_error(GError * error)104 abort_on_error (GError *error) {
105   if (!error)
106     return;
107 
108   fake_syscalls_debug ("Aborting on error: |%s|", error->message);
109   g_error_free (error);
110   fake_syscalls_debug_finish ();
111   g_assert (0);
112 }
113 
114 static void
setup_redirection_map(void)115 setup_redirection_map (void)
116 {
117   const char *orig_env;
118   GRegex *entry_delimiter, *key_value_delimiter, *escaped_colon;
119   gchar *buf;
120   gchar **redirections;
121   gchar **redirections_iter;
122   GError *error = NULL;
123 
124   file_redirection_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
125                                                 g_free);
126 
127   orig_env = getenv (k_env_file_redirections);
128   if (orig_env == NULL)
129     orig_env = "";
130   fake_syscalls_debug ("FILE_REDIRECTIONS_PRELOAD=|%s|", orig_env);
131 
132   entry_delimiter = g_regex_new ("(?:([^\\\\]):)|(?:^:)", 0, 0, &error);
133   abort_on_error (error);
134   key_value_delimiter = g_regex_new ("=", 0, 0, &error);
135   abort_on_error (error);
136   escaped_colon = g_regex_new ("(?:[^\\\\]\\\\:)|(?:^\\\\:)", 0, 0, &error);
137   abort_on_error (error);
138 
139   buf = g_regex_replace (entry_delimiter, orig_env, -1, 0, "\\1;", 0, &error);
140   abort_on_error (error);
141   redirections = g_strsplit (buf, ";", 0);
142   g_free (buf);
143 
144   for (redirections_iter = redirections;
145        *redirections_iter;
146        ++redirections_iter) {
147     gchar **parts;
148 
149     if (g_strcmp0 ("", *redirections_iter) == 0)
150       continue;
151 
152     /* Any ':' in the map has to be escaped with a '\' to allow for ':' to act
153      * as delimiter. Clean away the '\'.
154      */
155     buf = g_regex_replace_literal (escaped_colon, *redirections_iter, -1, 0,
156                                    ":", 0, &error);
157     abort_on_error (error);
158     parts = g_regex_split (key_value_delimiter, buf, 0);
159     g_free (buf);
160 
161     if (g_strv_length (parts) != 2) {
162       fake_syscalls_debug ("Error parsing redirection: |%s|. Malformed map?",
163                            *redirections_iter);
164       g_strfreev (parts);
165       continue;
166     }
167     if (strlen (parts[0]) == 0 || parts[0][0] != '/' ||
168         strlen (parts[1]) == 0 || parts[1][0] != '/') {
169       fake_syscalls_debug ("Error parsing redirection: |%s|."
170                            "Invalid absolute paths.",
171                            *redirections_iter);
172       g_strfreev (parts);
173       continue;
174     }
175 
176     fake_syscalls_debug ("Inserted redirection: |%s|->|%s|",
177                          parts[0], parts[1]);
178     g_hash_table_insert (file_redirection_map,
179                          g_strdup (parts[0]), g_strdup (parts[1]));
180     g_strfreev (parts);
181   }
182 
183   g_regex_unref (entry_delimiter);
184   g_regex_unref (key_value_delimiter);
185   g_regex_unref (escaped_colon);
186   g_strfreev (redirections);
187 }
188 
189 int
open(const char * pathname,int flags,...)190 open (const char *pathname, int flags, ...)
191 {
192   static int(*realfunc)(const char *, int, ...);
193   const char *redirection;
194   va_list ap;
195   gboolean is_creat = FALSE;
196   mode_t mode = S_IRUSR;  /* Make compiler happy. Remain restrictive. */
197 
198   if (file_redirection_map == NULL)
199     setup_redirection_map ();
200 
201   redirection = (char *) g_hash_table_lookup (file_redirection_map, pathname);
202   if (redirection == NULL)
203     redirection = pathname;
204 
205   if (realfunc == NULL)
206     realfunc = (int(*)(const char *, int, ...))dlsym (RTLD_NEXT, k_func_open);
207 
208   is_creat = flags & O_CREAT;
209 
210   if (is_creat) {
211     va_start (ap, flags);
212     mode = va_arg (ap, mode_t);
213     va_end (ap);
214     fake_syscalls_debug (
215         "Redirect: open (%s, %d, %d) --> open (%s, %d, %d)",
216         pathname, flags, mode, redirection, flags, mode);
217     return realfunc (redirection, flags, mode);
218   } else {
219     fake_syscalls_debug (
220         "Redirect: open (%s, %d) --> open (%s, %d)",
221         pathname, flags, redirection, flags);
222     return realfunc (redirection, flags);
223   }
224 }
225 
226 int
creat(const char * pathname,mode_t mode)227 creat (const char *pathname, mode_t mode)
228 {
229   static int(*realfunc)(const char *, mode_t);
230   const char *redirection;
231 
232   if (file_redirection_map == NULL)
233     setup_redirection_map ();
234 
235   redirection = (char *) g_hash_table_lookup (file_redirection_map, pathname);
236   if (redirection == NULL)
237     redirection = pathname;
238   fake_syscalls_debug (
239       "Redirect: creat (%s, %d) --> creat (%s, %d)",
240       pathname, mode, redirection, mode);
241 
242   if (realfunc == NULL)
243     realfunc = (int(*)(const char *, mode_t))dlsym (RTLD_NEXT, k_func_creat);
244 
245   return realfunc (redirection, mode);
246 }
247