4645f98185
`info->symlink_target` is a `std::string`. This `strdup` therefore leaks memory. Just use `std::string::operator=(const char *)` instead. Bug: 206470603 Test: TreeHugger Change-Id: I71bcaec583c7d429b4d09522cc5eeb2ced009007
426 lines
12 KiB
C++
426 lines
12 KiB
C++
// 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
|
|
// <workspace root>/output/path /real/path
|
|
// we will create directories
|
|
// RUNFILES/<workspace root>
|
|
// RUNFILES/<workspace root>/output
|
|
// a symlink
|
|
// RUNFILES/<workspace root>/output/path -> /real/path
|
|
// and the output manifest will contain a line
|
|
// <workspace root>/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
|
|
// <workspace root>. No output path may be equal to another. No output path may
|
|
// be a path prefix of another.
|
|
|
|
#define _FILE_OFFSET_BITS 64
|
|
|
|
#include <dirent.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <map>
|
|
#include <string>
|
|
|
|
// 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<std::string, FileInfo> 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;
|
|
}
|