// Copyright 2014 The Bazel Authors. All rights reserved. // // 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. // // This program creates a "runfiles tree" from a "runfiles manifest". // // The command line arguments are an input manifest INPUT and an output // directory RUNFILES. First, the files in the RUNFILES directory are scanned // and any extraneous ones are removed. Second, any missing files are created. // Finally, a copy of the input manifest is written to RUNFILES/MANIFEST. // // The input manifest consists of lines, each containing a relative path within // the runfiles, a space, and an optional absolute path. If this second path // is present, a symlink is created pointing to it; otherwise an empty file is // created. // // Given the line // /output/path /real/path // we will create directories // RUNFILES/ // RUNFILES//output // a symlink // RUNFILES//output/path -> /real/path // and the output manifest will contain a line // /output/path /real/path // // If --use_metadata is supplied, every other line is treated as opaque // metadata, and is ignored here. // // All output paths must be relative and generally (but not always) begin with // . No output path may be equal to another. No output path may // be a path prefix of another. #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include // program_invocation_short_name is not portable. static const char *argv0; const char *input_filename; const char *output_base_dir; enum FileType { FILE_TYPE_REGULAR, FILE_TYPE_DIRECTORY, FILE_TYPE_SYMLINK }; struct FileInfo { FileType type; std::string symlink_target; bool operator==(const FileInfo &other) const { return type == other.type && symlink_target == other.symlink_target; } bool operator!=(const FileInfo &other) const { return !(*this == other); } }; typedef std::map FileInfoMap; class RunfilesCreator { public: explicit RunfilesCreator(const std::string &output_base) : output_base_(output_base), output_filename_("MANIFEST"), temp_filename_(output_filename_ + ".tmp") { SetupOutputBase(); if (chdir(output_base_.c_str()) != 0) { err(2, "chdir '%s'", output_base_.c_str()); } } void ReadManifest(const std::string &manifest_file, bool allow_relative, bool use_metadata) { FILE *outfile = fopen(temp_filename_.c_str(), "w"); if (!outfile) { err(2, "opening '%s/%s' for writing", output_base_.c_str(), temp_filename_.c_str()); } FILE *infile = fopen(manifest_file.c_str(), "r"); if (!infile) { err(2, "opening '%s' for reading", manifest_file.c_str()); } // read input manifest int lineno = 0; char buf[3 * PATH_MAX]; while (fgets(buf, sizeof buf, infile)) { // copy line to output manifest if (fputs(buf, outfile) == EOF) { err(2, "writing to '%s/%s'", output_base_.c_str(), temp_filename_.c_str()); } // parse line ++lineno; // Skip metadata lines. They are used solely for // dependency checking. if (use_metadata && lineno % 2 == 0) continue; char *tok = strtok(buf, " \n"); if (tok == nullptr) { continue; } else if (*tok == '/') { errx(2, "%s:%d: paths must not be absolute", input_filename, lineno); } std::string link(tok); const char *target = strtok(nullptr, " \n"); if (target == nullptr) { target = ""; } else if (strtok(nullptr, " \n") != nullptr) { errx(2, "%s:%d: link or target filename contains space", input_filename, lineno); } else if (!allow_relative && target[0] != '/') { errx(2, "%s:%d: expected absolute path", input_filename, lineno); } FileInfo *info = &manifest_[link]; if (target[0] == '\0') { // No target means an empty file. info->type = FILE_TYPE_REGULAR; } else { info->type = FILE_TYPE_SYMLINK; info->symlink_target = target; } FileInfo parent_info; parent_info.type = FILE_TYPE_DIRECTORY; while (true) { int k = link.rfind('/'); if (k < 0) break; link.erase(k, std::string::npos); if (!manifest_.insert(std::make_pair(link, parent_info)).second) break; } } if (fclose(outfile) != 0) { err(2, "writing to '%s/%s'", output_base_.c_str(), temp_filename_.c_str()); } fclose(infile); // Don't delete the temp manifest file. manifest_[temp_filename_].type = FILE_TYPE_REGULAR; } void CreateRunfiles() { if (unlink(output_filename_.c_str()) != 0 && errno != ENOENT) { err(2, "removing previous file at '%s/%s'", output_base_.c_str(), output_filename_.c_str()); } ScanTreeAndPrune("."); CreateFiles(); // rename output file into place if (rename(temp_filename_.c_str(), output_filename_.c_str()) != 0) { err(2, "renaming '%s/%s' to '%s/%s'", output_base_.c_str(), temp_filename_.c_str(), output_base_.c_str(), output_filename_.c_str()); } } private: void SetupOutputBase() { struct stat st; if (stat(output_base_.c_str(), &st) != 0) { // Technically, this will cause problems if the user's umask contains // 0200, but we don't care. Anyone who does that deserves what's coming. if (mkdir(output_base_.c_str(), 0777) != 0) { err(2, "creating directory '%s'", output_base_.c_str()); } } else { EnsureDirReadAndWritePerms(output_base_); } } void ScanTreeAndPrune(const std::string &path) { // A note on non-empty files: // We don't distinguish between empty and non-empty files. That is, if // there's a file that has contents, we don't truncate it here, even though // the manifest supports creation of empty files, only. Given that // .runfiles are *supposed* to be immutable, this shouldn't be a problem. EnsureDirReadAndWritePerms(path); struct dirent *entry; DIR *dh = opendir(path.c_str()); if (!dh) { err(2, "opendir '%s'", path.c_str()); } errno = 0; const std::string prefix = (path == "." ? "" : path + "/"); while ((entry = readdir(dh)) != nullptr) { if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; std::string entry_path = prefix + entry->d_name; FileInfo actual_info; actual_info.type = DentryToFileType(entry_path, entry); if (actual_info.type == FILE_TYPE_SYMLINK) { ReadLinkOrDie(entry_path, &actual_info.symlink_target); } FileInfoMap::iterator expected_it = manifest_.find(entry_path); if (expected_it == manifest_.end() || expected_it->second != actual_info) { DelTree(entry_path, actual_info.type); } else { manifest_.erase(expected_it); if (actual_info.type == FILE_TYPE_DIRECTORY) { ScanTreeAndPrune(entry_path); } } errno = 0; } if (errno != 0) { err(2, "reading directory '%s'", path.c_str()); } closedir(dh); } void CreateFiles() { for (FileInfoMap::const_iterator it = manifest_.begin(); it != manifest_.end(); ++it) { const std::string &path = it->first; switch (it->second.type) { case FILE_TYPE_DIRECTORY: if (mkdir(path.c_str(), 0777) != 0) { err(2, "mkdir '%s'", path.c_str()); } break; case FILE_TYPE_REGULAR: { int fd = open(path.c_str(), O_CREAT|O_EXCL|O_WRONLY, 0555); if (fd < 0) { err(2, "creating empty file '%s'", path.c_str()); } close(fd); } break; case FILE_TYPE_SYMLINK: { const std::string& target = it->second.symlink_target; if (symlink(target.c_str(), path.c_str()) != 0) { err(2, "symlinking '%s' -> '%s'", path.c_str(), target.c_str()); } } break; } } } FileType DentryToFileType(const std::string &path, struct dirent *ent) { #ifdef _DIRENT_HAVE_D_TYPE if (ent->d_type != DT_UNKNOWN) { if (ent->d_type == DT_DIR) { return FILE_TYPE_DIRECTORY; } else if (ent->d_type == DT_LNK) { return FILE_TYPE_SYMLINK; } else { return FILE_TYPE_REGULAR; } } else // NOLINT (the brace is in the next line) #endif { struct stat st; LStatOrDie(path, &st); if (S_ISDIR(st.st_mode)) { return FILE_TYPE_DIRECTORY; } else if (S_ISLNK(st.st_mode)) { return FILE_TYPE_SYMLINK; } else { return FILE_TYPE_REGULAR; } } } void LStatOrDie(const std::string &path, struct stat *st) { if (lstat(path.c_str(), st) != 0) { err(2, "lstating file '%s'", path.c_str()); } } void StatOrDie(const std::string &path, struct stat *st) { if (stat(path.c_str(), st) != 0) { err(2, "stating file '%s'", path.c_str()); } } void ReadLinkOrDie(const std::string &path, std::string *output) { char readlink_buffer[PATH_MAX]; int sz = readlink(path.c_str(), readlink_buffer, sizeof(readlink_buffer)); if (sz < 0) { err(2, "reading symlink '%s'", path.c_str()); } // readlink returns a non-null terminated string. std::string(readlink_buffer, sz).swap(*output); } void EnsureDirReadAndWritePerms(const std::string &path) { const int kMode = 0700; struct stat st; LStatOrDie(path, &st); if ((st.st_mode & kMode) != kMode) { int new_mode = st.st_mode | kMode; if (chmod(path.c_str(), new_mode) != 0) { err(2, "chmod '%s'", path.c_str()); } } } bool DelTree(const std::string &path, FileType file_type) { if (file_type != FILE_TYPE_DIRECTORY) { if (unlink(path.c_str()) != 0) { err(2, "unlinking '%s'", path.c_str()); return false; } return true; } EnsureDirReadAndWritePerms(path); struct dirent *entry; DIR *dh = opendir(path.c_str()); if (!dh) { err(2, "opendir '%s'", path.c_str()); } errno = 0; while ((entry = readdir(dh)) != nullptr) { if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; const std::string entry_path = path + '/' + entry->d_name; FileType entry_file_type = DentryToFileType(entry_path, entry); DelTree(entry_path, entry_file_type); errno = 0; } if (errno != 0) { err(2, "readdir '%s'", path.c_str()); } closedir(dh); if (rmdir(path.c_str()) != 0) { err(2, "rmdir '%s'", path.c_str()); } return true; } private: std::string output_base_; std::string output_filename_; std::string temp_filename_; FileInfoMap manifest_; }; int main(int argc, char **argv) { argv0 = argv[0]; argc--; argv++; bool allow_relative = false; bool use_metadata = false; while (argc >= 1) { if (strcmp(argv[0], "--allow_relative") == 0) { allow_relative = true; argc--; argv++; } else if (strcmp(argv[0], "--use_metadata") == 0) { use_metadata = true; argc--; argv++; } else { break; } } if (argc != 2) { fprintf(stderr, "usage: %s " "[--allow_relative] [--use_metadata] " "INPUT RUNFILES\n", argv0); return 1; } input_filename = argv[0]; output_base_dir = argv[1]; std::string manifest_file = input_filename; if (input_filename[0] != '/') { char cwd_buf[PATH_MAX]; if (getcwd(cwd_buf, sizeof(cwd_buf)) == nullptr) { err(2, "getcwd failed"); } manifest_file = std::string(cwd_buf) + '/' + manifest_file; } RunfilesCreator runfiles_creator(output_base_dir); runfiles_creator.ReadManifest(manifest_file, allow_relative, use_metadata); runfiles_creator.CreateRunfiles(); return 0; }