| /* |
| * Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #define _GNU_SOURCE /* for RTLD_NEXT in dlfcn.h */ |
| |
| #include <glib.h> |
| |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| /* The purpose of this library is to override the open/creat syscalls to |
| * redirect these calls for selected devices. Adding the library file to |
| * LD_PRELOAD is the general way to accomplish this. The arbitrary file mapping |
| * is specified in the environment variable FILE_REDIRECTION_PRELOADS as |
| * follows: |
| * |
| * FILE_REDIRECTIONS_PRELOAD=<path1>=<target1>:<path2>=<target2> |
| * |
| * Here, <path1> etc are the absolute paths to files for which open/close should |
| * be intercepted. <target1> etc are the alternative files to which these calls |
| * should be redirected. |
| * |
| * - ':' is used to separate file mappings |
| * - The special character ':' in the paths should be escaped with '\' |
| * |
| * Example: |
| * export FILE_REDIRECTIONS_PRELOAD=/tmp/file1=/tmp/file2 |
| * LD_PRELOAD=./libfakesyscalls.so ./write_to_tmp_file1 |
| * |
| * where write_to_tmp_file1 is some executable that opens and writes to |
| * /tmp/file1. When the program exits, /tmp/file2 would have been created and |
| * written to, not /tmp/file1. |
| * |
| * cf: fakesyscalls-exercise.c |
| * |
| * Thread safety: This library is not thread-safe. If two threads |
| * simultaneously call open/creat for the first time, internal data-structures |
| * in the library can be corrupted. |
| * It is safe to have subsequent calls to open/creat be concurrent. |
| */ |
| |
| #ifdef FAKE_SYSCALLS_DEBUG |
| static const char *k_tmp_logging_file_full_path = "/tmp/fake_syscalls.dbg"; |
| static FILE *debug_file; |
| |
| #define fake_syscalls_debug_init() \ |
| debug_file = fopen (k_tmp_logging_file_full_path, "w") |
| |
| #define fake_syscalls_debug(...) \ |
| do { \ |
| if (debug_file) { \ |
| fprintf (debug_file, __VA_ARGS__); \ |
| fprintf (debug_file, "\n"); \ |
| } \ |
| } while (0) |
| |
| #define fake_syscalls_debug_finish() \ |
| do { \ |
| if (debug_file) { \ |
| fclose (debug_file); \ |
| debug_file = NULL; \ |
| } \ |
| } while (0) |
| |
| #else /* FAKE_SYSCALLS_DEBUG */ |
| #define fake_syscalls_debug_init() |
| #define fake_syscalls_debug(...) |
| #define fake_syscalls_debug_finish() |
| #endif /* FAKE_SYSCALLS_DEBUG */ |
| |
| static GHashTable *file_redirection_map; |
| |
| static const char *k_env_file_redirections = "FILE_REDIRECTIONS_PRELOAD"; |
| static const char *k_func_open = "open"; |
| static const char *k_func_creat = "creat"; |
| |
| void __attribute__ ((constructor)) |
| fake_syscalls_init (void) |
| { |
| fake_syscalls_debug_init (); |
| fake_syscalls_debug ("Initialized fakesyscalls library."); |
| } |
| |
| void __attribute__ ((destructor)) |
| fake_syscalls_finish (void) |
| { |
| if (file_redirection_map) |
| g_hash_table_unref (file_redirection_map); |
| fake_syscalls_debug ("Quit fakesyscalls library."); |
| fake_syscalls_debug_finish (); |
| } |
| |
| static void |
| abort_on_error (GError *error) { |
| if (!error) |
| return; |
| |
| fake_syscalls_debug ("Aborting on error: |%s|", error->message); |
| g_error_free (error); |
| fake_syscalls_debug_finish (); |
| g_assert (0); |
| } |
| |
| static void |
| setup_redirection_map (void) |
| { |
| const char *orig_env; |
| GRegex *entry_delimiter, *key_value_delimiter, *escaped_colon; |
| gchar *buf; |
| gchar **redirections; |
| gchar **redirections_iter; |
| GError *error = NULL; |
| |
| file_redirection_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, |
| g_free); |
| |
| orig_env = getenv (k_env_file_redirections); |
| if (orig_env == NULL) |
| orig_env = ""; |
| fake_syscalls_debug ("FILE_REDIRECTIONS_PRELOAD=|%s|", orig_env); |
| |
| entry_delimiter = g_regex_new ("(?:([^\\\\]):)|(?:^:)", 0, 0, &error); |
| abort_on_error (error); |
| key_value_delimiter = g_regex_new ("=", 0, 0, &error); |
| abort_on_error (error); |
| escaped_colon = g_regex_new ("(?:[^\\\\]\\\\:)|(?:^\\\\:)", 0, 0, &error); |
| abort_on_error (error); |
| |
| buf = g_regex_replace (entry_delimiter, orig_env, -1, 0, "\\1;", 0, &error); |
| abort_on_error (error); |
| redirections = g_strsplit (buf, ";", 0); |
| g_free (buf); |
| |
| for (redirections_iter = redirections; |
| *redirections_iter; |
| ++redirections_iter) { |
| gchar **parts; |
| |
| if (g_strcmp0 ("", *redirections_iter) == 0) |
| continue; |
| |
| /* Any ':' in the map has to be escaped with a '\' to allow for ':' to act |
| * as delimiter. Clean away the '\'. |
| */ |
| buf = g_regex_replace_literal (escaped_colon, *redirections_iter, -1, 0, |
| ":", 0, &error); |
| abort_on_error (error); |
| parts = g_regex_split (key_value_delimiter, buf, 0); |
| g_free (buf); |
| |
| if (g_strv_length (parts) != 2) { |
| fake_syscalls_debug ("Error parsing redirection: |%s|. Malformed map?", |
| *redirections_iter); |
| g_strfreev (parts); |
| continue; |
| } |
| if (strlen (parts[0]) == 0 || parts[0][0] != '/' || |
| strlen (parts[1]) == 0 || parts[1][0] != '/') { |
| fake_syscalls_debug ("Error parsing redirection: |%s|." |
| "Invalid absolute paths.", |
| *redirections_iter); |
| g_strfreev (parts); |
| continue; |
| } |
| |
| fake_syscalls_debug ("Inserted redirection: |%s|->|%s|", |
| parts[0], parts[1]); |
| g_hash_table_insert (file_redirection_map, |
| g_strdup (parts[0]), g_strdup (parts[1])); |
| g_strfreev (parts); |
| } |
| |
| g_regex_unref (entry_delimiter); |
| g_regex_unref (key_value_delimiter); |
| g_regex_unref (escaped_colon); |
| g_strfreev (redirections); |
| } |
| |
| int |
| open (const char *pathname, int flags, ...) |
| { |
| static int(*realfunc)(const char *, int, ...); |
| const char *redirection; |
| va_list ap; |
| gboolean is_creat = FALSE; |
| mode_t mode = S_IRUSR; /* Make compiler happy. Remain restrictive. */ |
| |
| if (file_redirection_map == NULL) |
| setup_redirection_map (); |
| |
| redirection = (char *) g_hash_table_lookup (file_redirection_map, pathname); |
| if (redirection == NULL) |
| redirection = pathname; |
| |
| if (realfunc == NULL) |
| realfunc = (int(*)(const char *, int, ...))dlsym (RTLD_NEXT, k_func_open); |
| |
| is_creat = flags & O_CREAT; |
| |
| if (is_creat) { |
| va_start (ap, flags); |
| mode = va_arg (ap, mode_t); |
| va_end (ap); |
| fake_syscalls_debug ( |
| "Redirect: open (%s, %d, %d) --> open (%s, %d, %d)", |
| pathname, flags, mode, redirection, flags, mode); |
| return realfunc (redirection, flags, mode); |
| } else { |
| fake_syscalls_debug ( |
| "Redirect: open (%s, %d) --> open (%s, %d)", |
| pathname, flags, redirection, flags); |
| return realfunc (redirection, flags); |
| } |
| } |
| |
| int |
| creat (const char *pathname, mode_t mode) |
| { |
| static int(*realfunc)(const char *, mode_t); |
| const char *redirection; |
| |
| if (file_redirection_map == NULL) |
| setup_redirection_map (); |
| |
| redirection = (char *) g_hash_table_lookup (file_redirection_map, pathname); |
| if (redirection == NULL) |
| redirection = pathname; |
| fake_syscalls_debug ( |
| "Redirect: creat (%s, %d) --> creat (%s, %d)", |
| pathname, mode, redirection, mode); |
| |
| if (realfunc == NULL) |
| realfunc = (int(*)(const char *, mode_t))dlsym (RTLD_NEXT, k_func_creat); |
| |
| return realfunc (redirection, mode); |
| } |