Home | History | Annotate | Download | only in src
      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))
     88 fake_syscalls_init (void)
     89 {
     90   fake_syscalls_debug_init ();
     91   fake_syscalls_debug ("Initialized fakesyscalls library.");
     92 }
     93 
     94 void __attribute__ ((destructor))
     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
    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
    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
    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
    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