1 /*
2  * The Doomsday Engine Project -- libcore
3  *
4  * Copyright © 2009-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
5  *
6  * @par License
7  * LGPL: http://www.gnu.org/licenses/lgpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
15  * General Public License for more details. You should have received a copy of
16  * the GNU Lesser General Public License along with this program; if not, see:
17  * http://www.gnu.org/licenses</small>
18  */
19 
20 #include "de/LibraryFile"
21 #include "de/Library"
22 #include "de/NativeFile"
23 #include "de/NativePath"
24 #include "de/LogBuffer"
25 
26 #include <QLibrary>
27 
28 using namespace de;
29 
DENG2_PIMPL_NOREF(LibraryFile)30 DENG2_PIMPL_NOREF(LibraryFile)
31 {
32     Library *library = nullptr;
33     NativePath nativePath;
34 };
35 
LibraryFile(File * source)36 LibraryFile::LibraryFile(File *source)
37     : File(source->name())
38     , d(new Impl)
39 {
40     DENG2_ASSERT(source != 0);
41     setSource(source); // takes ownership
42 }
43 
LibraryFile(NativePath const & nativePath)44 LibraryFile::LibraryFile(NativePath const &nativePath)
45     : File(nativePath.fileName())
46     , d(new Impl)
47 {
48     d->nativePath = nativePath;
49 }
50 
~LibraryFile()51 LibraryFile::~LibraryFile()
52 {
53     DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this);
54     audienceForDeletion().clear();
55 
56     deindex();
57     delete d->library;
58 }
59 
describe() const60 String LibraryFile::describe() const
61 {
62     String desc = "shared library";
63     if (loaded()) desc += " [" + library().type() + "]";
64     return desc;
65 }
66 
loaded() const67 bool LibraryFile::loaded() const
68 {
69     return d->library != 0;
70 }
71 
library()72 Library &LibraryFile::library()
73 {
74     if (d->library)
75     {
76         return *d->library;
77     }
78 
79     if (!d->nativePath.isEmpty())
80     {
81         d->library = new Library(d->nativePath);
82     }
83     else
84     {
85         /// @todo A mechanism to make a NativeFile out of any File via caching?
86 
87         NativeFile *native = maybeAs<NativeFile>(source());
88         if (!native)
89         {
90             /// @throw UnsupportedSourceError Currently shared libraries are only loaded directly
91             /// from native files. Other kinds of files would require a temporary native file.
92             throw UnsupportedSourceError("LibraryFile::library", source()->description() +
93                 ": can only load from NativeFile");
94         }
95         d->library = new Library(native->nativePath());
96     }
97     return *d->library;
98 }
99 
library() const100 Library const &LibraryFile::library() const
101 {
102     if (d->library) return *d->library;
103 
104     /// @throw NotLoadedError Library is presently not loaded.
105     throw NotLoadedError("LibraryFile::library", "Library is not loaded: " + description());
106 }
107 
clear()108 void LibraryFile::clear()
109 {
110     if (d->library)
111     {
112         delete d->library;
113         d->library = nullptr;
114     }
115 }
116 
hasUnderscoreName(String const & nameAfterUnderscore) const117 bool LibraryFile::hasUnderscoreName(String const &nameAfterUnderscore) const
118 {
119     return name().contains("_" + nameAfterUnderscore + ".") ||
120            name().endsWith("_" + nameAfterUnderscore);
121 }
122 
recognize(File const & file)123 bool LibraryFile::recognize(File const &file)
124 {
125     #if defined (DENG_APPLE)
126     {
127         // On macOS/iOS, plugins are in the .bundle format. The LibraryFile will point
128         // to the actual binary inside the bundle. Libraries must be loaded from
129         // native files.
130         if (NativeFile const *native = maybeAs<NativeFile>(file))
131         {
132             // Check if this in the executable folder with a matching bundle name.
133             if (native->nativePath().fileNamePath().toString()
134                 .endsWith(file.name() + ".bundle/Contents/MacOS"))
135             {
136                 return true;
137             }
138         }
139     }
140     #else // not Apple
141     {
142         // Check the extension first.
143         if (QLibrary::isLibrary(file.name()))
144         {
145             #if defined(UNIX)
146             {
147                 // Only actual .so files should be considered.
148                 if (!file.name().endsWith(".so")) // just checks the file name
149                 {
150                     return false;
151                 }
152             }
153             #endif
154 
155             // Looks like a library.
156             return true;
157         }
158     }
159     #endif
160 
161     return false;
162 }
163 
interpretFile(File * sourceData) const164 File *LibraryFile::Interpreter::interpretFile(File *sourceData) const
165 {
166     if (recognize(*sourceData))
167     {
168         LOG_RES_XVERBOSE("Interpreted %s as a shared library", sourceData->description());
169 
170         // It is a shared library intended for Doomsday.
171         return new LibraryFile(sourceData);
172     }
173     return nullptr;
174 }
175