1*0b57cec5SDimitry Andric //===- llvm-objcopy.cpp ---------------------------------------------------===// 2*0b57cec5SDimitry Andric // 3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0b57cec5SDimitry Andric // 7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 8*0b57cec5SDimitry Andric 9*0b57cec5SDimitry Andric #include "llvm-objcopy.h" 10*0b57cec5SDimitry Andric #include "Buffer.h" 11*0b57cec5SDimitry Andric #include "CopyConfig.h" 12*0b57cec5SDimitry Andric #include "ELF/ELFObjcopy.h" 13*0b57cec5SDimitry Andric #include "COFF/COFFObjcopy.h" 14*0b57cec5SDimitry Andric #include "MachO/MachOObjcopy.h" 15*0b57cec5SDimitry Andric 16*0b57cec5SDimitry Andric #include "llvm/ADT/STLExtras.h" 17*0b57cec5SDimitry Andric #include "llvm/ADT/SmallVector.h" 18*0b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h" 19*0b57cec5SDimitry Andric #include "llvm/ADT/Twine.h" 20*0b57cec5SDimitry Andric #include "llvm/Object/Archive.h" 21*0b57cec5SDimitry Andric #include "llvm/Object/ArchiveWriter.h" 22*0b57cec5SDimitry Andric #include "llvm/Object/Binary.h" 23*0b57cec5SDimitry Andric #include "llvm/Object/COFF.h" 24*0b57cec5SDimitry Andric #include "llvm/Object/ELFObjectFile.h" 25*0b57cec5SDimitry Andric #include "llvm/Object/ELFTypes.h" 26*0b57cec5SDimitry Andric #include "llvm/Object/Error.h" 27*0b57cec5SDimitry Andric #include "llvm/Object/MachO.h" 28*0b57cec5SDimitry Andric #include "llvm/Option/Arg.h" 29*0b57cec5SDimitry Andric #include "llvm/Option/ArgList.h" 30*0b57cec5SDimitry Andric #include "llvm/Option/Option.h" 31*0b57cec5SDimitry Andric #include "llvm/Support/Casting.h" 32*0b57cec5SDimitry Andric #include "llvm/Support/Error.h" 33*0b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h" 34*0b57cec5SDimitry Andric #include "llvm/Support/ErrorOr.h" 35*0b57cec5SDimitry Andric #include "llvm/Support/InitLLVM.h" 36*0b57cec5SDimitry Andric #include "llvm/Support/Memory.h" 37*0b57cec5SDimitry Andric #include "llvm/Support/Path.h" 38*0b57cec5SDimitry Andric #include "llvm/Support/Process.h" 39*0b57cec5SDimitry Andric #include "llvm/Support/WithColor.h" 40*0b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h" 41*0b57cec5SDimitry Andric #include <algorithm> 42*0b57cec5SDimitry Andric #include <cassert> 43*0b57cec5SDimitry Andric #include <cstdlib> 44*0b57cec5SDimitry Andric #include <memory> 45*0b57cec5SDimitry Andric #include <string> 46*0b57cec5SDimitry Andric #include <system_error> 47*0b57cec5SDimitry Andric #include <utility> 48*0b57cec5SDimitry Andric 49*0b57cec5SDimitry Andric namespace llvm { 50*0b57cec5SDimitry Andric namespace objcopy { 51*0b57cec5SDimitry Andric 52*0b57cec5SDimitry Andric // The name this program was invoked as. 53*0b57cec5SDimitry Andric StringRef ToolName; 54*0b57cec5SDimitry Andric 55*0b57cec5SDimitry Andric LLVM_ATTRIBUTE_NORETURN void error(Twine Message) { 56*0b57cec5SDimitry Andric WithColor::error(errs(), ToolName) << Message << "\n"; 57*0b57cec5SDimitry Andric exit(1); 58*0b57cec5SDimitry Andric } 59*0b57cec5SDimitry Andric 60*0b57cec5SDimitry Andric LLVM_ATTRIBUTE_NORETURN void error(Error E) { 61*0b57cec5SDimitry Andric assert(E); 62*0b57cec5SDimitry Andric std::string Buf; 63*0b57cec5SDimitry Andric raw_string_ostream OS(Buf); 64*0b57cec5SDimitry Andric logAllUnhandledErrors(std::move(E), OS); 65*0b57cec5SDimitry Andric OS.flush(); 66*0b57cec5SDimitry Andric WithColor::error(errs(), ToolName) << Buf; 67*0b57cec5SDimitry Andric exit(1); 68*0b57cec5SDimitry Andric } 69*0b57cec5SDimitry Andric 70*0b57cec5SDimitry Andric LLVM_ATTRIBUTE_NORETURN void reportError(StringRef File, std::error_code EC) { 71*0b57cec5SDimitry Andric assert(EC); 72*0b57cec5SDimitry Andric error(createFileError(File, EC)); 73*0b57cec5SDimitry Andric } 74*0b57cec5SDimitry Andric 75*0b57cec5SDimitry Andric LLVM_ATTRIBUTE_NORETURN void reportError(StringRef File, Error E) { 76*0b57cec5SDimitry Andric assert(E); 77*0b57cec5SDimitry Andric std::string Buf; 78*0b57cec5SDimitry Andric raw_string_ostream OS(Buf); 79*0b57cec5SDimitry Andric logAllUnhandledErrors(std::move(E), OS); 80*0b57cec5SDimitry Andric OS.flush(); 81*0b57cec5SDimitry Andric WithColor::error(errs(), ToolName) << "'" << File << "': " << Buf; 82*0b57cec5SDimitry Andric exit(1); 83*0b57cec5SDimitry Andric } 84*0b57cec5SDimitry Andric 85*0b57cec5SDimitry Andric ErrorSuccess reportWarning(Error E) { 86*0b57cec5SDimitry Andric assert(E); 87*0b57cec5SDimitry Andric WithColor::warning(errs(), ToolName) << toString(std::move(E)); 88*0b57cec5SDimitry Andric return Error::success(); 89*0b57cec5SDimitry Andric } 90*0b57cec5SDimitry Andric 91*0b57cec5SDimitry Andric } // end namespace objcopy 92*0b57cec5SDimitry Andric } // end namespace llvm 93*0b57cec5SDimitry Andric 94*0b57cec5SDimitry Andric using namespace llvm; 95*0b57cec5SDimitry Andric using namespace llvm::object; 96*0b57cec5SDimitry Andric using namespace llvm::objcopy; 97*0b57cec5SDimitry Andric 98*0b57cec5SDimitry Andric // For regular archives this function simply calls llvm::writeArchive, 99*0b57cec5SDimitry Andric // For thin archives it writes the archive file itself as well as its members. 100*0b57cec5SDimitry Andric static Error deepWriteArchive(StringRef ArcName, 101*0b57cec5SDimitry Andric ArrayRef<NewArchiveMember> NewMembers, 102*0b57cec5SDimitry Andric bool WriteSymtab, object::Archive::Kind Kind, 103*0b57cec5SDimitry Andric bool Deterministic, bool Thin) { 104*0b57cec5SDimitry Andric if (Error E = writeArchive(ArcName, NewMembers, WriteSymtab, Kind, 105*0b57cec5SDimitry Andric Deterministic, Thin)) 106*0b57cec5SDimitry Andric return createFileError(ArcName, std::move(E)); 107*0b57cec5SDimitry Andric 108*0b57cec5SDimitry Andric if (!Thin) 109*0b57cec5SDimitry Andric return Error::success(); 110*0b57cec5SDimitry Andric 111*0b57cec5SDimitry Andric for (const NewArchiveMember &Member : NewMembers) { 112*0b57cec5SDimitry Andric // Internally, FileBuffer will use the buffer created by 113*0b57cec5SDimitry Andric // FileOutputBuffer::create, for regular files (that is the case for 114*0b57cec5SDimitry Andric // deepWriteArchive) FileOutputBuffer::create will return OnDiskBuffer. 115*0b57cec5SDimitry Andric // OnDiskBuffer uses a temporary file and then renames it. So in reality 116*0b57cec5SDimitry Andric // there is no inefficiency / duplicated in-memory buffers in this case. For 117*0b57cec5SDimitry Andric // now in-memory buffers can not be completely avoided since 118*0b57cec5SDimitry Andric // NewArchiveMember still requires them even though writeArchive does not 119*0b57cec5SDimitry Andric // write them on disk. 120*0b57cec5SDimitry Andric FileBuffer FB(Member.MemberName); 121*0b57cec5SDimitry Andric if (Error E = FB.allocate(Member.Buf->getBufferSize())) 122*0b57cec5SDimitry Andric return E; 123*0b57cec5SDimitry Andric std::copy(Member.Buf->getBufferStart(), Member.Buf->getBufferEnd(), 124*0b57cec5SDimitry Andric FB.getBufferStart()); 125*0b57cec5SDimitry Andric if (Error E = FB.commit()) 126*0b57cec5SDimitry Andric return E; 127*0b57cec5SDimitry Andric } 128*0b57cec5SDimitry Andric return Error::success(); 129*0b57cec5SDimitry Andric } 130*0b57cec5SDimitry Andric 131*0b57cec5SDimitry Andric /// The function executeObjcopyOnIHex does the dispatch based on the format 132*0b57cec5SDimitry Andric /// of the output specified by the command line options. 133*0b57cec5SDimitry Andric static Error executeObjcopyOnIHex(const CopyConfig &Config, MemoryBuffer &In, 134*0b57cec5SDimitry Andric Buffer &Out) { 135*0b57cec5SDimitry Andric // TODO: support output formats other than ELF. 136*0b57cec5SDimitry Andric return elf::executeObjcopyOnIHex(Config, In, Out); 137*0b57cec5SDimitry Andric } 138*0b57cec5SDimitry Andric 139*0b57cec5SDimitry Andric /// The function executeObjcopyOnRawBinary does the dispatch based on the format 140*0b57cec5SDimitry Andric /// of the output specified by the command line options. 141*0b57cec5SDimitry Andric static Error executeObjcopyOnRawBinary(const CopyConfig &Config, 142*0b57cec5SDimitry Andric MemoryBuffer &In, Buffer &Out) { 143*0b57cec5SDimitry Andric switch (Config.OutputFormat) { 144*0b57cec5SDimitry Andric case FileFormat::ELF: 145*0b57cec5SDimitry Andric // FIXME: Currently, we call elf::executeObjcopyOnRawBinary even if the 146*0b57cec5SDimitry Andric // output format is binary/ihex or it's not given. This behavior differs from 147*0b57cec5SDimitry Andric // GNU objcopy. See https://bugs.llvm.org/show_bug.cgi?id=42171 for details. 148*0b57cec5SDimitry Andric case FileFormat::Binary: 149*0b57cec5SDimitry Andric case FileFormat::IHex: 150*0b57cec5SDimitry Andric case FileFormat::Unspecified: 151*0b57cec5SDimitry Andric return elf::executeObjcopyOnRawBinary(Config, In, Out); 152*0b57cec5SDimitry Andric } 153*0b57cec5SDimitry Andric 154*0b57cec5SDimitry Andric llvm_unreachable("unsupported output format"); 155*0b57cec5SDimitry Andric } 156*0b57cec5SDimitry Andric 157*0b57cec5SDimitry Andric /// The function executeObjcopyOnBinary does the dispatch based on the format 158*0b57cec5SDimitry Andric /// of the input binary (ELF, MachO or COFF). 159*0b57cec5SDimitry Andric static Error executeObjcopyOnBinary(const CopyConfig &Config, 160*0b57cec5SDimitry Andric object::Binary &In, Buffer &Out) { 161*0b57cec5SDimitry Andric if (auto *ELFBinary = dyn_cast<object::ELFObjectFileBase>(&In)) 162*0b57cec5SDimitry Andric return elf::executeObjcopyOnBinary(Config, *ELFBinary, Out); 163*0b57cec5SDimitry Andric else if (auto *COFFBinary = dyn_cast<object::COFFObjectFile>(&In)) 164*0b57cec5SDimitry Andric return coff::executeObjcopyOnBinary(Config, *COFFBinary, Out); 165*0b57cec5SDimitry Andric else if (auto *MachOBinary = dyn_cast<object::MachOObjectFile>(&In)) 166*0b57cec5SDimitry Andric return macho::executeObjcopyOnBinary(Config, *MachOBinary, Out); 167*0b57cec5SDimitry Andric else 168*0b57cec5SDimitry Andric return createStringError(object_error::invalid_file_type, 169*0b57cec5SDimitry Andric "unsupported object file format"); 170*0b57cec5SDimitry Andric } 171*0b57cec5SDimitry Andric 172*0b57cec5SDimitry Andric static Error executeObjcopyOnArchive(const CopyConfig &Config, 173*0b57cec5SDimitry Andric const Archive &Ar) { 174*0b57cec5SDimitry Andric std::vector<NewArchiveMember> NewArchiveMembers; 175*0b57cec5SDimitry Andric Error Err = Error::success(); 176*0b57cec5SDimitry Andric for (const Archive::Child &Child : Ar.children(Err)) { 177*0b57cec5SDimitry Andric Expected<StringRef> ChildNameOrErr = Child.getName(); 178*0b57cec5SDimitry Andric if (!ChildNameOrErr) 179*0b57cec5SDimitry Andric return createFileError(Ar.getFileName(), ChildNameOrErr.takeError()); 180*0b57cec5SDimitry Andric 181*0b57cec5SDimitry Andric Expected<std::unique_ptr<Binary>> ChildOrErr = Child.getAsBinary(); 182*0b57cec5SDimitry Andric if (!ChildOrErr) 183*0b57cec5SDimitry Andric return createFileError(Ar.getFileName() + "(" + *ChildNameOrErr + ")", 184*0b57cec5SDimitry Andric ChildOrErr.takeError()); 185*0b57cec5SDimitry Andric 186*0b57cec5SDimitry Andric MemBuffer MB(ChildNameOrErr.get()); 187*0b57cec5SDimitry Andric if (Error E = executeObjcopyOnBinary(Config, *ChildOrErr->get(), MB)) 188*0b57cec5SDimitry Andric return E; 189*0b57cec5SDimitry Andric 190*0b57cec5SDimitry Andric Expected<NewArchiveMember> Member = 191*0b57cec5SDimitry Andric NewArchiveMember::getOldMember(Child, Config.DeterministicArchives); 192*0b57cec5SDimitry Andric if (!Member) 193*0b57cec5SDimitry Andric return createFileError(Ar.getFileName(), Member.takeError()); 194*0b57cec5SDimitry Andric Member->Buf = MB.releaseMemoryBuffer(); 195*0b57cec5SDimitry Andric Member->MemberName = Member->Buf->getBufferIdentifier(); 196*0b57cec5SDimitry Andric NewArchiveMembers.push_back(std::move(*Member)); 197*0b57cec5SDimitry Andric } 198*0b57cec5SDimitry Andric if (Err) 199*0b57cec5SDimitry Andric return createFileError(Config.InputFilename, std::move(Err)); 200*0b57cec5SDimitry Andric 201*0b57cec5SDimitry Andric return deepWriteArchive(Config.OutputFilename, NewArchiveMembers, 202*0b57cec5SDimitry Andric Ar.hasSymbolTable(), Ar.kind(), 203*0b57cec5SDimitry Andric Config.DeterministicArchives, Ar.isThin()); 204*0b57cec5SDimitry Andric } 205*0b57cec5SDimitry Andric 206*0b57cec5SDimitry Andric static Error restoreStatOnFile(StringRef Filename, 207*0b57cec5SDimitry Andric const sys::fs::file_status &Stat, 208*0b57cec5SDimitry Andric bool PreserveDates) { 209*0b57cec5SDimitry Andric int FD; 210*0b57cec5SDimitry Andric 211*0b57cec5SDimitry Andric // Writing to stdout should not be treated as an error here, just 212*0b57cec5SDimitry Andric // do not set access/modification times or permissions. 213*0b57cec5SDimitry Andric if (Filename == "-") 214*0b57cec5SDimitry Andric return Error::success(); 215*0b57cec5SDimitry Andric 216*0b57cec5SDimitry Andric if (auto EC = 217*0b57cec5SDimitry Andric sys::fs::openFileForWrite(Filename, FD, sys::fs::CD_OpenExisting)) 218*0b57cec5SDimitry Andric return createFileError(Filename, EC); 219*0b57cec5SDimitry Andric 220*0b57cec5SDimitry Andric if (PreserveDates) 221*0b57cec5SDimitry Andric if (auto EC = sys::fs::setLastAccessAndModificationTime( 222*0b57cec5SDimitry Andric FD, Stat.getLastAccessedTime(), Stat.getLastModificationTime())) 223*0b57cec5SDimitry Andric return createFileError(Filename, EC); 224*0b57cec5SDimitry Andric 225*0b57cec5SDimitry Andric sys::fs::file_status OStat; 226*0b57cec5SDimitry Andric if (std::error_code EC = sys::fs::status(FD, OStat)) 227*0b57cec5SDimitry Andric return createFileError(Filename, EC); 228*0b57cec5SDimitry Andric if (OStat.type() == sys::fs::file_type::regular_file) 229*0b57cec5SDimitry Andric #ifdef _WIN32 230*0b57cec5SDimitry Andric if (auto EC = sys::fs::setPermissions( 231*0b57cec5SDimitry Andric Filename, static_cast<sys::fs::perms>(Stat.permissions() & 232*0b57cec5SDimitry Andric ~sys::fs::getUmask()))) 233*0b57cec5SDimitry Andric #else 234*0b57cec5SDimitry Andric if (auto EC = sys::fs::setPermissions( 235*0b57cec5SDimitry Andric FD, static_cast<sys::fs::perms>(Stat.permissions() & 236*0b57cec5SDimitry Andric ~sys::fs::getUmask()))) 237*0b57cec5SDimitry Andric #endif 238*0b57cec5SDimitry Andric return createFileError(Filename, EC); 239*0b57cec5SDimitry Andric 240*0b57cec5SDimitry Andric if (auto EC = sys::Process::SafelyCloseFileDescriptor(FD)) 241*0b57cec5SDimitry Andric return createFileError(Filename, EC); 242*0b57cec5SDimitry Andric 243*0b57cec5SDimitry Andric return Error::success(); 244*0b57cec5SDimitry Andric } 245*0b57cec5SDimitry Andric 246*0b57cec5SDimitry Andric /// The function executeObjcopy does the higher level dispatch based on the type 247*0b57cec5SDimitry Andric /// of input (raw binary, archive or single object file) and takes care of the 248*0b57cec5SDimitry Andric /// format-agnostic modifications, i.e. preserving dates. 249*0b57cec5SDimitry Andric static Error executeObjcopy(const CopyConfig &Config) { 250*0b57cec5SDimitry Andric sys::fs::file_status Stat; 251*0b57cec5SDimitry Andric if (Config.InputFilename != "-") { 252*0b57cec5SDimitry Andric if (auto EC = sys::fs::status(Config.InputFilename, Stat)) 253*0b57cec5SDimitry Andric return createFileError(Config.InputFilename, EC); 254*0b57cec5SDimitry Andric } else { 255*0b57cec5SDimitry Andric Stat.permissions(static_cast<sys::fs::perms>(0777)); 256*0b57cec5SDimitry Andric } 257*0b57cec5SDimitry Andric 258*0b57cec5SDimitry Andric typedef Error (*ProcessRawFn)(const CopyConfig &, MemoryBuffer &, Buffer &); 259*0b57cec5SDimitry Andric ProcessRawFn ProcessRaw; 260*0b57cec5SDimitry Andric switch (Config.InputFormat) { 261*0b57cec5SDimitry Andric case FileFormat::Binary: 262*0b57cec5SDimitry Andric ProcessRaw = executeObjcopyOnRawBinary; 263*0b57cec5SDimitry Andric break; 264*0b57cec5SDimitry Andric case FileFormat::IHex: 265*0b57cec5SDimitry Andric ProcessRaw = executeObjcopyOnIHex; 266*0b57cec5SDimitry Andric break; 267*0b57cec5SDimitry Andric default: 268*0b57cec5SDimitry Andric ProcessRaw = nullptr; 269*0b57cec5SDimitry Andric } 270*0b57cec5SDimitry Andric 271*0b57cec5SDimitry Andric if (ProcessRaw) { 272*0b57cec5SDimitry Andric auto BufOrErr = MemoryBuffer::getFileOrSTDIN(Config.InputFilename); 273*0b57cec5SDimitry Andric if (!BufOrErr) 274*0b57cec5SDimitry Andric return createFileError(Config.InputFilename, BufOrErr.getError()); 275*0b57cec5SDimitry Andric FileBuffer FB(Config.OutputFilename); 276*0b57cec5SDimitry Andric if (Error E = ProcessRaw(Config, *BufOrErr->get(), FB)) 277*0b57cec5SDimitry Andric return E; 278*0b57cec5SDimitry Andric } else { 279*0b57cec5SDimitry Andric Expected<OwningBinary<llvm::object::Binary>> BinaryOrErr = 280*0b57cec5SDimitry Andric createBinary(Config.InputFilename); 281*0b57cec5SDimitry Andric if (!BinaryOrErr) 282*0b57cec5SDimitry Andric return createFileError(Config.InputFilename, BinaryOrErr.takeError()); 283*0b57cec5SDimitry Andric 284*0b57cec5SDimitry Andric if (Archive *Ar = dyn_cast<Archive>(BinaryOrErr.get().getBinary())) { 285*0b57cec5SDimitry Andric if (Error E = executeObjcopyOnArchive(Config, *Ar)) 286*0b57cec5SDimitry Andric return E; 287*0b57cec5SDimitry Andric } else { 288*0b57cec5SDimitry Andric FileBuffer FB(Config.OutputFilename); 289*0b57cec5SDimitry Andric if (Error E = executeObjcopyOnBinary(Config, 290*0b57cec5SDimitry Andric *BinaryOrErr.get().getBinary(), FB)) 291*0b57cec5SDimitry Andric return E; 292*0b57cec5SDimitry Andric } 293*0b57cec5SDimitry Andric } 294*0b57cec5SDimitry Andric 295*0b57cec5SDimitry Andric if (Error E = 296*0b57cec5SDimitry Andric restoreStatOnFile(Config.OutputFilename, Stat, Config.PreserveDates)) 297*0b57cec5SDimitry Andric return E; 298*0b57cec5SDimitry Andric 299*0b57cec5SDimitry Andric if (!Config.SplitDWO.empty()) { 300*0b57cec5SDimitry Andric Stat.permissions(static_cast<sys::fs::perms>(0666)); 301*0b57cec5SDimitry Andric if (Error E = 302*0b57cec5SDimitry Andric restoreStatOnFile(Config.SplitDWO, Stat, Config.PreserveDates)) 303*0b57cec5SDimitry Andric return E; 304*0b57cec5SDimitry Andric } 305*0b57cec5SDimitry Andric 306*0b57cec5SDimitry Andric return Error::success(); 307*0b57cec5SDimitry Andric } 308*0b57cec5SDimitry Andric 309*0b57cec5SDimitry Andric int main(int argc, char **argv) { 310*0b57cec5SDimitry Andric InitLLVM X(argc, argv); 311*0b57cec5SDimitry Andric ToolName = argv[0]; 312*0b57cec5SDimitry Andric bool IsStrip = sys::path::stem(ToolName).contains("strip"); 313*0b57cec5SDimitry Andric Expected<DriverConfig> DriverConfig = 314*0b57cec5SDimitry Andric IsStrip ? parseStripOptions(makeArrayRef(argv + 1, argc), reportWarning) 315*0b57cec5SDimitry Andric : parseObjcopyOptions(makeArrayRef(argv + 1, argc)); 316*0b57cec5SDimitry Andric if (!DriverConfig) { 317*0b57cec5SDimitry Andric logAllUnhandledErrors(DriverConfig.takeError(), 318*0b57cec5SDimitry Andric WithColor::error(errs(), ToolName)); 319*0b57cec5SDimitry Andric return 1; 320*0b57cec5SDimitry Andric } 321*0b57cec5SDimitry Andric for (const CopyConfig &CopyConfig : DriverConfig->CopyConfigs) { 322*0b57cec5SDimitry Andric if (Error E = executeObjcopy(CopyConfig)) { 323*0b57cec5SDimitry Andric logAllUnhandledErrors(std::move(E), WithColor::error(errs(), ToolName)); 324*0b57cec5SDimitry Andric return 1; 325*0b57cec5SDimitry Andric } 326*0b57cec5SDimitry Andric } 327*0b57cec5SDimitry Andric 328*0b57cec5SDimitry Andric return 0; 329*0b57cec5SDimitry Andric } 330