Index: lld/trunk/COFF/Driver.cpp =================================================================== --- lld/trunk/COFF/Driver.cpp +++ lld/trunk/COFF/Driver.cpp @@ -18,6 +18,7 @@ #include "lld/Common/Args.h" #include "lld/Common/Driver.h" #include "lld/Common/ErrorHandler.h" +#include "lld/Common/Filesystem.h" #include "lld/Common/Memory.h" #include "lld/Common/Threads.h" #include "lld/Common/Timer.h" @@ -1525,6 +1526,12 @@ getOutputPath((*Args.filtered(OPT_INPUT).begin())->getValue()); } + // Fail early if an output file is not writable. + if (auto E = tryCreateFile(Config->OutputFile)) { + error("cannot open output file " + Config->OutputFile + ": " + E.message()); + return; + } + if (ShouldCreatePDB) { // Put the PDB next to the image if no /pdb flag was passed. if (Config->PDBPath.empty()) { Index: lld/trunk/Common/CMakeLists.txt =================================================================== --- lld/trunk/Common/CMakeLists.txt +++ lld/trunk/Common/CMakeLists.txt @@ -30,6 +30,7 @@ add_lld_library(lldCommon Args.cpp ErrorHandler.cpp + Filesystem.cpp Memory.cpp Reproduce.cpp Strings.cpp Index: lld/trunk/Common/Filesystem.cpp =================================================================== --- lld/trunk/Common/Filesystem.cpp +++ lld/trunk/Common/Filesystem.cpp @@ -0,0 +1,99 @@ +//===- Filesystem.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains a few utility functions to handle files. +// +//===----------------------------------------------------------------------===// + +#include "lld/Common/Filesystem.h" +#include "lld/Common/Threads.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/FileOutputBuffer.h" +#include "llvm/Support/FileSystem.h" +#if LLVM_ON_UNIX +#include +#endif +#include + +using namespace llvm; +using namespace lld; + +// Removes a given file asynchronously. This is a performance hack, +// so remove this when operating systems are improved. +// +// On Linux (and probably on other Unix-like systems), unlink(2) is a +// noticeably slow system call. As of 2016, unlink takes 250 +// milliseconds to remove a 1 GB file on ext4 filesystem on my machine. +// +// To create a new result file, we first remove existing file. So, if +// you repeatedly link a 1 GB program in a regular compile-link-debug +// cycle, every cycle wastes 250 milliseconds only to remove a file. +// Since LLD can link a 1 GB binary in about 5 seconds, that waste +// actually counts. +// +// This function spawns a background thread to remove the file. +// The calling thread returns almost immediately. +void lld::unlinkAsync(StringRef Path) { +// Removing a file is async on windows. +#if defined(_WIN32) + sys::fs::remove(Path); +#else + if (!ThreadsEnabled || !sys::fs::exists(Path) || + !sys::fs::is_regular_file(Path)) + return; + + // We cannot just remove path from a different thread because we are now going + // to create path as a new file. + // Instead we open the file and unlink it on this thread. The unlink is fast + // since the open fd guarantees that it is not removing the last reference. + int FD; + std::error_code EC = sys::fs::openFileForRead(Path, FD); + sys::fs::remove(Path); + + if (EC) + return; + + // close and therefore remove TempPath in background. + std::mutex M; + std::condition_variable CV; + bool Started = false; + std::thread([&, FD] { + { + std::lock_guard L(M); + Started = true; + CV.notify_all(); + } + ::close(FD); + }).detach(); + + // GLIBC 2.26 and earlier have race condition that crashes an entire process + // if the main thread calls exit(2) while other thread is starting up. + std::unique_lock L(M); + CV.wait(L, [&] { return Started; }); +#endif +} + +// Simulate file creation to see if Path is writable. +// +// Determining whether a file is writable or not is amazingly hard, +// and after all the only reliable way of doing that is to actually +// create a file. But we don't want to do that in this function +// because LLD shouldn't update any file if it will end in a failure. +// We also don't want to reimplement heuristics to determine if a +// file is writable. So we'll let FileOutputBuffer do the work. +// +// FileOutputBuffer doesn't touch a desitnation file until commit() +// is called. We use that class without calling commit() to predict +// if the given file is writable. +std::error_code lld::tryCreateFile(StringRef Path) { + if (Path.empty()) + return std::error_code(); + if (Path == "-") + return std::error_code(); + return errorToErrorCode(FileOutputBuffer::create(Path, 1).takeError()); +} Index: lld/trunk/ELF/CMakeLists.txt =================================================================== --- lld/trunk/ELF/CMakeLists.txt +++ lld/trunk/ELF/CMakeLists.txt @@ -27,7 +27,6 @@ Driver.cpp DriverUtils.cpp EhFrame.cpp - Filesystem.cpp ICF.cpp InputFiles.cpp InputSection.cpp Index: lld/trunk/ELF/Driver.cpp =================================================================== --- lld/trunk/ELF/Driver.cpp +++ lld/trunk/ELF/Driver.cpp @@ -24,7 +24,6 @@ #include "Driver.h" #include "Config.h" -#include "Filesystem.h" #include "ICF.h" #include "InputFiles.h" #include "InputSection.h" @@ -40,6 +39,7 @@ #include "lld/Common/Args.h" #include "lld/Common/Driver.h" #include "lld/Common/ErrorHandler.h" +#include "lld/Common/Filesystem.h" #include "lld/Common/Memory.h" #include "lld/Common/Strings.h" #include "lld/Common/TargetOptionsCommandFlags.h" Index: lld/trunk/ELF/Filesystem.h =================================================================== --- lld/trunk/ELF/Filesystem.h +++ lld/trunk/ELF/Filesystem.h @@ -1,22 +0,0 @@ -//===- Filesystem.h ---------------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLD_ELF_FILESYSTEM_H -#define LLD_ELF_FILESYSTEM_H - -#include "lld/Common/LLVM.h" -#include - -namespace lld { -namespace elf { -void unlinkAsync(StringRef Path); -std::error_code tryCreateFile(StringRef Path); -} // namespace elf -} // namespace lld - -#endif Index: lld/trunk/ELF/Filesystem.cpp =================================================================== --- lld/trunk/ELF/Filesystem.cpp +++ lld/trunk/ELF/Filesystem.cpp @@ -1,102 +0,0 @@ -//===- Filesystem.cpp -----------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file contains a few utility functions to handle files. -// -//===----------------------------------------------------------------------===// - -#include "Filesystem.h" -#include "Config.h" -#include "lld/Common/Threads.h" -#include "llvm/Config/llvm-config.h" -#include "llvm/Support/FileOutputBuffer.h" -#include "llvm/Support/FileSystem.h" -#if LLVM_ON_UNIX -#include -#endif -#include - -using namespace llvm; - -using namespace lld; -using namespace lld::elf; - -// Removes a given file asynchronously. This is a performance hack, -// so remove this when operating systems are improved. -// -// On Linux (and probably on other Unix-like systems), unlink(2) is a -// noticeably slow system call. As of 2016, unlink takes 250 -// milliseconds to remove a 1 GB file on ext4 filesystem on my machine. -// -// To create a new result file, we first remove existing file. So, if -// you repeatedly link a 1 GB program in a regular compile-link-debug -// cycle, every cycle wastes 250 milliseconds only to remove a file. -// Since LLD can link a 1 GB binary in about 5 seconds, that waste -// actually counts. -// -// This function spawns a background thread to remove the file. -// The calling thread returns almost immediately. -void elf::unlinkAsync(StringRef Path) { -// Removing a file is async on windows. -#if defined(_WIN32) - sys::fs::remove(Path); -#else - if (!ThreadsEnabled || !sys::fs::exists(Path) || - !sys::fs::is_regular_file(Path)) - return; - - // We cannot just remove path from a different thread because we are now going - // to create path as a new file. - // Instead we open the file and unlink it on this thread. The unlink is fast - // since the open fd guarantees that it is not removing the last reference. - int FD; - std::error_code EC = sys::fs::openFileForRead(Path, FD); - sys::fs::remove(Path); - - if (EC) - return; - - // close and therefore remove TempPath in background. - std::mutex M; - std::condition_variable CV; - bool Started = false; - std::thread([&, FD] { - { - std::lock_guard L(M); - Started = true; - CV.notify_all(); - } - ::close(FD); - }).detach(); - - // GLIBC 2.26 and earlier have race condition that crashes an entire process - // if the main thread calls exit(2) while other thread is starting up. - std::unique_lock L(M); - CV.wait(L, [&] { return Started; }); -#endif -} - -// Simulate file creation to see if Path is writable. -// -// Determining whether a file is writable or not is amazingly hard, -// and after all the only reliable way of doing that is to actually -// create a file. But we don't want to do that in this function -// because LLD shouldn't update any file if it will end in a failure. -// We also don't want to reimplement heuristics to determine if a -// file is writable. So we'll let FileOutputBuffer do the work. -// -// FileOutputBuffer doesn't touch a desitnation file until commit() -// is called. We use that class without calling commit() to predict -// if the given file is writable. -std::error_code elf::tryCreateFile(StringRef Path) { - if (Path.empty()) - return std::error_code(); - if (Path == "-") - return std::error_code(); - return errorToErrorCode(FileOutputBuffer::create(Path, 1).takeError()); -} Index: lld/trunk/ELF/Writer.cpp =================================================================== --- lld/trunk/ELF/Writer.cpp +++ lld/trunk/ELF/Writer.cpp @@ -10,7 +10,6 @@ #include "AArch64ErrataFix.h" #include "CallGraphSort.h" #include "Config.h" -#include "Filesystem.h" #include "LinkerScript.h" #include "MapFile.h" #include "OutputSections.h" @@ -19,6 +18,7 @@ #include "Symbols.h" #include "SyntheticSections.h" #include "Target.h" +#include "lld/Common/Filesystem.h" #include "lld/Common/Memory.h" #include "lld/Common/Strings.h" #include "lld/Common/Threads.h" Index: lld/trunk/include/lld/Common/Filesystem.h =================================================================== --- lld/trunk/include/lld/Common/Filesystem.h +++ lld/trunk/include/lld/Common/Filesystem.h @@ -0,0 +1,20 @@ +//===- Filesystem.h ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_FILESYSTEM_H +#define LLD_FILESYSTEM_H + +#include "lld/Common/LLVM.h" +#include + +namespace lld { +void unlinkAsync(StringRef Path); +std::error_code tryCreateFile(StringRef Path); +} // namespace lld + +#endif Index: lld/trunk/test/COFF/driver.test =================================================================== --- lld/trunk/test/COFF/driver.test +++ lld/trunk/test/COFF/driver.test @@ -15,3 +15,7 @@ # RUN: not lld-link /WX /lib 2>&1 | FileCheck -check-prefix=LIBBAD %s LIBBAD: ignoring /lib since it's not the first argument + +# RUN: yaml2obj < %p/Inputs/hello32.yaml > %t.obj +# RUN: not lld-link /out:/ %t.obj 2>&1 | FileCheck -check-prefix=DIR %s +DIR: cannot open output file