From 0986c53a7069b5aaad9d4c1c1d52e0c5561b5502 Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Wed, 13 Sep 2017 15:26:32 -0400 Subject: --- underlying.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 underlying.c 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 +#include +#include +#include + +#include +#include +#include + +// 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 \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 -- cgit v1.2.3