├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── path-mapping.c └── test ├── integration-tests.sh ├── test-pathmatching.c ├── testtool-execl.c ├── testtool-fts.c ├── testtool-ftw.c ├── testtool-nftw.c ├── testtool-printenv.c └── testtool-utime.c /.gitignore: -------------------------------------------------------------------------------- 1 | path-mapping*.so 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Fritz Webering 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | override CFLAGS += -std=c99 -Wall 2 | 3 | SRCDIR = $(CURDIR) 4 | TESTDIR ?= /tmp/path-mapping 5 | TESTTOOLS = $(notdir $(basename $(wildcard $(SRCDIR)/test/testtool-*.c))) 6 | UNIT_TESTS = test-pathmatching 7 | 8 | path-mapping.so: path-mapping.c 9 | gcc $(CFLAGS) -shared -fPIC path-mapping.c -o $@ -ldl 10 | 11 | path-mapping-debug.so: path-mapping.c 12 | gcc $(CFLAGS) -DDEBUG -shared -fPIC path-mapping.c -o $@ -ldl 13 | 14 | path-mapping-quiet.so: path-mapping.c 15 | gcc $(CFLAGS) -DQUIET -shared -fPIC path-mapping.c -o $@ -ldl 16 | 17 | all: path-mapping.so path-mapping-debug.so path-mapping-quiet.so 18 | 19 | clean: 20 | rm -f *.so 21 | rm -rf $(TESTDIR) 22 | 23 | test: all unit_tests testtools 24 | for f in $(UNIT_TESTS); do $(TESTDIR)/$$f; done 25 | TESTDIR="$(TESTDIR)" test/integration-tests.sh 26 | 27 | unit_tests: $(addprefix $(TESTDIR)/, $(UNIT_TESTS)) 28 | 29 | testtools: $(addprefix $(TESTDIR)/, $(TESTTOOLS)) 30 | 31 | $(TESTDIR)/test-%: $(SRCDIR)/test/test-%.c $(SRCDIR)/path-mapping.c 32 | mkdir -p $(TESTDIR) 33 | cd $(TESTDIR); gcc $(CFLAGS) $< "$(SRCDIR)/path-mapping.c" -ldl -o $@ 34 | 35 | $(TESTDIR)/testtool-%: $(SRCDIR)/test/testtool-%.c 36 | mkdir -p $(TESTDIR) 37 | cd $(TESTDIR); gcc $(CFLAGS) $^ -o $@ 38 | 39 | .PHONY: all libs clean test unit_tests testtools 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ld-preload-open / path-mapping.so 2 | This library can trick a process into opening absolute paths from a different location, similar to bind mounts, but without root access. 3 | The main difference that an affected process can (apparently) see and access files *inside* the virtual mapped directory, but cannot see the virtual directory *itself*. 4 | Also, in contrast to mounts, every process on a system can have its own mapping (disregarding mount namespaces in current kernels, which basically also require root access). 5 | 6 | > **WARNING:** This library is kind of a hack. 7 | > 8 | > It works quite well, mostly, but unforseen side effects or crashes are totally possible (see **Potential problems** below). 9 | > 10 | > **_DO NOT USE for mission critical software!_** 11 | 12 | ## Example 1 13 | 14 | One example are ["Environment Modules"](https://modules.readthedocs.io/en/latest/) which can be loaded with the command `module load` to activate some software in the current shell. If you have multiple versions of a software, which should be available side by side, you can use this library to trick them into loading version specific assets from a common absolute path. 15 | 16 | Imagine you are an admin and would like to provide both version 2019 and version 2021 of `someprogram` to your users. 17 | However, `someprogram` loads a file from a hard-coded path `/usr/share/someprogram/assets`, but different versions of the program ship with different versions of the file `/usr/share/someprogram/assets`. 18 | 19 | By creating a wrapper script which runs the original program with `path-mapping.so`, you can force each version to load its own version, without altering the executable binary: 20 | 21 | ```bash 22 | #!/bin/bash 23 | # in file /modules/someprogram/v2019/wrapper/someprogram 24 | PATH_MAPPING="/usr/share/someprogram:/modules/someprogram/v2019/share/someprogram" \ 25 | LD_PRELOAD=/path/to/path-mapping.so \ 26 | /modules/someprogram/v2019/bin/someprogram 27 | ``` 28 | 29 | Then just add `/modules/someprogram/v2019/wrapper` to `PATH` in your module definition file for version 2019 of `someprogram` and your users will be able to load it with `module load someprogram/2019` and run the wrapper script as `someprogram`. 30 | 31 | ## Example 2 32 | 33 | Another example use-case might be to run a program with hard-coded paths from your home folder. 34 | Imagine, `someprogram` tries to load files from `/usr/share/someprogram`, with no way to configure that path (apart from re-compiling, *if* you have the source code at all). 35 | If you can't put the files there (for whatever reason), you could place them in `$HOME/.local/share/someprogram` instead. 36 | 37 | With the following command, when the program tries to open e.g. `/usr/share/someprogram/icons.png` (which does not exist), 38 | `path-mapping.so` would intercept that file access and rewrite the path to `$HOME/.local/share/someprogram/icons.png`, which does exist: 39 | ```bash 40 | PATH_MAPPING="/usr/share/someprogram:$HOME/.local/share/someprogram" \ 41 | LD_PRELOAD=/path/to/path-mapping.so \ 42 | someprogram 43 | ``` 44 | 45 | ## How it works 46 | 47 | The path mapping works by intercepting standard library functions which have a path argument, like `open` or `chmod`. 48 | If this argument matches the given prefix (the first part) of the mapping, the prefix is replaced by the destination (the second part) of the mapping. 49 | Then the original standard library function is called with this possibly modified path. 50 | 51 | Most Linux `libc` functions that do something with files are supported (except those that I forgot). 52 | The actual list of functions is quite long and can be looked up in the code. 53 | However, even if all functions with a `path` argument are overloaded, there are some pitfalls. 54 | See below under **Potential problems** for more information. 55 | 56 | ## Path mapping configuration 57 | 58 | There are two ways to specify the path mappings. An arbitrary number of mappings can be used at once. 59 | 60 | 1. If the environment variable `PATH_MAPPING` exists, path-mapping.so will try to initialize the mappins from there. 61 | The first part of each pair is the prefix, and the second part is the destination, so the number of given paths must be even. 62 | All parts are separated by colons: 63 | ```bash 64 | export PATH_MAPPING="/usr/virtual1:/map/dest1:/usr/virtual2:/map/dest2" 65 | ``` 66 | 67 | 2. If `PATH_MAPPING` is unset or empty, the mapping specified in the variable `default_path_map` will be used instead. 68 | You can modify it if you don't want to set `PATH_MAPPING`, for example like this: 69 | ```C 70 | static const char *default_path_map[][2] = { 71 | { "/usr/virtual1", "/map/dest1" }, 72 | { "/usr/virtual1", "/map/dest2" }, 73 | }; 74 | ``` 75 | 76 | ## Compiling and installation 77 | 78 | Just run `make all` to compile the different versions of the library: 79 | * `path-mapping-quiet.so` is compiled with `#define QUIET`. 80 | It will not print anything, except in case of a fatal configuration error, before stopping the process. 81 | * `path-mapping.so` will print out a diagnostig string to stderr when a path is mapped to a new destination. 82 | * `path-mapping-debug.so` is compiled with `#define DEBUG`. 83 | It will will additionally print out one line for each function call to any overridden function. 84 | This is very slow and noisy. Only use it to determine which paths need overriding. 85 | 86 | Choose one of those files and place it anywhere convenient. 87 | Note its absolute path and provide it to the target program as `LD_PRELOAD`, for example: 88 | 89 | ```bash 90 | cd $HOME/repos/ 91 | git clone https://github.com/fritzw/ld-preload-open.git 92 | cd ld-preload-open 93 | make all 94 | make test 95 | export PATH_MAPPING=/somewhere:/$HOME 96 | LD_PRELOAD=$HOME/repos/ld-preload-open/path-mapping.so /bin/ls /somewhere 97 | # This should print something like the following: 98 | # PATH_MAPPING[0]: /somewhere => /home/you 99 | # Mapped Path: __xstat('/somewhere') => '/home/you' 100 | # Mapped Path: opendir('/somewhere') => '/home/you' 101 | # ... followed by the contents of your home directory. 102 | ``` 103 | 104 | 105 | ## Compile time options 106 | 107 | There some options which can be set during compile time by defining some preprocessor macros. 108 | For example, adding `#define QUIET` or compiling with `-DQUIET` will remove all informational printf commands. 109 | 110 | Normally, the loaded path mapping will be printed to `stderr` at startup, and an info message will be printed to `stderr` each time a path mapping is applied. 111 | 112 | * `QUIET`: Removes all `info_fprintf` commands. 113 | The resulting `path-mapping.so` will not print anything, except for initialization errors which will `exit()` the program immediately. 114 | * `DEBUG`: Enables all `debug_fprintf` calls. 115 | This will print additional output to `stderr` each time an overloaded functions called, including the path argument(s). 116 | * `DISABLE_*`: These options allow you to disable the overloading of some specific functions if you desire. 117 | See the code in `path-mapping.c` for a complete list. 118 | 119 | ## Tests 120 | 121 | Run `make test` to execute the included test suite. 122 | Most things should be tested, but multiple variants of the same function are usually not tested separately. 123 | 124 | ## Potential problems 125 | 126 | On first glance, this library might look like it can be used as a replacement for `mount --bind`. 127 | However, since this is quite a hacky solution that runs only in user space, there are some issues where things do not work quite as one would expect. 128 | Some of these could be fixed or worked around, but in some cases that would require significantly more work than just overloading a few functions. 129 | 130 | 1. Only absolute paths are currently mapped, relative paths are not. 131 | If a program does `open("/usr/virtual1/file")`, it will be mapped to `/map/dest1/file`, but `chdir("/usr")` followed by `open("virtual1/file")` will fail with `ENOENT`. 132 | 133 | The same problem applies to functions ending in `at`, like `openat`. 134 | These functions have a parameter `int dirfd`, relative to which the `path` argument is searched (if it is not an absolute path). 135 | This library makes no attempt to find out the path of the directory which `dirfd` represents. 136 | 2. Return values from standard library functions are not mapped. 137 | For example, `getcwd()` will return `/map/dest1` after a calling `chdir("/usr/virtual1")` (from the example above). 138 | 139 | However, this is usually not be a problem, because the program can then internally use that existing path for all future accesses, which will succeed as expected. 140 | Even an interactive `bash` session can work (to a certain extent) inside virtual mapped directories. 141 | 3. Virtual mapped entries do not appear in directory listings. 142 | The example mapping for `/usr/virtual1` will not show up in `ls /usr` or `find /usr`. 143 | 4. Symlinks that point into virtual directories will not work, because symlinks are evaluated by the kernel, not in user space. 144 | For example, the following will fail: 145 | ```bash 146 | export PATH_MAPPING=/tmp/virtual:/tmp/real 147 | export LD_PRELOAD=/path/to/path-mapping.so 148 | mkdir /tmp/real 149 | touch /tmp/real/file 150 | ln -s /tmp/virtual /tmp/link 151 | ls -l /tmp/virtual/file # works 152 | ls -l /tmp/link/file # fails because kernel can not see `/tmp/virtual` 153 | ``` 154 | 5. Creating relative symlinks that cross a mapping boundary will not work as expected: 155 | ```bash 156 | export PATH_MAPPING=/tmp/1/virtual:/tmp/real 157 | export LD_PRELOAD=/path/to/path-mapping.so 158 | mkdir /tmp/real 159 | echo content >/tmp/realfile 160 | ln -s ../../realfile /tmp/1/virtual/virtuallink 161 | cat /tmp/1/virtual/virtuallink # fails because /realfile does not exist 162 | ``` 163 | The created link *would* point to `/tmp/realfile`, if `/tmp/1/virtual/` was a real directory. 164 | But since the symlink is evaluated relative to `/tmp/real`, it will actually point to `/realfile`, which does not exist. 165 | 6. If a programs manually loads a function like `fopen` from `libc.so` using `ldopen` and `dlsym`, then `LD_PRELOAD` can not intercept that. 166 | In this case, the path mapping will not work. 167 | 7. If a standard library function internally calls an overloaded function like `stat` or `open`, then `LD_PRELOAD` can not intercept that. 168 | 8. If internal workings of the libc change in the future, a program might just stop working. 169 | 9. Path mapping does not work if a program talks to the kernel directly using syscalls (which would be *very* bad practice) instead of using the `libc` functions to make the syscalls for it. 170 | Or if a program uses a different standard library, which does syscalls directly instead of falling back to the standard `libc` functions (not sure if something like that exists in practice). 171 | 172 | ## License 173 | 174 | This repository is released unter the MIT license, see the file LICENSE for details. 175 | -------------------------------------------------------------------------------- /path-mapping.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Fritz Webering 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #define _GNU_SOURCE 26 | 27 | #include 28 | #include 29 | #include // exit 30 | #include // dlsym 31 | #include // stat 32 | #include // DIR* 33 | #include // va_start, va_arg 34 | #include // statfs 35 | #include // statvfs 36 | #include // uid_t, gid_t 37 | #include // for execl 38 | #include // utimebuf 39 | #include // struct timeval 40 | #include // dev_t 41 | #include // ftw 42 | #include // fts 43 | #include 44 | 45 | //#define DEBUG 46 | //#define QUIET 47 | 48 | #ifdef QUIET 49 | #define info_fprintf(...) // remove all info_fprintf including args 50 | #else 51 | #define info_fprintf fprintf // compile info_fprintf as fprintf 52 | #endif 53 | 54 | #ifdef DEBUG 55 | #define debug_fprintf fprintf // compile debug_fprintf as fprintf 56 | #else 57 | #define debug_fprintf(...) // remove all debug_fprintf including args 58 | #endif 59 | 60 | #define error_fprintf fprintf // always print errors 61 | 62 | // Enable or disable specific overrides (always includes different variants and the 64 version if applicable) 63 | // #define DISABLE_OPEN 64 | // #define DISABLE_OPENAT 65 | // #define DISABLE_FOPEN 66 | // #define DISABLE_CHDIR 67 | // #define DISABLE_STAT 68 | // #define DISABLE_FSTATAT 69 | // #define DISABLE_STATFS 70 | // #define DISABLE_XSTAT 71 | // #define DISABLE_ACCESS 72 | // #define DISABLE_XATTR 73 | // #define DISABLE_OPENDIR 74 | // #define DISABLE_MKDIR 75 | // #define DISABLE_FTW 76 | // #define DISABLE_FTS 77 | // #define DISABLE_PATHCONF 78 | // #define DISABLE_REALPATH 79 | // #define DISABLE_READLINK 80 | // #define DISABLE_SYMLINK 81 | // #define DISABLE_MKFIFO 82 | // #define DISABLE_MKNOD 83 | // #define DISABLE_UTIME 84 | // #define DISABLE_CHMOD 85 | // #define DISABLE_CHOWN 86 | // #define DISABLE_UNLINK 87 | // #define DISABLE_EXEC 88 | // #define DISABLE_RENAME 89 | // #define DISABLE_LINK 90 | 91 | // List of path pairs. Paths beginning with the first item will be 92 | // translated by replacing the matching part with the second item. 93 | static const char *default_path_map[][2] = { 94 | { "/tmp/path-mapping/tests/virtual", "/tmp/path-mapping/tests/real" }, 95 | }; 96 | 97 | static const char *(*path_map)[2] = default_path_map; 98 | static int path_map_length = (sizeof default_path_map) / (sizeof default_path_map[0]); 99 | static char *path_map_buffer = NULL; 100 | 101 | 102 | ////////////////////////////////////////////////////////// 103 | // Constructor to inspect the PATH_MAPPING env variable // 104 | ////////////////////////////////////////////////////////// 105 | 106 | 107 | __attribute__((constructor)) 108 | static void path_mapping_init() 109 | { 110 | if (path_map != default_path_map) return; 111 | 112 | // If environment variable is set and non-empty, override the default 113 | const char *env_string = getenv("PATH_MAPPING"); 114 | if (env_string != NULL && strlen(env_string) > 0) { 115 | 116 | // Allocate a buffer to store the entries of the map in one big block, separated by null bytes 117 | size_t buffersize = strlen(env_string) + 1; 118 | path_map_buffer = malloc(buffersize); 119 | if (path_map_buffer == NULL) { 120 | error_fprintf(stderr, "PATH_MAPPING out of memory\n"); 121 | exit(255); 122 | } 123 | strncpy(path_map_buffer, env_string, buffersize); 124 | path_map_buffer[buffersize - 1] = '\0'; 125 | 126 | // Count the number of separators ':' to determine the size of the array of pointers 127 | int n_segments = 1; 128 | for (int i = 0; env_string[i]; i++) { 129 | if (env_string[i] == ':') n_segments++; 130 | } 131 | if (n_segments % 2 != 0) { 132 | error_fprintf(stderr, "PATH_MAPPING must have an even number of parts, not %d\n", n_segments); 133 | exit(255); 134 | } 135 | 136 | // Allocate memory for the actual array of pointers to the map entries 137 | path_map = malloc(n_segments * 2 * sizeof(char*)); 138 | if (path_map == NULL) { 139 | error_fprintf(stderr, "PATH_MAPPING out of memory\n"); 140 | exit(255); 141 | } 142 | 143 | // Split the large string buffer into smaller strings by replacing ':' with null bytes 144 | char **linear_path_map = (char **)path_map; 145 | int linear_index = 0; 146 | linear_path_map[linear_index++] = path_map_buffer; 147 | for (int i = 0; path_map_buffer[i]; i++) { 148 | if (path_map_buffer[i] == ':') { 149 | path_map_buffer[i] = '\0'; 150 | linear_path_map[linear_index++] = &(path_map_buffer[i+1]); 151 | } 152 | } 153 | path_map_length = linear_index / 2; 154 | assert(linear_index == n_segments); 155 | } 156 | 157 | for (int i = 0; i < path_map_length; i++) { 158 | info_fprintf(stderr, "PATH_MAPPING[%d]: %s => %s\n", i, path_map[i][0], path_map[i][1]); 159 | } 160 | } 161 | 162 | __attribute__((destructor)) 163 | static void path_mapping_deinit() 164 | { 165 | if (path_map != default_path_map) { 166 | free(path_map); 167 | } 168 | free(path_map_buffer); 169 | } 170 | 171 | 172 | ///////////////////////////////////////////////////////// 173 | // Helper functions to do the actual path mapping // 174 | ///////////////////////////////////////////////////////// 175 | 176 | 177 | #ifndef MAX_PATH 178 | #define MAX_PATH 4096 179 | #endif 180 | 181 | // Returns strlen(path) without trailing slashes 182 | size_t pathlen(const char *path) 183 | { 184 | size_t path_length = strlen(path); 185 | while (path_length > 0 && path[path_length - 1] == '/') { 186 | // If the prefix ends with a slash ("/example/dir/"), ignore the slash. 187 | // Otherwise it would not match the dir itself ("/examle/dir"), e.g. in opendir(). 188 | path_length -= 1; 189 | } 190 | return path_length; 191 | } 192 | 193 | // Returns true if the first path components of path match those of prefix (whole word matches only) 194 | int path_prefix_matches(const char *prefix, const char *path) 195 | { 196 | size_t prefix_len = pathlen(prefix); 197 | if (strncmp(prefix, path, prefix_len) == 0) { 198 | // The prefix matches, but "/example/dir" would also match "/example/dirty/file" 199 | // Thus we only return true if a slash or end-of-string follows the match. 200 | char char_after_match = path[prefix_len]; 201 | return char_after_match == '/' || char_after_match == '\0'; 202 | } 203 | return 0; 204 | } 205 | 206 | // Check if path matches any defined prefix, and if so, replace it with its substitution 207 | static const char *fix_path(const char *function_name, const char *path, char *new_path, size_t new_path_size) 208 | { 209 | if (path == NULL) return path; 210 | 211 | for (int i = 0; i < path_map_length; i++) { 212 | const char *prefix = path_map[i][0]; 213 | if (path_prefix_matches(prefix, path)) { 214 | const char *replace = path_map[i][1]; 215 | size_t prefix_length = pathlen(prefix); 216 | size_t new_length = strlen(path) + pathlen(replace) - prefix_length; 217 | if (new_length > new_path_size - 1) { 218 | error_fprintf(stderr, "ERROR fix_path: Path too long: %s(%s)", function_name, path); 219 | return path; 220 | } 221 | const char *rest = path + prefix_length; 222 | strcpy(new_path, replace); 223 | strcat(new_path, rest); 224 | info_fprintf(stderr, "Mapped Path: %s('%s') => '%s'\n", function_name, path, new_path); 225 | return new_path; 226 | } 227 | } 228 | return path; 229 | } 230 | 231 | 232 | ///////////////////////////////////////////////////////// 233 | // Macro definitions for generating function overrides // 234 | ///////////////////////////////////////////////////////// 235 | 236 | 237 | // Hint for debugging these macros: 238 | // Remove the #define __NL__, then compile with gcc -save-temps. 239 | // Then open path-mapping.i with a text editor and replace __NL__ with newlines. 240 | #define __NL__ 241 | 242 | // Select argument name i from the variable argument list (ignoring types) 243 | #define OVERRIDE_ARG(i, ...) OVERRIDE_ARG_##i(__VA_ARGS__) 244 | #define OVERRIDE_ARG_1(type1, arg1, ...) arg1 245 | #define OVERRIDE_ARG_2(type1, arg1, type2, arg2, ...) arg2 246 | #define OVERRIDE_ARG_3(type1, arg1, type2, arg2, type3, arg3, ...) arg3 247 | #define OVERRIDE_ARG_4(type1, arg1, type2, arg2, type3, arg3, type4, arg4, ...) arg4 248 | #define OVERRIDE_ARG_5(type1, arg1, type2, arg2, type3, arg3, type4, arg4, arg5, ...) arg5 249 | 250 | // Create the function pointer typedef for the function 251 | #define OVERRIDE_TYPEDEF_NAME(funcname) orig_##funcname##_func_type 252 | #define OVERRIDE_TYPEDEF(has_varargs, nargs, returntype, funcname, ...) typedef returntype (*OVERRIDE_TYPEDEF_NAME(funcname))(OVERRIDE_ARGS(has_varargs, nargs, __VA_ARGS__)); 253 | 254 | // Create a valid C argument list including types 255 | #define OVERRIDE_ARGS(has_varargs, nargs, ...) OVERRIDE_ARGS_##nargs(has_varargs, __VA_ARGS__) 256 | #define OVERRIDE_ARGS_1(has_varargs, type1, arg1) type1 arg1 OVERRIDE_VARARGS(has_varargs) 257 | #define OVERRIDE_ARGS_2(has_varargs, type1, arg1, type2, arg2) type1 arg1, type2 arg2 OVERRIDE_VARARGS(has_varargs) 258 | #define OVERRIDE_ARGS_3(has_varargs, type1, arg1, type2, arg2, type3, arg3) type1 arg1, type2 arg2, type3 arg3 OVERRIDE_VARARGS(has_varargs) 259 | #define OVERRIDE_ARGS_4(has_varargs, type1, arg1, type2, arg2, type3, arg3, type4, arg4) type1 arg1, type2 arg2, type3 arg3, type4 arg4 OVERRIDE_VARARGS(has_varargs) 260 | #define OVERRIDE_ARGS_5(has_varargs, type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5) type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5 OVERRIDE_VARARGS(has_varargs) 261 | // Print ", ..." in the argument list if has_varargs is 1 262 | #define OVERRIDE_VARARGS(has_varargs) OVERRIDE_VARARGS_##has_varargs 263 | #define OVERRIDE_VARARGS_0 264 | #define OVERRIDE_VARARGS_1 , ... 265 | 266 | // Create an argument list without types where one argument is replaced with new_path 267 | #define OVERRIDE_RETURN_ARGS(nargs, path_arg_pos, ...) OVERRIDE_RETURN_ARGS_##nargs##_##path_arg_pos(__VA_ARGS__) 268 | #define OVERRIDE_RETURN_ARGS_1_1(type1, arg1) new_path 269 | #define OVERRIDE_RETURN_ARGS_2_1(type1, arg1, type2, arg2) new_path, arg2 270 | #define OVERRIDE_RETURN_ARGS_2_2(type1, arg1, type2, arg2) arg1, new_path 271 | #define OVERRIDE_RETURN_ARGS_3_1(type1, arg1, type2, arg2, type3, arg3) new_path, arg2, arg3 272 | #define OVERRIDE_RETURN_ARGS_3_2(type1, arg1, type2, arg2, type3, arg3) arg1, new_path, arg3 273 | #define OVERRIDE_RETURN_ARGS_3_3(type1, arg1, type2, arg2, type3, arg3) arg1, arg2, new_path 274 | #define OVERRIDE_RETURN_ARGS_4_1(type1, arg1, type2, arg2, type3, arg3, type4, arg4) new_path, arg2, arg3, arg4 275 | #define OVERRIDE_RETURN_ARGS_4_2(type1, arg1, type2, arg2, type3, arg3, type4, arg4) arg1, new_path, arg3, arg4 276 | #define OVERRIDE_RETURN_ARGS_4_3(type1, arg1, type2, arg2, type3, arg3, type4, arg4) arg1, arg2, new_path, arg4 277 | #define OVERRIDE_RETURN_ARGS_4_4(type1, arg1, type2, arg2, type3, arg3, type4, arg4) arg1, arg2, arg3, new_path 278 | #define OVERRIDE_RETURN_ARGS_5_1(type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5) new_path, arg2, arg3, arg4, arg5 279 | #define OVERRIDE_RETURN_ARGS_5_2(type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5) arg1, new_path, arg3, arg4, arg5 280 | #define OVERRIDE_RETURN_ARGS_5_3(type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5) arg1, arg2, new_path, arg4, arg5 281 | #define OVERRIDE_RETURN_ARGS_5_4(type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5) arg1, arg2, arg3, new_path, arg5 282 | #define OVERRIDE_RETURN_ARGS_5_5(type1, arg1, type2, arg2, type3, arg3, type4, arg4, type5, arg5) arg1, arg2, arg3, arg4, new_path 283 | 284 | // Use this to override a function without varargs 285 | #define OVERRIDE_FUNCTION(nargs, path_arg_pos, returntype, funcname, ...) \ 286 | OVERRIDE_FUNCTION_MODE_GENERIC(0, nargs, path_arg_pos, returntype, funcname, __VA_ARGS__) 287 | 288 | // Use this to override a function with a vararg mode that works like open() or openat() 289 | #define OVERRIDE_FUNCTION_VARARGS(nargs, path_arg_pos, returntype, funcname, ...) \ 290 | OVERRIDE_FUNCTION_MODE_GENERIC(1, nargs, path_arg_pos, returntype, funcname, __VA_ARGS__) 291 | 292 | #define OVERRIDE_FUNCTION_MODE_GENERIC(has_varargs, nargs, path_arg_pos, returntype, funcname, ...) \ 293 | OVERRIDE_TYPEDEF(has_varargs, nargs, returntype, funcname, __VA_ARGS__) \ 294 | __NL__ returntype funcname (OVERRIDE_ARGS(has_varargs, nargs, __VA_ARGS__))\ 295 | __NL__{\ 296 | __NL__ debug_fprintf(stderr, #funcname "(%s) called\n", OVERRIDE_ARG(path_arg_pos, __VA_ARGS__));\ 297 | __NL__ char buffer[MAX_PATH];\ 298 | __NL__ const char *new_path = fix_path(#funcname, OVERRIDE_ARG(path_arg_pos, __VA_ARGS__), buffer, sizeof buffer);\ 299 | __NL__ \ 300 | __NL__ static OVERRIDE_TYPEDEF_NAME(funcname) orig_func = NULL;\ 301 | __NL__ if (orig_func == NULL) {\ 302 | __NL__ orig_func = (OVERRIDE_TYPEDEF_NAME(funcname))dlsym(RTLD_NEXT, #funcname);\ 303 | __NL__ }\ 304 | __NL__ OVERRIDE_DO_MODE_VARARG(has_varargs, nargs, path_arg_pos, __VA_ARGS__) \ 305 | __NL__ return orig_func(OVERRIDE_RETURN_ARGS(nargs, path_arg_pos, __VA_ARGS__));\ 306 | __NL__} 307 | 308 | // Conditionally expands to the code used to handle the mode argument of open() and openat() 309 | #define OVERRIDE_DO_MODE_VARARG(has_mode_vararg, nargs, path_arg_pos, ...) \ 310 | OVERRIDE_DO_MODE_VARARG_##has_mode_vararg(nargs, path_arg_pos, __VA_ARGS__) 311 | #define OVERRIDE_DO_MODE_VARARG_0(nargs, path_arg_pos, ...) // Do nothing 312 | #define OVERRIDE_DO_MODE_VARARG_1(nargs, path_arg_pos, ...) \ 313 | __NL__ if (__OPEN_NEEDS_MODE(flags)) {\ 314 | __NL__ va_list args;\ 315 | __NL__ va_start(args, flags);\ 316 | __NL__ int mode = va_arg(args, int);\ 317 | __NL__ va_end(args);\ 318 | __NL__ return orig_func(OVERRIDE_RETURN_ARGS(nargs, path_arg_pos, __VA_ARGS__), mode);\ 319 | __NL__ } 320 | 321 | 322 | ///////////////////////////////////////////////////////// 323 | // Definition of all function overrides below // 324 | ///////////////////////////////////////////////////////// 325 | 326 | 327 | #ifndef DISABLE_OPEN 328 | OVERRIDE_FUNCTION_VARARGS(2, 1, int, open, const char *, pathname, int, flags) 329 | OVERRIDE_FUNCTION_VARARGS(2, 1, int, open64, const char *, pathname, int, flags) 330 | #endif // DISABLE_OPEN 331 | 332 | 333 | #ifndef DISABLE_OPENAT 334 | OVERRIDE_FUNCTION_VARARGS(3, 2, int, openat, int, dirfd, const char *, pathname, int, flags) 335 | OVERRIDE_FUNCTION_VARARGS(3, 2, int, openat64, int, dirfd, const char *, pathname, int, flags) 336 | #endif // DISABLE_OPENAT 337 | 338 | 339 | #ifndef DISABLE_FOPEN 340 | OVERRIDE_FUNCTION(2, 1, FILE*, fopen, const char *, filename, const char *, mode) 341 | OVERRIDE_FUNCTION(2, 1, FILE*, fopen64, const char *, filename, const char *, mode) 342 | OVERRIDE_FUNCTION(3, 1, FILE*, freopen, const char *, filename, const char *, mode, FILE *, stream) 343 | #endif // DISABLE_FOPEN 344 | 345 | 346 | #ifndef DISABLE_CHDIR 347 | OVERRIDE_FUNCTION(1, 1, int, chdir, const char *, path) 348 | #endif // DISABLE_CHDIR 349 | 350 | 351 | #ifndef DISABLE_STAT 352 | OVERRIDE_FUNCTION(2, 1, int, stat, const char *, path, struct stat *, buf) 353 | OVERRIDE_FUNCTION(2, 1, int, lstat, const char *, path, struct stat *, buf) 354 | #endif // DISABLE_STAT 355 | 356 | 357 | #ifndef DISABLE_XSTAT 358 | OVERRIDE_FUNCTION(3, 2, int, __xstat, int, ver, const char *, path, struct stat *, stat_buf) 359 | OVERRIDE_FUNCTION(3, 2, int, __lxstat, int, ver, const char *, path, struct stat *, stat_buf) 360 | OVERRIDE_FUNCTION(3, 2, int, __xstat64, int, ver, const char *, path, struct stat64 *, stat_buf) 361 | OVERRIDE_FUNCTION(3, 2, int, __lxstat64, int, ver, const char *, path, struct stat64 *, stat_buf) 362 | #endif // DISABLE_XSTAT 363 | 364 | 365 | #ifndef DISABLE_FSTATAT 366 | OVERRIDE_FUNCTION(4, 2, int, fstatat, int, dirfd, const char *, pathname, struct stat *, statbuf, int, flags) 367 | OVERRIDE_FUNCTION(4, 2, int, fstatat64, int, dirfd, const char *, pathname, struct stat64 *, statbuf, int, flags) 368 | OVERRIDE_FUNCTION(5, 3, int, __fxstatat, int, ver, int, dirfd, const char *, pathname, struct stat *, statbuf, int, flags) 369 | OVERRIDE_FUNCTION(5, 3, int, __fxstatat64, int, ver, int, dirfd, const char *, pathname, struct stat64 *, statbuf, int, flags) 370 | #endif // DISABLE_FSTATAT 371 | 372 | 373 | #ifndef DISABLE_STATFS 374 | OVERRIDE_FUNCTION(2, 1, int, statfs, const char *, path, struct statfs *, buf) 375 | OVERRIDE_FUNCTION(2, 1, int, statvfs, const char *, path, struct statvfs *, buf) 376 | OVERRIDE_FUNCTION(2, 1, int, statfs64, const char *, path, struct statfs64 *, buf) 377 | OVERRIDE_FUNCTION(2, 1, int, statvfs64, const char *, path, struct statvfs64 *, buf) 378 | #endif // DISABLE_STATFS 379 | 380 | 381 | #ifndef DISABLE_ACCESS 382 | OVERRIDE_FUNCTION(2, 1, int, access, const char *, pathname, int, mode) 383 | OVERRIDE_FUNCTION(4, 2, int, faccessat, int, dirfd, const char *, pathname, int, mode, int, flags) 384 | #endif // DISABLE_ACCESS 385 | 386 | 387 | #ifndef DISABLE_XATTR 388 | OVERRIDE_FUNCTION(4, 1, ssize_t, getxattr, const char *, path, const char *, name, void *, value, size_t, size) 389 | OVERRIDE_FUNCTION(4, 1, ssize_t, lgetxattr, const char *, path, const char *, name, void *, value, size_t, size) 390 | #endif // DISABLE_XATTR 391 | 392 | 393 | #ifndef DISABLE_OPENDIR 394 | OVERRIDE_FUNCTION(1, 1, DIR *, opendir, const char *, name) 395 | #endif // DISABLE_OPENDIR 396 | 397 | 398 | #ifndef DISABLE_MKDIR 399 | OVERRIDE_FUNCTION(2, 1, int, mkdir, const char *, pathname, mode_t, mode) 400 | #endif // DISABLE_MKDIR 401 | 402 | 403 | #ifndef DISABLE_FTW 404 | OVERRIDE_FUNCTION(3, 1, int, ftw, const char *, filename, __ftw_func_t, func, int, descriptors) 405 | OVERRIDE_FUNCTION(4, 1, int, nftw, const char *, filename, __nftw_func_t, func, int, descriptors, int, flags) 406 | OVERRIDE_FUNCTION(3, 1, int, ftw64, const char *, filename, __ftw64_func_t, func, int, descriptors) 407 | OVERRIDE_FUNCTION(4, 1, int, nftw64, const char *, filename, __nftw64_func_t, func, int, descriptors, int, flags) 408 | #endif // DISABLE_FTW 409 | 410 | 411 | #ifndef DISABLE_FTS 412 | typedef int (*fts_compare_func_t)(const FTSENT **, const FTSENT **); 413 | typedef FTS* (*orig_fts_open_func_type)(char * const *path_argv, int options, fts_compare_func_t compare); 414 | FTS *fts_open(char * const *path_argv, int options, fts_compare_func_t compare) 415 | { 416 | if (path_argv[0] == NULL) return NULL; 417 | debug_fprintf(stderr, "fts_open(%s) called\n", path_argv[0]); 418 | 419 | FTS *result = NULL; 420 | int argc = 0; 421 | const char **new_paths; 422 | char **buffers; 423 | 424 | for (argc = 0; path_argv[argc] != NULL; argc++) {} // count number of paths in argument array 425 | 426 | buffers = malloc((argc + 1) * sizeof(char *)); 427 | if (buffers == NULL) { 428 | goto _fts_open_return; 429 | } 430 | for (int i = 0; i < argc; i++) { buffers[i] = NULL; } // Initialize for free() in case of failure 431 | 432 | new_paths = malloc((argc + 1) * sizeof(char *)); 433 | if (new_paths == NULL) { 434 | goto _fts_open_cleanup_buffers; 435 | } 436 | for (int i = 0; i < argc; i++) { 437 | buffers[i] = malloc(MAX_PATH + 1); 438 | if (buffers[i] == NULL) { 439 | goto _fts_open_cleanup; 440 | } 441 | new_paths[i] = fix_path("fts_open", path_argv[i], buffers[i], MAX_PATH); 442 | } 443 | new_paths[argc] = NULL; // terminating null pointer 444 | 445 | static orig_fts_open_func_type orig_func = NULL; 446 | if (orig_func == NULL) { 447 | orig_func = (orig_fts_open_func_type)dlsym(RTLD_NEXT, "fts_open"); 448 | } 449 | 450 | result = orig_func((char * const *)new_paths, options, compare); 451 | 452 | _fts_open_cleanup: 453 | for (int i = 0; i < argc; i++) { 454 | free(buffers[i]); 455 | } 456 | free(new_paths); 457 | _fts_open_cleanup_buffers: 458 | free(buffers); 459 | _fts_open_return: 460 | return result; 461 | } 462 | #endif // DISABLE_FTS 463 | 464 | 465 | #ifndef DISABLE_PATHCONF 466 | OVERRIDE_FUNCTION(2, 1, long, pathconf, const char *, path, int, name) 467 | #endif // DISABLE_PATHCONF 468 | 469 | 470 | #ifndef DISABLE_REALPATH 471 | OVERRIDE_FUNCTION(2, 1, char *, realpath, const char *, path, char *, resolved_path) 472 | OVERRIDE_FUNCTION(1, 1, char *, canonicalize_file_name, const char *, path) 473 | #endif // DISABLE_REALPATH 474 | 475 | 476 | #ifndef DISABLE_READLINK 477 | OVERRIDE_FUNCTION(3, 1, ssize_t, readlink, const char *, pathname, char *, buf, size_t, bufsiz) 478 | OVERRIDE_FUNCTION(4, 2, ssize_t, readlinkat, int, dirfd, const char *, pathname, char *, buf, size_t, bufsiz) 479 | #endif // DISABLE_READLINK 480 | 481 | 482 | #ifndef DISABLE_SYMLINK 483 | OVERRIDE_FUNCTION(2, 2, int, symlink, const char *, target, const char *, linkpath) 484 | OVERRIDE_FUNCTION(3, 3, int, symlinkat, const char *, target, int, newdirfd, const char *, linkpath) 485 | #endif // DISABLE_SYMLINK 486 | 487 | 488 | #ifndef DISABLE_MKFIFO 489 | OVERRIDE_FUNCTION(2, 1, int, mkfifo, const char *, filename, mode_t, mode) 490 | #endif // DISABLE_MKFIFO 491 | 492 | 493 | #ifndef DISABLE_MKNOD 494 | OVERRIDE_FUNCTION(3, 1, int, mknod, const char *, filename, mode_t, mode, dev_t, dev) 495 | #endif // DISABLE_MKNOD 496 | 497 | 498 | #ifndef DISABLE_UTIME 499 | OVERRIDE_FUNCTION(2, 1, int, utime, const char *, filename, const struct utimbuf *, times) 500 | OVERRIDE_FUNCTION(2, 1, int, utimes, const char *, filename, const struct timeval *, tvp) 501 | OVERRIDE_FUNCTION(2, 1, int, lutime, const char *, filename, const struct utimbuf *, tvp) 502 | OVERRIDE_FUNCTION(4, 2, int, utimensat, int, dirfd, const char *, pathname, const struct timespec *, times, int, flags) 503 | OVERRIDE_FUNCTION(3, 2, int, futimesat, int, dirfd, const char *, pathname, const struct timeval *, times) 504 | #endif // DISABLE_UTIME 505 | 506 | 507 | #ifndef DISABLE_CHMOD 508 | OVERRIDE_FUNCTION(2, 1, int, chmod, const char *, pathname, mode_t, mode) 509 | OVERRIDE_FUNCTION(4, 2, int, fchmodat, int, dirfd, const char *, pathname, mode_t, mode, int, flags) 510 | #endif // DISABLE_CHMOD 511 | 512 | 513 | #ifndef DISABLE_CHOWN 514 | OVERRIDE_FUNCTION(3, 1, int, chown, const char *, pathname, uid_t, owner, gid_t, group) 515 | OVERRIDE_FUNCTION(3, 1, int, lchown, const char *, pathname, uid_t, owner, gid_t, group) 516 | OVERRIDE_FUNCTION(5, 2, int, fchownat, int, dirfd, const char *, pathname, uid_t, owner, gid_t, group, int, flags) 517 | #endif // DISABLE_CHOWN 518 | 519 | 520 | #ifndef DISABLE_UNLINK 521 | OVERRIDE_FUNCTION(1, 1, int, unlink, const char *, pathname) 522 | OVERRIDE_FUNCTION(3, 2, int, unlinkat, int, dirfd, const char *, pathname, int, flags) 523 | OVERRIDE_FUNCTION(1, 1, int, rmdir, const char *, pathname) 524 | OVERRIDE_FUNCTION(1, 1, int, remove, const char *, pathname) 525 | #endif // DISABLE_UNLINK 526 | 527 | 528 | #ifndef DISABLE_EXEC 529 | OVERRIDE_FUNCTION(2, 1, int, execv, const char *, filename, char * const*, argv) 530 | OVERRIDE_FUNCTION(3, 1, int, execve, const char *, filename, char * const*, argv, char * const*, env) 531 | OVERRIDE_FUNCTION(2, 1, int, execvp, const char *, filename, char * const*, argv) 532 | 533 | int execl(const char *filename, const char *arg0, ...) 534 | { 535 | debug_fprintf(stderr, "execl(%s) called\n", filename); 536 | 537 | char buffer[MAX_PATH]; 538 | const char *new_path = fix_path("execl", filename, buffer, sizeof buffer); 539 | 540 | // Note: call execv, not execl, because we can't call varargs functions with an unknown number of args 541 | static orig_execv_func_type execv_func = NULL; 542 | if (execv_func == NULL) { 543 | execv_func = (orig_execv_func_type)dlsym(RTLD_NEXT, "execv"); 544 | } 545 | 546 | // count args 547 | int argc = 1; 548 | va_list args_list; 549 | va_start(args_list, arg0); 550 | while (va_arg(args_list, char *) != NULL) argc += 1; 551 | va_end(args_list); 552 | 553 | // extract args 554 | const char **argv_buffer = malloc(sizeof(char *) * (argc + 1)); 555 | va_start(args_list, arg0); 556 | argv_buffer[0] = arg0; 557 | argc = 1; 558 | char *arg = NULL; 559 | while ((arg = va_arg(args_list, char *)) != NULL) { 560 | argv_buffer[argc++] = arg; 561 | } 562 | va_end(args_list); 563 | argv_buffer[argc] = NULL; 564 | 565 | int result = execv_func(new_path, (char * const*)argv_buffer); 566 | free(argv_buffer); // We ONLY reach this if exec fails, so we need to clean up 567 | return result; 568 | } 569 | 570 | int execlp(const char *filename, const char *arg0, ...) 571 | { 572 | debug_fprintf(stderr, "execlp(%s) called\n", filename); 573 | 574 | char buffer[MAX_PATH]; 575 | const char *new_path = fix_path("execlp", filename, buffer, sizeof buffer); 576 | 577 | // Note: call execvp, not execlp, because we can't call varargs functions with an unknown number of args 578 | static orig_execvp_func_type execvp_func = NULL; 579 | if (execvp_func == NULL) { 580 | execvp_func = (orig_execvp_func_type)dlsym(RTLD_NEXT, "execvp"); 581 | } 582 | 583 | // count args 584 | int argc = 1; 585 | va_list args_list; 586 | va_start(args_list, arg0); 587 | while (va_arg(args_list, char *) != NULL) argc += 1; 588 | va_end(args_list); 589 | 590 | // extract args 591 | const char **argv_buffer = malloc(sizeof(char *) * (argc + 1)); 592 | va_start(args_list, arg0); 593 | argv_buffer[0] = arg0; 594 | argc = 1; 595 | char *arg = NULL; 596 | while ((arg = va_arg(args_list, char *)) != NULL) { 597 | argv_buffer[argc++] = arg; 598 | } 599 | va_end(args_list); 600 | argv_buffer[argc] = NULL; 601 | 602 | int result = execvp_func(new_path, (char * const*)argv_buffer); 603 | free(argv_buffer); // We ONLY reach this if exec fails, so we need to clean up 604 | return result; 605 | } 606 | 607 | int execle(const char *filename, const char *arg0, ... /* , char *const env[] */) 608 | { 609 | debug_fprintf(stderr, "execl(%s) called\n", filename); 610 | 611 | char buffer[MAX_PATH]; 612 | const char *new_path = fix_path("execle", filename, buffer, sizeof buffer); 613 | 614 | // Note: call execve, not execle, because we can't call varargs functions with an unknown number of args 615 | static orig_execve_func_type execve_func = NULL; 616 | if (execve_func == NULL) { 617 | execve_func = (orig_execve_func_type)dlsym(RTLD_NEXT, "execve"); 618 | } 619 | 620 | // count args 621 | int argc = 1; 622 | va_list args_list; 623 | va_start(args_list, arg0); 624 | while (va_arg(args_list, char *) != NULL) argc += 1; 625 | va_end(args_list); 626 | 627 | // extract args 628 | const char **argv_buffer = malloc(sizeof(char *) * (argc + 1)); 629 | va_start(args_list, arg0); 630 | argv_buffer[0] = arg0; 631 | argc = 1; 632 | char *arg = NULL; 633 | while ((arg = va_arg(args_list, char *)) != NULL) { 634 | argv_buffer[argc++] = arg; 635 | } 636 | char * const* env = va_arg(args_list, char * const*); 637 | va_end(args_list); 638 | argv_buffer[argc] = NULL; 639 | 640 | int result = execve_func(new_path, (char * const*)argv_buffer, env); 641 | free(argv_buffer); // We ONLY reach this if exec fails, so we need to clean up 642 | return result; 643 | } 644 | #endif // DISABLE_EXEC 645 | 646 | 647 | #ifndef DISABLE_RENAME 648 | typedef int (*orig_rename_func_type)(const char *oldpath, const char *newpath); 649 | int rename(const char *oldpath, const char *newpath) 650 | { 651 | debug_fprintf(stderr, "rename(%s, %s) called\n", oldpath, newpath); 652 | 653 | char buffer[MAX_PATH], buffer2[MAX_PATH]; 654 | const char *new_oldpath = fix_path("rename-old", oldpath, buffer, sizeof buffer); 655 | const char *new_newpath = fix_path("rename-new", newpath, buffer2, sizeof buffer2); 656 | 657 | static orig_rename_func_type orig_func = NULL; 658 | if (orig_func == NULL) { 659 | orig_func = (orig_rename_func_type)dlsym(RTLD_NEXT, "rename"); 660 | } 661 | 662 | return orig_func(new_oldpath, new_newpath); 663 | } 664 | 665 | typedef int (*orig_renameat_func_type)(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); 666 | int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) 667 | { 668 | debug_fprintf(stderr, "renameat(%s, %s) called\n", oldpath, newpath); 669 | 670 | char buffer[MAX_PATH], buffer2[MAX_PATH]; 671 | const char *new_oldpath = fix_path("renameat-old", oldpath, buffer, sizeof buffer); 672 | const char *new_newpath = fix_path("renameat-new", newpath, buffer2, sizeof buffer2); 673 | 674 | static orig_renameat_func_type orig_func = NULL; 675 | if (orig_func == NULL) { 676 | orig_func = (orig_renameat_func_type)dlsym(RTLD_NEXT, "renameat"); 677 | } 678 | 679 | return orig_func(olddirfd, new_oldpath, newdirfd, new_newpath); 680 | } 681 | 682 | typedef int (*orig_renameat2_func_type)(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags); 683 | int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags) 684 | { 685 | debug_fprintf(stderr, "renameat2(%s, %s) called\n", oldpath, newpath); 686 | 687 | char buffer[MAX_PATH], buffer2[MAX_PATH]; 688 | const char *new_oldpath = fix_path("renameat2-old", oldpath, buffer, sizeof buffer); 689 | const char *new_newpath = fix_path("renameat2-new", newpath, buffer2, sizeof buffer2); 690 | 691 | static orig_renameat2_func_type orig_func = NULL; 692 | if (orig_func == NULL) { 693 | orig_func = (orig_renameat2_func_type)dlsym(RTLD_NEXT, "renameat2"); 694 | } 695 | 696 | return orig_func(olddirfd, new_oldpath, newdirfd, new_newpath, flags); 697 | } 698 | #endif // DISABLE_RENAME 699 | 700 | 701 | #ifndef DISABLE_LINK 702 | typedef int (*orig_link_func_type)(const char *oldpath, const char *newpath); 703 | int link(const char *oldpath, const char *newpath) 704 | { 705 | debug_fprintf(stderr, "link(%s, %s) called\n", oldpath, newpath); 706 | 707 | char buffer[MAX_PATH], buffer2[MAX_PATH]; 708 | const char *new_oldpath = fix_path("link-old", oldpath, buffer, sizeof buffer); 709 | const char *new_newpath = fix_path("link-new", newpath, buffer2, sizeof buffer2); 710 | 711 | static orig_link_func_type orig_func = NULL; 712 | if (orig_func == NULL) { 713 | orig_func = (orig_link_func_type)dlsym(RTLD_NEXT, "link"); 714 | } 715 | 716 | return orig_func(new_oldpath, new_newpath); 717 | } 718 | 719 | typedef int (*orig_linkat_func_type)(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags); 720 | int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) 721 | { 722 | debug_fprintf(stderr, "linkat(%s, %s) called\n", oldpath, newpath); 723 | 724 | char buffer[MAX_PATH], buffer2[MAX_PATH]; 725 | const char *new_oldpath = fix_path("linkat-old", oldpath, buffer, sizeof buffer); 726 | const char *new_newpath = fix_path("linkat-new", newpath, buffer2, sizeof buffer2); 727 | 728 | static orig_linkat_func_type orig_func = NULL; 729 | if (orig_func == NULL) { 730 | orig_func = (orig_linkat_func_type)dlsym(RTLD_NEXT, "linkat"); 731 | } 732 | 733 | return orig_func(olddirfd, new_oldpath, newdirfd, new_newpath, flags); 734 | } 735 | 736 | #endif // DISABLE_LINK 737 | -------------------------------------------------------------------------------- /test/integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o functrace 4 | set -o errtrace # trap ERR in functions 5 | set -o errexit 6 | set -o nounset 7 | 8 | lib="$PWD/path-mapping.so" 9 | testdir="${TESTDIR:-/tmp/path-mapping}" 10 | 11 | export PATH_MAPPING="$testdir/virtual:$testdir/real" 12 | 13 | failure() { 14 | local lineno="$1" 15 | local msg="$2" 16 | local test_case="${FUNCNAME[1]}" 17 | echo 18 | echo "Failed $test_case in line $lineno at command:" 19 | echo "$msg" 20 | echo 21 | if [[ -f "out/$test_case.err" ]]; then 22 | echo "stderr:" 23 | cat "out/$test_case.err" 24 | else 25 | echo "stderr is empty" 26 | fi 27 | echo 28 | } 29 | trap 'failure "${LINENO}" "${BASH_COMMAND}"' ERR 30 | 31 | setup() { 32 | rm -rf "$testdir/real" # clean up previous test case if present 33 | mkdir -p "$testdir/real" 34 | cd "$testdir/real" 35 | mkdir -p dir1/dir2 36 | echo content0 >file0 37 | echo content1 >dir1/file1 38 | echo content2 >dir1/dir2/file2 39 | echo content3 >dir1/dir2/file3 40 | cd "$testdir" 41 | } 42 | 43 | check_strace_file() { 44 | test_name="${FUNCNAME[1]}" 45 | if [[ $# == 2 ]]; then 46 | test_name="$1"; shift 47 | fi 48 | strace_file="$testdir/strace/$test_name" 49 | lines="$( grep virtual "$strace_file" | grep -vE '^execve|^write|^Mapped Path:|PATH_MAPPING: ' || true )" 50 | if [[ "$lines" ]] ; then 51 | echo "Unmapped path in $strace_file:" 52 | echo "$lines" 53 | return 1 54 | fi 55 | } 56 | 57 | check_output_file() { 58 | test_name="${FUNCNAME[1]}" 59 | if [[ $# == 2 ]]; then 60 | test_name="$1"; shift 61 | fi 62 | expected="$1" 63 | out_file="$testdir/out/$test_name" 64 | output="$(cat "$out_file")" 65 | if ! [[ "$output" == "$expected" ]]; then 66 | echo "ERROR: output was not as expected:" 67 | echo "'$output' != '$expected'" 68 | return 1 69 | fi 70 | } 71 | 72 | assert_readlink() { 73 | create_link_path="$1" 74 | link_content="$2" 75 | readlink_path="$3" 76 | expected="$4" 77 | ln -sf "$link_content" "$create_link_path" 78 | result="$(LD_PRELOAD="$lib" readlink -f "$readlink_path" 2>/dev/null)" 79 | if ! [[ "$result" == "$expected" ]]; then 80 | echo "assert_readlink $@:" 81 | echo "'$result' != '$expected'" 82 | return 1 83 | fi 84 | } 85 | test_readlink() { 86 | setup 87 | assert_readlink "$testdir/real/link" "$testdir/real/target" "$testdir/real/link" "$testdir/real/target" # no mapping 88 | assert_readlink "$testdir/real/link" "$testdir/real/target" "$testdir/virtual/link" "$testdir/real/target" # link name mapped 89 | assert_readlink "$testdir/real/link" "$testdir/virtual/target" "$testdir/virtual/link" "$testdir/virtual/target" # link contents not mapped 90 | } 91 | 92 | test_readlink_f_relative() { 93 | setup 94 | ln -s "dir2/file2" "$testdir/real/dir1/relativelink" 95 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 96 | readlink -f "$testdir/virtual/dir1/relativelink" \ 97 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 98 | check_strace_file 99 | check_output_file "$testdir/virtual/dir1/dir2/file2" 100 | test x"$(cat "$testdir/real/dir1/relativelink")" == xcontent2 101 | } 102 | 103 | test_readlink_f_real() { 104 | setup 105 | ln -s "$testdir/real/dir1/dir2/file2" "$testdir/real/dir1/reallink" 106 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 107 | readlink -f "$testdir/virtual/dir1/reallink" \ 108 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 109 | check_strace_file 110 | check_output_file "$testdir/real/dir1/dir2/file2" 111 | } 112 | 113 | test_readlink_f_virtual() { 114 | setup 115 | LD_PRELOAD="$lib" ln -s "$testdir/virtual/dir1/dir2/file2" "$testdir/virtual/dir1/virtlink" 2>/dev/null 116 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 117 | readlink -f "$testdir/virtual/dir1/virtlink" \ 118 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 119 | #check_strace_file # False positive because link contains the word "virtual" 120 | check_output_file "$testdir/virtual/dir1/dir2/file2" 121 | } 122 | 123 | test_ln() { 124 | setup 125 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 126 | ln -s "linkcontent" "$testdir/virtual/dir1/link" \ 127 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 128 | readlink "$testdir/real/dir1/link" >out/${FUNCNAME[0]} 129 | check_strace_file 130 | check_output_file "linkcontent" 131 | } 132 | 133 | disabled_test_thunar() { # Disabled because slow and not really useful 134 | if ! which Thunar >/dev/null; then 135 | echo "Thunar not found, skipping test case" 136 | return 137 | fi 138 | if pgrep Thunar; then 139 | echo "Thunar is running. Execute Thunar -q if you want to run it." 140 | return 141 | fi 142 | setup 143 | cd real 144 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 145 | Thunar "$testdir/virtual" \ 146 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err & 147 | sleep 3; kill %1 148 | check_strace_file Thunar 149 | } 150 | 151 | test_cat() { 152 | setup 153 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 154 | cat "$testdir/virtual/file0" \ 155 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 156 | check_output_file "content0" 157 | check_strace_file 158 | } 159 | 160 | test_find() { 161 | setup 162 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 163 | find "$testdir/virtual" \ 164 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 165 | check_strace_file 166 | check_output_file "$testdir/virtual 167 | $testdir/virtual/file0 168 | $testdir/virtual/dir1 169 | $testdir/virtual/dir1/file1 170 | $testdir/virtual/dir1/dir2 171 | $testdir/virtual/dir1/dir2/file3 172 | $testdir/virtual/dir1/dir2/file2" 173 | } 174 | 175 | test_grep() { 176 | setup 177 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 178 | grep -R content "$testdir/virtual" \ 179 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 180 | check_strace_file 181 | check_output_file "$testdir/virtual/file0:content0 182 | $testdir/virtual/dir1/file1:content1 183 | $testdir/virtual/dir1/dir2/file3:content3 184 | $testdir/virtual/dir1/dir2/file2:content2" 185 | } 186 | 187 | test_chmod() { 188 | setup 189 | chmod 700 "$testdir/real/file0" 190 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 191 | chmod 777 "$testdir/virtual/file0" \ 192 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 193 | check_strace_file 194 | test "$(stat -c %a "$testdir/real/file0")" == 777 195 | } 196 | 197 | test_utime() { 198 | setup 199 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 200 | ./testtool-utime "$testdir/virtual/dir1/file1" \ 201 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 202 | chmod 700 real/dir1/file1 203 | stat -c %X:%Y "real/dir1/file1" >out/${FUNCNAME[0]} 204 | check_strace_file 205 | check_output_file '200000000:100000000' 206 | } 207 | 208 | test_rm() { 209 | setup 210 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 211 | rm -r "$testdir/virtual/dir1" \ 212 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 213 | check_strace_file 214 | test '!' -e "$testdir/real/dir1" 215 | } 216 | 217 | test_rename() { 218 | setup 219 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 220 | /usr/bin/mv "$testdir/virtual/dir1" "$testdir/virtual/dir1_renamed" \ 221 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 222 | check_strace_file 223 | test '!' -e "$testdir/real/dir1" 224 | test -e "$testdir/real/dir1_renamed" 225 | } 226 | 227 | test_bash_exec() { 228 | setup 229 | cp /usr/bin/echo "$testdir/real/dir1/" 230 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 231 | bash -c "'$testdir/virtual/dir1/echo' arg1 arg2 arg3 arg4 arg5" \ 232 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 233 | check_strace_file 234 | check_output_file "arg1 arg2 arg3 arg4 arg5" 235 | } 236 | 237 | test_bash_cd() { # Test chdir() 238 | setup 239 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 240 | bash -c "cd virtual; ls; cd dir1; ls" \ 241 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 242 | check_strace_file 243 | check_output_file $'dir1\nfile0\ndir2\nfile1' 244 | } 245 | 246 | test_execl_0() { 247 | setup 248 | cp ./testtool-execl ./testtool-printenv real/ 249 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 250 | "$testdir/virtual/testtool-execl" execl "$testdir/virtual/testtool-printenv" 0 \ 251 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 252 | check_strace_file 253 | check_output_file $'TEST0=value0' 254 | } 255 | 256 | test_execl_1() { 257 | setup 258 | cp ./testtool-execl ./testtool-printenv real/ 259 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 260 | "$testdir/virtual/testtool-execl" execl "$testdir/virtual/testtool-printenv" 1 \ 261 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 262 | check_strace_file 263 | check_output_file $'arg1\nTEST0=value0' 264 | } 265 | 266 | test_execlp_2() { 267 | setup 268 | cp ./testtool-execl ./testtool-printenv real/ 269 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 270 | "$testdir/virtual/testtool-execl" execlp "$testdir/virtual/testtool-printenv" 2 \ 271 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 272 | check_strace_file 273 | check_output_file $'arg1\narg2\nTEST0=value0' 274 | } 275 | 276 | test_execle_3() { 277 | setup 278 | cp ./testtool-execl ./testtool-printenv real/ 279 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 280 | "$testdir/virtual/testtool-execl" execle "$testdir/virtual/testtool-printenv" 3 \ 281 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 282 | check_strace_file 283 | check_output_file $'arg1\narg2\narg3\nTEST1=value1\nTEST2=value2' 284 | 285 | } 286 | 287 | test_du() { 288 | setup 289 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 290 | du "$testdir/virtual/" \ 291 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 292 | check_strace_file 293 | check_output_file "8 $testdir/virtual/dir1/dir2 294 | 12 $testdir/virtual/dir1 295 | 16 $testdir/virtual/" 296 | } 297 | 298 | test_df() { # Tests realpath() 299 | setup 300 | expected="$(df --output="source,fstype,itotal,size,target" "$testdir/real/")" 301 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 302 | df --output="source,fstype,itotal,size,target" "$testdir/virtual/" \ 303 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 304 | check_strace_file 305 | check_output_file "$expected" 306 | } 307 | 308 | test_getfacl() { # Tests getxattr() 309 | setup 310 | expected="$(getfacl "$testdir/real/" 2>/dev/null | sed 's/real/virtual/')" 311 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 312 | getfacl "$testdir/virtual/" \ 313 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 314 | check_strace_file 315 | check_output_file "$expected" 316 | } 317 | 318 | test_mkfifo() { 319 | setup 320 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 321 | mkfifo "$testdir/virtual/dir1/fifo" \ 322 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 323 | stat -c %F real/dir1/fifo >out/${FUNCNAME[0]} 324 | check_strace_file 325 | check_output_file "fifo" 326 | } 327 | 328 | test_mkdir() { 329 | setup 330 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 331 | mkdir "$testdir/virtual/dir1/newdir" \ 332 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 333 | stat -c %F real/dir1/newdir >out/${FUNCNAME[0]} 334 | check_strace_file 335 | check_output_file "directory" 336 | } 337 | 338 | test_ftw() { 339 | setup 340 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 341 | ./testtool-ftw "$testdir/virtual/dir1" \ 342 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 343 | check_strace_file 344 | check_output_file "$testdir/real/dir1 345 | $testdir/real/dir1/file1 346 | $testdir/real/dir1/dir2 347 | $testdir/real/dir1/dir2/file3 348 | $testdir/real/dir1/dir2/file2" 349 | } 350 | 351 | test_nftw() { 352 | setup 353 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 354 | ./testtool-nftw "$testdir/virtual/dir1" \ 355 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 356 | check_strace_file 357 | check_output_file "$testdir/real/dir1 358 | $testdir/real/dir1/file1 359 | $testdir/real/dir1/dir2 360 | $testdir/real/dir1/dir2/file3 361 | $testdir/real/dir1/dir2/file2" 362 | } 363 | 364 | test_fts() { 365 | setup 366 | mkdir -p real/dir1/dir4 367 | echo content4 > real/dir1/dir4/file4 368 | LD_PRELOAD="$lib" strace -o "strace/${FUNCNAME[0]}" \ 369 | ./testtool-fts "$testdir/virtual/dir1/dir2" "$testdir/virtual/dir1/dir4" \ 370 | >out/${FUNCNAME[0]} 2>out/${FUNCNAME[0]}.err 371 | check_strace_file 372 | check_output_file $'dir2\nfile2\nfile3\ndir2\ndir4\nfile4\ndir4' 373 | } 374 | 375 | # Setup up output directories for the test cases 376 | mkdir -p "$testdir/out" 377 | mkdir -p "$testdir/strace" 378 | 379 | # Find all declared functions starting with "test_" in a random order 380 | all_testcases="$(declare -F | cut -d " " -f 3- | grep test)" 381 | enabled_testcases="$(declare -F | cut -d " " -f 3- | grep '^test_' | shuf)" 382 | num_testcases="$(echo "$enabled_testcases" | wc -l)" 383 | 384 | N=0 385 | if [[ $# -gt 0 ]]; then 386 | if [[ $1 == '-l' ]] || [[ $1 == "--list" ]]; then 387 | echo "All test cases (including disabled ones):" 388 | echo "$all_testcases" 389 | exit 0 390 | fi 391 | while [[ $# -gt 0 ]]; do 392 | if [[ "$all_testcases" =~ "$1" ]]; then 393 | echo "$1" 394 | $1 395 | shift 396 | else 397 | echo "Unknown test case $1" 398 | exit 1 399 | N=$[N+1] 400 | fi 401 | done 402 | else 403 | # If no argument is given, execute all test cases 404 | for cmd in $enabled_testcases; do 405 | echo "$cmd" 406 | $cmd 407 | N=$[N+1] 408 | done 409 | fi 410 | 411 | echo "$N/$num_testcases TESTS PASSED!" 412 | #rm -rf "$testdir" # use make clean to clean up instead 413 | -------------------------------------------------------------------------------- /test/test-pathmatching.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int path_prefix_matches(const char *path, const char *prefix); 4 | 5 | void test_path_prefix_matches() { 6 | assert(path_prefix_matches("/example/dir/", "/example/dir/") != 0); 7 | assert(path_prefix_matches("/example/dir/", "/example/dir") != 0); 8 | assert(path_prefix_matches("/example/dir", "/example/dir") != 0); 9 | assert(path_prefix_matches("/example/dir", "/example/dir/") != 0); 10 | 11 | assert(path_prefix_matches("/example/dir", "/example/dirt") == 0); 12 | assert(path_prefix_matches("/example/dir", "/example/dirty") == 0); 13 | assert(path_prefix_matches("/example/dir", "/example/dirty/") == 0); 14 | assert(path_prefix_matches("/example/dir", "/example/dirty/file") == 0); 15 | 16 | assert(path_prefix_matches("/", "/") != 0); 17 | assert(path_prefix_matches("/", "/e") != 0); 18 | assert(path_prefix_matches("/", "/example") != 0); 19 | assert(path_prefix_matches("/e", "/e") != 0); 20 | assert(path_prefix_matches("/e", "/e") != 0); 21 | 22 | assert(path_prefix_matches("/e", "/example") == 0); 23 | } 24 | 25 | int main() { 26 | test_path_prefix_matches(); 27 | return 0; 28 | } -------------------------------------------------------------------------------- /test/testtool-execl.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L // setenv 2 | #include // clearenv 3 | #include // execl 4 | #include // printf 5 | #include // strcmp 6 | 7 | 8 | extern char **environ; 9 | 10 | int main(int argc, char **argv) { 11 | if (argc != 4) { 12 | printf("Usage: $0 [calltype] [executable path] [nargs 0...3]\n"); 13 | return 1; 14 | } 15 | const char *calltype = argv[1]; 16 | const char *executable = argv[2]; 17 | const char nargs = argv[3][0]; 18 | 19 | environ[0] = NULL; 20 | setenv("TEST0", "value0", 1); 21 | 22 | if (strcmp(calltype, "execl") == 0) { 23 | switch (nargs) { 24 | case '0': execl(executable, executable, NULL); // no break because exec replaces the current process 25 | case '1': execl(executable, executable, "arg1", NULL); 26 | case '2': execl(executable, executable, "arg1", "arg2", NULL); 27 | case '3': execl(executable, executable, "arg1", "arg2", "arg3", NULL); 28 | } 29 | } 30 | if (strcmp(calltype, "execlp") == 0) { 31 | switch (nargs) { 32 | case '0': execlp(executable, executable, NULL); // no break because exec replaces the current process 33 | case '1': execlp(executable, executable, "arg1", NULL); 34 | case '2': execlp(executable, executable, "arg1", "arg2", NULL); 35 | case '3': execlp(executable, executable, "arg1", "arg2", "arg3", NULL); 36 | } 37 | } 38 | if (strcmp(calltype, "execle") == 0) { 39 | const char *env[] = { 40 | "TEST1=value1", 41 | "TEST2=value2", 42 | NULL, 43 | }; 44 | switch (nargs) { 45 | case '0': execle(executable, executable, NULL, env); // no break because exec replaces the current process 46 | case '1': execle(executable, executable, "arg1", NULL, env); 47 | case '2': execle(executable, executable, "arg1", "arg2", NULL, env); 48 | case '3': execle(executable, executable, "arg1", "arg2", "arg3", NULL, env); 49 | } 50 | } 51 | return 0; 52 | } -------------------------------------------------------------------------------- /test/testtool-fts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int compare(const FTSENT** left, const FTSENT** right) 6 | { 7 | return strcmp((*left)->fts_name, (*right)->fts_name); 8 | } 9 | 10 | int main(int argc, char* const argv[]) 11 | { 12 | if (argc < 2) return 1; 13 | 14 | FTS* ftsp = NULL; 15 | FTSENT *node = NULL; 16 | 17 | ftsp = fts_open(argv + 1, 0, &compare); 18 | 19 | if (ftsp != NULL) { 20 | while ( (node = fts_read(ftsp)) != NULL ) { 21 | printf("%s\n", node->fts_name); 22 | } 23 | fts_close(ftsp); 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /test/testtool-ftw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static int print_filename(const char* filename, const struct stat *stats, int flags) 5 | { 6 | printf("%s\n", filename); 7 | return 0; 8 | } 9 | 10 | int main(int argc, const char **argv) 11 | { 12 | if (argc != 2) return 1; 13 | return ftw(argv[1], print_filename, 20); 14 | } -------------------------------------------------------------------------------- /test/testtool-nftw.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 500 2 | #include 3 | #include 4 | 5 | static int print_filename(const char* filename, const struct stat *stats, int flags, struct FTW *ftwbuf) 6 | { 7 | printf("%s\n", filename); 8 | return 0; 9 | } 10 | 11 | int main(int argc, const char **argv) 12 | { 13 | if (argc != 2) return 1; 14 | return nftw(argv[1], print_filename, 20, 0); 15 | } -------------------------------------------------------------------------------- /test/testtool-printenv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern char **environ; 5 | 6 | int main(int argc, char **argv) { 7 | for (int i = 1; i < argc; i++) { 8 | printf("%s\n", argv[i]); 9 | } 10 | 11 | for (char **env = environ; *env != NULL; env++) 12 | { 13 | char *thisEnv = *env; 14 | printf("%s\n", thisEnv); 15 | } 16 | return 0; 17 | } -------------------------------------------------------------------------------- /test/testtool-utime.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_SOURCE 2 | #include 3 | 4 | int main(int argc, const char **argv) 5 | { 6 | if (argc < 2) { 7 | return 1; 8 | } 9 | const char *filename = argv[1]; 10 | struct utimbuf ubuf; 11 | 12 | ubuf.modtime = 100000000; 13 | ubuf.actime = 200000000; 14 | return utime(filename, &ubuf); 15 | } 16 | --------------------------------------------------------------------------------