MatN work for versatile appimage creation for all types of os
[goodguy/cinelerra.git] / cinelerra-5.1 / tools / makeappimagetool / appdir.cpp
1 // system headers
2 #include <set>
3 #include <map>
4 #include <string>
5 #include <vector>
6
7 // library headers
8 #include <boost/filesystem.hpp>
9 #include <fnmatch.h>
10
11 // local headers
12 #include "includes/appdir.h"
13 #include "includes/CImg.h"
14 #include "includes/elf_file.h"
15 #include "includes/log.h"
16 #include "includes/util.h"
17 #include "includes/subprocess.h"
18 #include "includes/copyright.h"
19
20 // auto-generated headers
21 #include "includes/excludelist.h"
22 #include "includes/appdir_root_setup.h"
23
24 using namespace linuxdeploy::core;
25 using namespace linuxdeploy::desktopfile;
26 using namespace linuxdeploy::core::log;
27
28 using namespace cimg_library;
29 namespace bf = boost::filesystem;
30
31 namespace {
32     // equivalent to 0644
33     constexpr bf::perms DEFAULT_PERMS = bf::owner_write | bf::owner_read | bf::group_read | bf::others_read;
34     // equivalent to 0755
35     constexpr bf::perms EXECUTABLE_PERMS = DEFAULT_PERMS | bf::owner_exe | bf::group_exe | bf::others_exe;
36
37     class CopyOperation {
38     public:
39         bf::path fromPath;
40         bf::path toPath;
41         bf::perms addedPermissions;
42     };
43
44     typedef std::map<bf::path, CopyOperation> CopyOperationsMap;
45
46     /**
47      * Stores copy operations.
48      * This way, the storage logic does not have to be known to the using class.
49      */
50     class CopyOperationsStorage {
51     private:
52         // using a map to make sure every target path is there only once
53         CopyOperationsMap _storedOperations;
54
55     public:
56         CopyOperationsStorage() = default;
57
58         /**
59          * Add copy operation.
60          * @param fromPath path to copy from
61          * @param toPath path to copy to
62          * @param addedPermissions permissions to add to the file's permissions
63          */
64         void addOperation(const bf::path& fromPath, const bf::path& toPath, const bf::perms addedPermissions) {
65              CopyOperation operation{fromPath, toPath, addedPermissions};
66             _storedOperations[fromPath] = operation;
67         }
68
69         /**
70          * Export operations.
71          * @return vector containing all operations (random order).
72          */
73         std::vector<CopyOperation> getOperations() {
74             std::vector<CopyOperation> operations;
75             operations.reserve(_storedOperations.size());
76
77             for (const auto& operationsPair : _storedOperations) {
78                 operations.emplace_back(operationsPair.second);
79             }
80
81             return operations;
82         }
83
84         /**
85          * Clear internal storage.
86          */
87         void clear() {
88             _storedOperations.clear();
89         }
90     };
91 }
92
93 namespace linuxdeploy {
94     namespace core {
95         namespace appdir {
96             class AppDir::PrivateData {
97                 public:
98                     bf::path appDirPath;
99
100                     // store deferred operations
101                     // these can be executed by calling excuteDeferredOperations
102                     CopyOperationsStorage copyOperationsStorage;
103                     std::set<bf::path> stripOperations;
104                     std::map<bf::path, std::string> setElfRPathOperations;
105
106                     // stores all files that have been visited by the deploy functions, e.g., when they're blacklisted,
107                     // have been added to the deferred operations already, etc.
108                     // lookups in a single container are a lot faster than having to look up in several ones, therefore
109                     // the little amount of additional memory is worth it, considering the improved performance
110                     std::set<bf::path> visitedFiles;
111
112                     // used to automatically rename resources to improve the UX, e.g. icons
113                     std::string appName;
114
115                     // platform dependent implementation of copyright files deployment
116                     std::shared_ptr<copyright::ICopyrightFilesManager> copyrightFilesManager;
117
118                     // decides whether copyright files deployment is performed
119                     bool disableCopyrightFilesDeployment = false;
120
121                 public:
122                 PrivateData() : copyOperationsStorage(), stripOperations(), setElfRPathOperations(), visitedFiles(), appDirPath() {
123                         copyrightFilesManager = copyright::ICopyrightFilesManager::getInstance();
124                     };
125
126                 public:
127                     // calculate library directory name for given ELF file, taking system architecture into account
128                     static std::string getLibraryDirName(const bf::path& path) {
129                         const auto systemElfClass = elf_file::ElfFile::getSystemElfClass();
130                         const auto elfClass = elf_file::ElfFile(path).getElfClass();
131
132                         std::string libDirName = "lib";
133
134                         if (systemElfClass != elfClass) {
135                             if (elfClass == ELFCLASS32)
136                                 libDirName += "32";
137                             else
138                                 libDirName += "64";
139                         }
140
141                          return libDirName;
142                     }
143
144                     // actually copy file
145                     // mimics cp command behavior
146                     // also adds minimum file permissions (by default adds 0644 to existing permissions)
147                     static bool copyFile(const bf::path& from, bf::path to, bf::perms addedPerms, bool overwrite = false) {
148                         ldLog() << "Copying file" << from << "to" << to << std::endl;
149
150                         try {
151                             if (!to.parent_path().empty() && !bf::is_directory(to.parent_path()) && !bf::create_directories(to.parent_path())) {
152                                 ldLog() << LD_ERROR << "Failed to create parent directory" << to.parent_path() << "for path" << to << std::endl;
153                                 return false;
154                             }
155
156                             if (*(to.string().end() - 1) == '/' || bf::is_directory(to))
157                                 to /= from.filename();
158
159                             if (!overwrite && bf::exists(to)) {
160                                 ldLog() << LD_DEBUG << "File exists, skipping:" << to << std::endl;
161                                 return true;
162                             }
163
164                             bf::copy_file(from, to, bf::copy_option::overwrite_if_exists);
165                             bf::permissions(to, addedPerms | bf::add_perms);
166                         } catch (const bf::filesystem_error& e) {
167                             ldLog() << LD_ERROR << "Failed to copy file" << from << "to" << to << LD_NO_SPACE << ":" << e.what() << std::endl;
168                             return false;
169                         }
170
171                         return true;
172                     }
173
174                     // create symlink
175                     static bool symlinkFile(const bf::path& target, bf::path symlink, const bool useRelativePath = true) {
176                         ldLog() << "Creating symlink for file" << target << "in/as" << symlink << std::endl;
177
178                         if (!useRelativePath) {
179                             ldLog() << LD_ERROR << "Not implemented" << std::endl;
180                             return false;
181                         }
182
183                         bf::path relativeTargetPath;
184
185                         // cannot use ln's --relative option any more since we want to support old distros as well
186                         // (looking at you, CentOS 6!)
187                         {
188                             auto symlinkBase = symlink;
189
190                             if (!bf::is_directory(symlinkBase))
191                                 symlinkBase = symlinkBase.parent_path();
192
193                             relativeTargetPath = bf::relative(target, symlinkBase);
194                         }
195
196                         // if a directory is passed as path to create the symlink as/in, we need to complete it with
197                         // the filename of the source file to mimic ln's behavior
198                         if (bf::is_directory(symlink))
199                             symlink /= target.filename();
200
201                         // override existing target (similar to ln's -f flag)
202                         if (bf::exists(symlink))
203                             bf::remove(symlink);
204
205                         // actually perform symlink creation
206                         try {
207                             bf::create_symlink(relativeTargetPath, symlink);
208                         } catch (const bf::filesystem_error& e) {
209                             ldLog() << LD_ERROR << "symlink creation failed:" << e.what() << std::endl;
210                             return false;
211                         }
212
213                         return true;
214                     }
215
216                     bool hasBeenVisitedAlready(const bf::path& path) {
217                         return visitedFiles.find(path) != visitedFiles.end();
218                     }
219
220                     // execute deferred copy operations registered with the deploy* functions
221                     bool executeDeferredOperations() {
222                         bool success = true;
223
224                         const auto copyOperations = copyOperationsStorage.getOperations();
225                         std::for_each(copyOperations.begin(), copyOperations.end(), [&success](const CopyOperation& operation) {
226                             if (!copyFile(operation.fromPath, operation.toPath, operation.addedPermissions)) {
227                                 success = false;
228                             }
229                         });
230                         copyOperationsStorage.clear();
231
232                         if (!success)
233                             return false;
234
235                         if (getenv("NO_STRIP") != nullptr) {
236                             ldLog() << LD_WARNING << "$NO_STRIP environment variable detected, not stripping binaries" << std::endl;
237                             stripOperations.clear();
238                         } else {
239                             const auto stripPath = getStripPath();
240
241                             while (!stripOperations.empty()) {
242                                 const auto& filePath = *(stripOperations.begin());
243
244                                 if (util::stringStartsWith(elf_file::ElfFile(filePath).getRPath(), "$")) {
245                                     ldLog() << LD_WARNING << "Not calling strip on binary" << filePath << LD_NO_SPACE
246                                             << ": rpath starts with $" << std::endl;
247                                 } else {
248                                     ldLog() << "Calling strip on library" << filePath << std::endl;
249
250                                     subprocess::subprocess_env_map_t env;
251                                     env.insert(std::make_pair(std::string("LC_ALL"), std::string("C")));
252
253                                     subprocess::subprocess proc({stripPath, filePath.string()}, env);
254
255                                     const auto result = proc.run();
256                                     const auto& err = result.stderr_string();
257
258                                     if (result.exit_code() != 0 &&
259                                         !util::stringContains(err, "Not enough room for program headers")) {
260                                         ldLog() << LD_ERROR << "Strip call failed:" << err << std::endl;
261                                         success = false;
262                                     }
263                                 }
264
265                                 stripOperations.erase(stripOperations.begin());
266                             }
267                         }
268
269                         if (!success)
270                             return false;
271
272                         while (!setElfRPathOperations.empty()) {
273                             const auto& currentEntry = *(setElfRPathOperations.begin());
274                             const auto& filePath = currentEntry.first;
275                             const auto& rpath = currentEntry.second;
276
277                             elf_file::ElfFile elfFile(filePath);
278
279                             // no need to set rpath in debug symbols files
280                             // also, patchelf crashes on such symbols
281                             if (isInDebugSymbolsLocation(filePath) || elfFile.isDebugSymbolsFile()) {
282                                 ldLog() << LD_WARNING << "Not setting rpath in debug symbols file:" << filePath
283                                         << std::endl;
284                             } else if (!elfFile.isDynamicallyLinked()) {
285                                 ldLog() << LD_WARNING << "Not setting rpath in statically-linked file: " << filePath
286                                         << std::endl;
287                             } else {
288                                 ldLog() << "Setting rpath in ELF file" << filePath << "to" << rpath << std::endl;
289                                 if (!elf_file::ElfFile(filePath).setRPath(rpath)) {
290                                     ldLog() << LD_ERROR << "Failed to set rpath in ELF file:" << filePath << std::endl;
291                                     success = false;
292                                 }
293                             }
294
295                             setElfRPathOperations.erase(setElfRPathOperations.begin());
296                         }
297
298                         return true;
299                     }
300
301                     // search for copyright file for file and deploy it to AppDir
302                     bool deployCopyrightFiles(const bf::path& from) {
303                         if (disableCopyrightFilesDeployment)
304                             return true;
305
306                         if (copyrightFilesManager == nullptr)
307                             return false;
308
309                         auto copyrightFiles = copyrightFilesManager->getCopyrightFilesForPath(from);
310
311                         if (copyrightFiles.empty())
312                             return false;
313
314                         ldLog() << "Deploying copyright files for file" << from << std::endl;
315
316                         for (const auto& file : copyrightFiles) {
317                             std::string targetDir = file.string();
318                             targetDir.erase(0, 1);
319                             deployFile(file, appDirPath / targetDir, DEFAULT_PERMS);
320                         }
321
322                         return true;
323                     }
324
325                     // register copy operation that will be executed later
326                     // by compiling a list of files to copy instead of just copying everything, one can ensure that
327                     // the files are touched once only
328                     // returns the full path of the deployment destination (useful if to is a directory
329                     bf::path deployFile(const bf::path& from, bf::path to, bf::perms addedPerms, bool verbose = false) {
330                         // not sure whether this is 100% bullet proof, but it simulates the cp command behavior
331                         if (to.string().back() == '/' || bf::is_directory(to)) {
332                             to /= from.filename();
333                         }
334
335                         if (verbose)
336                             ldLog() << "Deploying file" << from << "to" << to << std::endl;
337
338                         copyOperationsStorage.addOperation(from, to, addedPerms);
339
340                         // mark file as visited
341                         visitedFiles.insert(from);
342
343                         return to;
344                     }
345
346                     bool deployElfDependencies(const bf::path& path) {
347                         ldLog() << "Deploying dependencies for ELF file" << path << std::endl;
348                         try {
349                             for (const auto &dependencyPath : elf_file::ElfFile(path).traceDynamicDependencies())
350                                 if (!deployLibrary(dependencyPath, false, false))
351                                     return false;
352                         } catch (const elf_file::DependencyNotFoundError& e) {
353                             ldLog() << LD_ERROR << e.what() << std::endl;
354                             return false;
355                         }
356
357                         return true;
358                     }
359
360                     static std::string getStripPath() {
361                         // by default, try to use a strip next to the makeappimage binary
362                         // if that isn't available, fall back to searching for strip in the PATH
363                         std::string stripPath = "strip";
364
365                         auto binDirPath = bf::path(util::getOwnExecutablePath()).parent_path();
366                         auto localStripPath = binDirPath / "strip";
367
368                         if (bf::exists(localStripPath))
369                             stripPath = localStripPath.string();
370
371                         ldLog() << LD_DEBUG << "Using strip:" << stripPath << std::endl;
372
373                         return stripPath;
374                     }
375
376                     static std::string calculateRelativeRPath(const bf::path& originDir, const bf::path& dependencyLibrariesDir) {
377                         auto relPath = bf::relative(bf::absolute(dependencyLibrariesDir), bf::absolute(originDir));
378                         std::string rpath = "$ORIGIN/" + relPath.string() + ":$ORIGIN";
379                         return rpath;
380                     }
381
382                     bool deployLibrary(const bf::path& path, bool forceDeploy = false, bool deployDependencies = true, const bf::path& destination = bf::path()) {
383                         if (!forceDeploy && hasBeenVisitedAlready(path)) {
384                             ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
385                             return true;
386                         }
387
388                         if (!bf::exists(path)) {
389                             ldLog() << LD_ERROR << "Cannot deploy non-existing library file:" << path << std::endl;
390                             return false;
391                         }
392
393                         static auto isInExcludelist = [](const bf::path& fileName) {
394                             for (const auto& excludePattern : generatedExcludelist) {
395                                 // simple string match is faster than using fnmatch
396                                 if (excludePattern == fileName)
397                                     return true;
398
399                                 auto fnmatchResult = fnmatch(excludePattern.c_str(), fileName.string().c_str(), FNM_PATHNAME);
400                                 switch (fnmatchResult) {
401                                     case 0:
402                                         return true;
403                                     case FNM_NOMATCH:
404                                         break;
405                                     default:
406                                         ldLog() << LD_ERROR << "fnmatch() reported error:" << fnmatchResult << std::endl;
407                                         return false;
408                                 }
409                             }
410
411                             return false;
412                         };
413
414                         if (!forceDeploy && isInExcludelist(path.filename())) {
415                             ldLog() << "Skipping deployment of blacklisted library" << path << std::endl;
416
417                             // mark file as visited
418                             visitedFiles.insert(path);
419
420                             return true;
421                         }
422
423                         // note for self: make sure to have a trailing slash in libraryDir, otherwise copyFile won't
424                         // create a directory
425                         bf::path libraryDir = appDirPath / "usr" / (getLibraryDirName(path) + "/");
426
427                         ldLog() << "Deploying shared library" << path;
428                         if (!destination.empty())
429                             ldLog() << " (destination:" << destination << LD_NO_SPACE << ")";
430                         ldLog() << std::endl;
431
432                         auto actualDestination = destination.empty() ? libraryDir : destination;
433
434                         // not sure whether this is 100% bullet proof, but it simulates the cp command behavior
435                         if (actualDestination.string().back() == '/' || bf::is_directory(actualDestination)) {
436                             actualDestination /= path.filename();
437                         }
438
439                         // in case destinationPath is a directory, deployFile will give us the deployed file's path
440                         actualDestination = deployFile(path, actualDestination, DEFAULT_PERMS);
441                         deployCopyrightFiles(path);
442
443                         std::string rpath = "$ORIGIN";
444
445                         if (!destination.empty()) {
446                             // destination is the place where to deploy this file
447                             // therefore, rpathDestination means
448                             std::string rpathOriginDir = destination.string();
449
450                             if (destination.string().back() == '/') {
451                                 rpathOriginDir = destination.string();
452
453                                 while (rpathOriginDir.back() == '/')
454                                     rpathOriginDir.erase(rpathOriginDir.end() - 1, rpathOriginDir.end());
455                             } else {
456                                 rpathOriginDir = destination.parent_path().string();
457                             }
458
459                             rpath = calculateRelativeRPath(rpathOriginDir, libraryDir);
460                         }
461
462                         // no need to set rpath in debug symbols files
463                         // also, patchelf crashes on such symbols
464                         if (!isInDebugSymbolsLocation(actualDestination)) {
465                             setElfRPathOperations[actualDestination] = rpath;
466                         }
467
468                         stripOperations.insert(actualDestination);
469
470                         if (!deployDependencies)
471                             return true;
472
473                         return deployElfDependencies(path);
474                     }
475
476                     bool deployExecutable(const bf::path& path, const boost::filesystem::path& destination) {
477                         if (hasBeenVisitedAlready(path)) {
478                             ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
479                             return true;
480                         }
481
482                         ldLog() << "Deploying executable" << path << std::endl;
483
484                         // FIXME: make executables executable
485
486                         auto destinationPath = destination.empty() ? appDirPath / "usr/bin/" : destination;
487
488                         deployFile(path, destinationPath, EXECUTABLE_PERMS);
489                         deployCopyrightFiles(path);
490
491                         std::string rpath = "$ORIGIN/../" + getLibraryDirName(path);
492
493                         if (!destination.empty()) {
494                             std::string rpathDestination = destination.string();
495
496                             if (destination.string().back() == '/') {
497                                 rpathDestination = destination.string();
498
499                                 while (rpathDestination.back() == '/')
500                                     rpathDestination.erase(rpathDestination.end() - 1, rpathDestination.end());
501                             } else {
502                                 rpathDestination = destination.parent_path().string();
503                             }
504
505                             auto relPath = bf::relative(bf::absolute(appDirPath) / "usr" / getLibraryDirName(path), bf::absolute(rpathDestination));
506                             rpath = "$ORIGIN/" + relPath.string();
507                         }
508
509                         setElfRPathOperations[destinationPath / path.filename()] = rpath;
510                         stripOperations.insert(destinationPath / path.filename());
511
512                         if (!deployElfDependencies(path))
513                             return false;
514
515                         return true;
516                     }
517
518                     bool deployDesktopFile(const DesktopFile& desktopFile) {
519                         if (hasBeenVisitedAlready(desktopFile.path())) {
520                             ldLog() << LD_DEBUG << "File has been visited already:" << desktopFile.path() << std::endl;
521                             return true;
522                         }
523
524                         if (!desktopFile.validate()) {
525                             ldLog() << LD_ERROR << "Failed to validate desktop file:" << desktopFile.path() << std::endl;
526                         }
527
528                         ldLog() << "Deploying desktop file" << desktopFile.path() << std::endl;
529
530                         deployFile(desktopFile.path(), appDirPath / "usr/share/applications/", DEFAULT_PERMS);
531
532                         return true;
533                     }
534
535                     bool deployIcon(const bf::path& path, const std::string targetFilename = "") {
536                         if (hasBeenVisitedAlready(path)) {
537                             ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
538                             return true;
539                         }
540
541                         ldLog() << "Deploying icon" << path << std::endl;
542
543                         std::string resolution;
544
545                         // if file is a vector image, use "scalable" directory
546                         if (util::strLower(path.filename().extension().string()) == ".svg") {
547                             resolution = "scalable";
548                         } else {
549                             try {
550                                 CImg<unsigned char> image(path.c_str());
551
552                                 auto xRes = image.width();
553                                 auto yRes = image.height();
554
555                                 if (xRes != yRes) {
556                                     ldLog() << LD_WARNING << "x and y resolution of icon are not equal:" << path;
557                                 }
558
559                                 resolution = std::to_string(xRes) + "x" + std::to_string(yRes);
560
561                                 // otherwise, test resolution against "known good" values, and reject invalid ones
562                                 const auto knownResolutions = {8, 16, 20, 22, 24, 28, 32, 36, 42, 48, 64, 72, 96, 128, 160, 192, 256, 384, 480, 512};
563
564                                 // assume invalid
565                                 bool invalidXRes = true, invalidYRes = true;
566
567                                 for (const auto res : knownResolutions) {
568                                     if (xRes == res)
569                                         invalidXRes = false;
570                                     if (yRes == res)
571                                         invalidYRes = false;
572                                 }
573
574                                 auto printIconHint = [&knownResolutions]() {
575                                     std::stringstream ss;
576                                     for (const auto res : knownResolutions) {
577                                         ss << res << "x" << res;
578
579                                         if (res != *(knownResolutions.end() - 1))
580                                             ss << ", ";
581                                     }
582
583                                     ldLog() << LD_ERROR << "Valid resolutions for icons are:" << ss.str() << std::endl;
584                                 };
585
586                                 if (invalidXRes) {
587                                     ldLog() << LD_ERROR << "Icon" << path << "has invalid x resolution:" << xRes << std::endl;
588                                     printIconHint();
589                                     return false;
590                                 }
591
592                                 if (invalidYRes) {
593                                     ldLog() << LD_ERROR << "Icon" << path << "has invalid y resolution:" << yRes << std::endl;
594                                     printIconHint();
595                                     return false;
596                                 }
597                             } catch (const CImgException& e) {
598                                 ldLog() << LD_ERROR << "CImg error: " << e.what() << std::endl;
599                                 return false;
600                             }
601                         }
602
603                         auto filename = path.filename().string();
604
605                         // if the user wants us to automatically rename icon files, we can do so
606                         // this is useful when passing multiple icons via -i in different resolutions
607                         if (!targetFilename.empty()) {
608                             auto newFilename = targetFilename + path.extension().string();
609                             if (newFilename != filename) {
610                                 ldLog() << LD_WARNING << "Changing name of icon" << path << "to target filename" << newFilename << std::endl;
611                                 filename = newFilename;
612                             }
613                         }
614
615                         deployFile(path, appDirPath / "usr/share/icons/hicolor" / resolution / "apps" / filename, DEFAULT_PERMS);
616                         deployCopyrightFiles(path);
617
618                         return true;
619                     }
620
621                     static bool isInDebugSymbolsLocation(const bf::path& path) {
622                         // TODO: check if there's more potential locations for debug symbol files
623                         for (const std::string& dbgSymbolsPrefix : {".debug/"}) {
624                             if (path.string().substr(0, dbgSymbolsPrefix.size()) == dbgSymbolsPrefix)
625                                 return true;
626                         }
627
628                         return false;
629                     }
630             };
631
632             AppDir::AppDir(const bf::path& path) {
633                 d = std::make_shared<PrivateData>();
634
635                 d->appDirPath = path;
636             }
637
638             AppDir::AppDir(const std::string& path) : AppDir(bf::path(path)) {}
639
640             bool AppDir::createBasicStructure() const {
641                 std::vector<std::string> dirPaths = {
642                     "usr/bin/",
643                     "usr/lib/",
644                     "usr/share/applications/",
645                     "usr/share/icons/hicolor/",
646                 };
647
648                 for (const std::string& resolution : {"16x16", "32x32", "64x64", "128x128", "256x256", "scalable"}) {
649                     auto iconPath = "usr/share/icons/hicolor/" + resolution + "/apps/";
650                     dirPaths.push_back(iconPath);
651                 }
652
653                 for (const auto& dirPath : dirPaths) {
654                     auto fullDirPath = d->appDirPath / dirPath;
655
656                     ldLog() << "Creating directory" << fullDirPath << std::endl;
657
658                     // skip directory if it exists
659                     if (bf::is_directory(fullDirPath))
660                         continue;
661
662                     try {
663                         bf::create_directories(fullDirPath);
664                     } catch (const bf::filesystem_error&) {
665                         ldLog() << LD_ERROR << "Failed to create directory" << fullDirPath;
666                         return false;
667                     }
668                 }
669
670                 return true;
671             }
672
673             bool AppDir::deployLibrary(const bf::path& path, const bf::path& destination) {
674                 return d->deployLibrary(path, false, true, destination);
675             }
676
677             bool AppDir::forceDeployLibrary(const bf::path& path, const bf::path& destination) {
678                 return d->deployLibrary(path, true, true, destination);
679             }
680
681             bool AppDir::deployExecutable(const bf::path& path, const boost::filesystem::path& destination) {
682                 return d->deployExecutable(path, destination);
683             }
684
685             bool AppDir::deployDesktopFile(const DesktopFile& desktopFile) {
686                 return d->deployDesktopFile(desktopFile);
687             }
688
689             bool AppDir::deployIcon(const bf::path& path) {
690                 return d->deployIcon(path);
691             }
692
693             bool AppDir::deployIcon(const bf::path& path, const std::string& targetFilename) {
694                 return d->deployIcon(path, targetFilename);
695             }
696
697             bool AppDir::executeDeferredOperations() {
698                 return d->executeDeferredOperations();
699             }
700
701             boost::filesystem::path AppDir::path() const {
702                 return d->appDirPath;
703             }
704
705             static std::vector<bf::path> listFilesInDirectory(const bf::path& path, const bool recursive = true) {
706                 std::vector<bf::path> foundPaths;
707
708                 // directory_iterators throw exceptions if the directory doesn't exist
709                 if (!bf::is_directory(path)) {
710                     ldLog() << LD_DEBUG << "No such directory:" << path << std::endl;
711                     return {};
712                 }
713
714                 if (recursive) {
715                     for (bf::recursive_directory_iterator i(path); i != bf::recursive_directory_iterator(); ++i) {
716                         if (bf::is_regular_file(*i)) {
717                             foundPaths.push_back((*i).path());
718                         }
719                     }
720                 } else {
721                     for (bf::directory_iterator i(path); i != bf::directory_iterator(); ++i) {
722                         if (bf::is_regular_file(*i)) {
723                             foundPaths.push_back((*i).path());
724                         }
725                     }
726                 }
727
728                 return foundPaths;
729             }
730
731             std::vector<bf::path> AppDir::deployedIconPaths() const {
732                 auto icons = listFilesInDirectory(path() / "usr/share/icons/");
733                 auto pixmaps = listFilesInDirectory(path() / "usr/share/pixmaps/", false);
734                 icons.reserve(pixmaps.size());
735                 std::copy(pixmaps.begin(), pixmaps.end(), std::back_inserter(icons));
736                 return icons;
737             }
738
739             std::vector<bf::path> AppDir::deployedExecutablePaths() const {
740                 return listFilesInDirectory(path() / "usr/bin/", false);
741             }
742
743             std::vector<DesktopFile> AppDir::deployedDesktopFiles() const {
744                 std::vector<DesktopFile> desktopFiles;
745
746                 auto paths = listFilesInDirectory(path() / "usr/share/applications/", false);
747                 paths.erase(std::remove_if(paths.begin(), paths.end(), [](const bf::path& path) {
748                     return path.extension() != ".desktop";
749                 }), paths.end());
750
751                 for (const auto& path : paths) {
752                     desktopFiles.emplace_back(path.string());
753                 }
754
755                 return desktopFiles;
756             }
757
758             bool AppDir::setUpAppDirRoot(const DesktopFile& desktopFile, boost::filesystem::path customAppRunPath) {
759                 AppDirRootSetup setup(*this);
760                 return setup.run(desktopFile, customAppRunPath);
761             }
762
763             bf::path AppDir::deployFile(const boost::filesystem::path& from, const boost::filesystem::path& to) {
764                 return d->deployFile(from, to, DEFAULT_PERMS, true);
765             }
766
767             bool AppDir::copyFile(const bf::path& from, const bf::path& to, bool overwrite) const {
768                 return d->copyFile(from, to, DEFAULT_PERMS, overwrite);
769             }
770
771             bool AppDir::createRelativeSymlink(const bf::path& target, const bf::path& symlink) const {
772                 return d->symlinkFile(target, symlink, true);
773             }
774
775             std::vector<bf::path> AppDir::listExecutables() const {
776                 std::vector<bf::path> executables;
777
778                 for (const auto& file : listFilesInDirectory(path() / "usr" / "bin", false)) {
779                     // make sure it's an ELF file
780                     try {
781                         elf_file::ElfFile elfFile(file);
782                     } catch (const elf_file::ElfFileParseError&) {
783                         // FIXME: remove this workaround once the MIME check below works as intended
784                         continue;
785                     }
786
787                     executables.push_back(file);
788                 }
789
790                 return executables;
791             }
792
793             std::vector<bf::path> AppDir::listSharedLibraries() const {
794                 std::vector<bf::path> sharedLibraries;
795
796                 for (const auto& file : listFilesInDirectory(path() / "usr" / "lib", true)) {
797                     // exclude debug symbols
798                     if (d->isInDebugSymbolsLocation(file))
799                         continue;
800
801                     // make sure it's an ELF file
802                     try {
803                         elf_file::ElfFile elfFile(file);
804                     } catch (const elf_file::ElfFileParseError&) {
805                         // FIXME: remove this workaround once the MIME check below works as intended
806                         continue;
807                     }
808
809                     sharedLibraries.push_back(file);
810                 }
811
812                 return sharedLibraries;
813             }
814
815             bool AppDir::deployDependenciesForExistingFiles() const {
816                 for (const auto& executable : listExecutables()) {
817                     if (bf::is_symlink(executable))
818                         continue;
819
820                     if (!d->deployElfDependencies(executable))
821                         return false;
822
823                     std::string rpath = "$ORIGIN/../" + PrivateData::getLibraryDirName(executable);
824
825                     d->setElfRPathOperations[executable] = rpath;
826                 }
827
828                 for (const auto& sharedLibrary : listSharedLibraries()) {
829                     if (bf::is_symlink(sharedLibrary))
830                         continue;
831
832                     if (!d->deployElfDependencies(sharedLibrary))
833                         return false;
834
835                     const auto rpath = elf_file::ElfFile(sharedLibrary).getRPath();
836                     auto rpathList = util::split(rpath, ':');
837                     if (std::find(rpathList.begin(), rpathList.end(), "$ORIGIN") == rpathList.end()) {
838                         rpathList.push_back("$ORIGIN");
839                         d->setElfRPathOperations[sharedLibrary] = util::join(rpathList, ":");
840                     } else {
841                         d->setElfRPathOperations[sharedLibrary] = rpath;
842                     }
843                 }
844
845                 // used to bundle dependencies of executables or libraries in the AppDir without moving them
846                 // useful e.g., for plugin systems, etc.
847                 {
848                     constexpr auto VAR_NAME = "ADDITIONAL_BIN_DIRS";
849
850                     const auto additionalBinDirs = getenv(VAR_NAME);
851
852                     if (additionalBinDirs != nullptr) {
853                         ldLog() << LD_DEBUG << "Read value of" << VAR_NAME << LD_NO_SPACE << ":" << additionalBinDirs << std::endl;
854
855                         auto additionalBinaryDirs = util::split(getenv(VAR_NAME));
856
857                         for (const auto& additionalBinaryDir : additionalBinaryDirs) {
858                             ldLog() << "Deploying additional executables in directory:" << additionalBinaryDir << std::endl;
859
860                             if (!bf::is_directory(additionalBinaryDir)) {
861                                 ldLog() << LD_ERROR << "Could not find additional binary dir, skipping:" << additionalBinaryDir;
862                             }
863
864                             for (bf::directory_iterator it(additionalBinaryDir); it != bf::directory_iterator(); ++it) {
865                                 const auto entry = *it;
866                                 const auto& path = entry.path();
867
868                                 // can't bundle directories
869                                 if (!bf::is_regular_file(entry)) {
870                                     ldLog() << LD_DEBUG << "Skipping non-file directory entry:" << entry.path() << std::endl;
871                                     continue;
872                                 }
873
874                                 // make sure we have an ELF file
875                                 try {
876                                     elf_file::ElfFile(entry.path().string());
877                                 } catch (const elf_file::ElfFileParseError& e) {
878                                     ldLog() << LD_DEBUG << "Skipping non-ELF directory entry:" << entry.path() << std::endl;
879                                 }
880
881                                 ldLog() << "Deploying additional executable:" << entry.path().string() << std::endl;
882
883                                 // bundle dependencies
884                                 if (!d->deployElfDependencies(path))
885                                     return false;
886
887                                 // set rpath correctly
888                                 const auto rpathDestination = this->path() / "usr/lib";
889
890                                 const auto rpath = PrivateData::calculateRelativeRPath(additionalBinaryDir, rpathDestination);
891                                 ldLog() << LD_DEBUG << "Calculated rpath:" << rpath << std::endl;
892
893                                 d->setElfRPathOperations[path] = rpath;
894                             }
895                         }
896                     }
897                 }
898
899                 return true;
900             }
901
902             // TODO: quite similar to deployDependenciesForExistingFiles... maybe they should be merged or use each other
903             bool AppDir::deployDependenciesOnlyForElfFile(const boost::filesystem::path& elfFilePath, bool failSilentForNonElfFile) {
904                 // preconditions: file must be an ELF one, and file must be contained in the AppDir
905                 const auto canonicalElfFilePath = bf::canonical(elfFilePath);
906
907                 // can't bundle directories
908                 if (!bf::is_regular_file(canonicalElfFilePath)) {
909                     ldLog() << LD_DEBUG << "Skipping non-file directory entry:" << canonicalElfFilePath << std::endl;
910                     return false;
911                 }
912
913                 // to do a proper prefix check, we need a proper absolute canonical path for the AppDir
914                 const auto canonicalAppDirPath = bf::canonical(this->path());
915                 ldLog() << LD_DEBUG << "absolute canonical AppDir path:" << canonicalAppDirPath << std::endl;
916
917                 // a fancy way to check STL strings for prefixes is to "ab"use rfind
918                 if (canonicalElfFilePath.string().rfind(canonicalAppDirPath.string()) != 0) {
919                     ldLog() << LD_ERROR << "File" << canonicalElfFilePath << "is not contained in AppDir, its dependencies cannot be deployed into the AppDir" << std::endl;
920                     return false;
921                 }
922
923                 // make sure we have an ELF file
924                 try {
925                     elf_file::ElfFile(canonicalElfFilePath.string());
926                 } catch (const elf_file::ElfFileParseError& e) {
927                     auto level = LD_ERROR;
928
929                     if (failSilentForNonElfFile) {
930                         level = LD_WARNING;
931                     }
932
933                     ldLog() << level << "Not an ELF file:" << canonicalElfFilePath << std::endl;
934
935                     return failSilentForNonElfFile;
936                 }
937
938                 // relative path makes for a nicer and more consistent log
939                 ldLog() << "Deploying dependencies for ELF file in AppDir:" << elfFilePath << std::endl;
940
941                 // bundle dependencies
942                 if (!d->deployElfDependencies(canonicalElfFilePath))
943                     return false;
944
945                 // set rpath correctly
946                 const auto rpathDestination = this->path() / "usr/lib";
947                 ldLog() << LD_DEBUG << "rpath destination:" << rpathDestination << std::endl;
948
949                 const auto rpath = PrivateData::calculateRelativeRPath(elfFilePath.parent_path(), rpathDestination);
950                 ldLog() << LD_DEBUG << "Calculated rpath:" << rpath << std::endl;
951
952                 d->setElfRPathOperations[canonicalElfFilePath] = rpath;
953
954                 return true;
955             }
956
957             void AppDir::setDisableCopyrightFilesDeployment(bool disable) {
958                 d->disableCopyrightFilesDeployment = disable;
959             }
960         }
961     }
962 }