summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Benjamin Barenblat <benjamin@barenblat.name>2017-09-13 15:26:32 -0400
committerGravatar GitHub <noreply@github.com>2017-09-13 15:26:32 -0400
commit0986c53a7069b5aaad9d4c1c1d52e0c5561b5502 (patch)
tree2a3690f63adb2f9afcfd58d3bd2e3b11c336023e
-rw-r--r--underlying.c103
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