Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -42,6 +42,7 @@ #include "llvm/ADT/StringSwitch.h" #include "llvm/Object/Decompressor.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/TarWriter.h" #include "llvm/Support/TargetSelect.h" @@ -501,6 +502,30 @@ return Ret; } +// Gets a path for an output file, or emits an error if the path specified is +// not a valid file path. +static StringRef getOutputFilePath(opt::InputArgList &Args, unsigned Key, + bool PerformPathChecks) { + StringRef OutputPath = getString(Args, Key); + if (OutputPath.empty() || !PerformPathChecks) + return OutputPath; + + auto *Arg = Args.getLastArg(Key); + assert(Arg != nullptr && "expected argument in list"); + auto OptionName = Arg->getSpelling(); + + if (sys::fs::is_directory(OutputPath)) { + error(OptionName + ": " + OutputPath + " is a directory"); + return OutputPath; + } + + StringRef ParentPath = sys::path::parent_path(OutputPath); + if (!ParentPath.empty() && !sys::fs::is_directory(ParentPath)) { + error(OptionName + ": cannot write to " + OutputPath); + } + return OutputPath; +} + // Initializes Config members by the command line options. void LinkerDriver::readConfigs(opt::InputArgList &Args) { for (auto *Arg : Args.filtered(OPT_L)) @@ -560,9 +585,15 @@ Config->Init = getString(Args, OPT_init, "_init"); Config->LTOAAPipeline = getString(Args, OPT_lto_aa_pipeline); Config->LTONewPmPasses = getString(Args, OPT_lto_newpm_passes); - Config->MapFile = getString(Args, OPT_Map); - Config->OptRemarksFilename = getString(Args, OPT_opt_remarks_filename); - Config->OutputFile = getString(Args, OPT_o); + + bool PerformPathChecks = !Args.hasArg(OPT_no_early_path_checks); + Config->MapFile = + getOutputFilePath(Args, OPT_Map, PerformPathChecks); + Config->OptRemarksFilename = getOutputFilePath( + Args, OPT_opt_remarks_filename, PerformPathChecks); + Config->OutputFile = + getOutputFilePath(Args, OPT_o, PerformPathChecks); + Config->SoName = getString(Args, OPT_soname); Config->Sysroot = getString(Args, OPT_sysroot); Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -151,6 +151,9 @@ def no_dynamic_linker: F<"no-dynamic-linker">, HelpText<"Inhibit output of .interp section">; +def no_early_path_checks: F<"no-early-path-checks">, + HelpText<"Disable early checks for valid output file paths">; + def no_export_dynamic: F<"no-export-dynamic">; def no_fatal_warnings: F<"no-fatal-warnings">; Index: test/ELF/driver.test =================================================================== --- test/ELF/driver.test +++ test/ELF/driver.test @@ -10,7 +10,7 @@ # UNKNOWN: unable to find library -lnosuchlib # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t -# RUN: not ld.lld %t -o /no/such/file 2>&1 | FileCheck -check-prefix=MISSING %s +# RUN: not ld.lld %t -o /no/such/file -no-early-path-checks 2>&1 | FileCheck -check-prefix=MISSING %s # MISSING: failed to open /no/such/file # RUN: ld.lld --help 2>&1 | FileCheck -check-prefix=HELP %s @@ -47,11 +47,11 @@ # ERR6: -shared and -pie may not be used together ## "--output=foo" is equivalent to "-o foo". -# RUN: not ld.lld %t --output=/no/such/file 2>&1 | FileCheck -check-prefix=ERR7 %s +# RUN: not ld.lld %t --output=/no/such/file -no-early-path-checks 2>&1 | FileCheck -check-prefix=ERR7 %s # ERR7: failed to open /no/such/file ## "-output=foo" is equivalent to "-o utput=foo". -# RUN: not ld.lld %t -output=/no/such/file 2>&1 | FileCheck -check-prefix=ERR8 %s +# RUN: not ld.lld %t -output=/no/such/file -no-early-path-checks 2>&1 | FileCheck -check-prefix=ERR8 %s # ERR8: failed to open utput=/no/such/file .globl _start Index: test/ELF/early-exit-for-bad-paths.s =================================================================== --- test/ELF/early-exit-for-bad-paths.s +++ test/ELF/early-exit-for-bad-paths.s @@ -0,0 +1,43 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o + +# RUN: not ld.lld %t.o -o does_not_exist/output 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=OUTPUT,NO-DIR,CHECK +# RUN: not ld.lld %t.o -o %s/dir_is_a_file 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=OUTPUT,DIR-IS-FILE,CHECK +# RUN: not ld.lld %t.o -o %p 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=OUTPUT,PATH-IS-DIR,CHECK + +# RUN: not ld.lld %t.o -o %t.elf --Map=does_not_exist/output 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=MAP,NO-DIR,CHECK +# RUN: not ld.lld %t.o -o %t.elf --Map=%s/dir_is_a_file 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=MAP,DIR-IS-FILE,CHECK +# RUN: not ld.lld %t.o -o %t.elf --Map=%p 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=MAP,PATH-IS-DIR,CHECK + +# RUN: not ld.lld %t.o -o %t.elf -opt-remarks-filename does_not_exist/output 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=OPTREMARKS,NO-DIR,CHECK +# RUN: not ld.lld %t.o -o %t.elf -opt-remarks-filename %s/dir_is_a_file 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=OPTREMARKS,DIR-IS-FILE,CHECK +# RUN: not ld.lld %t.o -o %t.elf -opt-remarks-filename %p 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=OPTREMARKS,PATH-IS-DIR,CHECK + +# RUN: not ld.lld %t.o -o %s/do_not_check --no-early-path-checks 2>&1 | \ +# RUN: FileCheck %s -check-prefixes=NOCHECK + +# OUTPUT: error: -o: +# MAP: error: --Map: +# OPTREMARKS: error: -opt-remarks-filename: + +# NO-DIR-SAME: cannot write to does_not_exist/output +# DIR-IS-FILE-SAME: cannot write to {{.*}}/dir_is_a_file +# PATH-IS-DIR-SAME: {{.*}} is a directory + +# We should exit before doing the actual link. If an undefined symbol error is +# discovered we haven't bailed out early as expected. +# CHECK-NOT: undefined_symbol +# NOCHECK: undefined symbol 'undefined_symbol' + + .globl _start +_start: + call undefined_symbol Index: test/ELF/map-file.s =================================================================== --- test/ELF/map-file.s +++ test/ELF/map-file.s @@ -57,6 +57,6 @@ // CHECK-NEXT: 0000000000000000 000000000000002f 1 .strtab // CHECK-NEXT: 0000000000000000 000000000000002f 1 .strtab -// RUN: not ld.lld %t1.o %t2.o %t3.o %t4.a -o %t -Map=/ 2>&1 \ +// RUN: not ld.lld %t1.o %t2.o %t3.o %t4.a -o %t -Map=/ -no-early-path-checks 2>&1 \ // RUN: | FileCheck -check-prefix=FAIL %s // FAIL: cannot open /