// 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; }