diff options
author | Benjamin Barenblat <benjamin@barenblat.name> | 2017-09-13 15:26:32 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-13 15:26:32 -0400 |
commit | 0986c53a7069b5aaad9d4c1c1d52e0c5561b5502 (patch) | |
tree | 2a3690f63adb2f9afcfd58d3bd2e3b11c336023e /underlying.c |
Diffstat (limited to 'underlying.c')
-rw-r--r-- | underlying.c | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/underlying.c b/underlying.c new file mode 100644 index 0000000..cdaaf56 --- /dev/null +++ b/underlying.c @@ -0,0 +1,103 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define _POSIX_C_SOURCE 200809L + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libgen.h> +#include <limits.h> +#include <unistd.h> + +// Like `strchr`, but if `c` is not found, returns a pointer to the null at the +// end of `s` rather than `NULL` itself. (In glibc, this is `strchrnul`.) +static const char* FindCharacterOrEndOfString(const char* s, int c) { + while (*s != c && *s != '\0') { + s++; + } + return s; +} + +// Splits a colon-separated list (e.g., `PATH`) into head and tail. Places the +// head in `dir` and stores `strlen(dir)` in `dir_len`. Returns a pointer to the +// tail, or a pointer to an empty string if the tail is empty. +static const char* GetNextEntry(const char* const restrict path, + char* const restrict dir, + size_t* const restrict dir_len) { + const char* const end = FindCharacterOrEndOfString(path, ':'); + *dir_len = (size_t)(end - path); + memcpy(dir, path, *dir_len); + dir[*dir_len] = '\0'; + if (*end == '\0') { + // Empty tail. + return end; + } else { + // Skip over the ':'. + return end + 1; + } +} + +int main(const int argc, char* const argv[]) { + if (argc != 2) { + fprintf(stderr, + "usage: underlying <full_path>\n" + "example: exec \"$(underlying \"$(readlink -f \"$0\")\")\"\n"); + return EXIT_FAILURE; + } + + const char* const parent_path = argv[1]; + + // Determine the base name of the caller. basename may modify its argument, so + // give it a copy. + char parent_path2[PATH_MAX] = {0}; + strcpy(parent_path2, parent_path); + const char* const parent_name = basename(parent_path2); + + char underlying_path[PATH_MAX] = {0}; + bool underlying_path_found = false; + + // Look for parent_name in each directory in PATH. Find the first one that's + // below parent_path. + const char* path_environment = getenv("PATH"); + if (path_environment == NULL) { + perror("getenv"); + return EXIT_FAILURE; + } + while (*path_environment != '\0') { + // Generate a candidate path. + char candidate_path[PATH_MAX] = {0}; + size_t candidate_path_len = 0; + path_environment = + GetNextEntry(path_environment, candidate_path, &candidate_path_len); + stpcpy(stpcpy(candidate_path + candidate_path_len, "/"), parent_name); + + if (strcmp(candidate_path, parent_path) == 0) { + // We're looking at ourself. Invalidate any work we've done so far. + underlying_path_found = false; + } else if (!underlying_path_found && access(candidate_path, F_OK) == 0) { + strcpy(underlying_path, candidate_path); + underlying_path_found = true; + } + } + + if (!underlying_path_found) { + return EXIT_FAILURE; + } + + puts(underlying_path); + return EXIT_SUCCESS; +}
\ No newline at end of file |