1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
17 /** \file
18  * \ingroup bke
19  *
20  * Access to application level directories.
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "BLI_fileops.h"
28 #include "BLI_fileops_types.h"
29 #include "BLI_listbase.h"
30 #include "BLI_path_util.h"
31 #include "BLI_string.h"
32 #include "BLI_string_utf8.h"
33 #include "BLI_string_utils.h"
34 #include "BLI_utildefines.h"
35 
36 #include "BKE_appdir.h" /* own include */
37 #include "BKE_blender_version.h"
38 
39 #include "GHOST_Path-api.h"
40 
41 #include "MEM_guardedalloc.h"
42 
43 #include "CLG_log.h"
44 
45 #ifdef WIN32
46 #  include "utf_winfunc.h"
47 #  include "utfconv.h"
48 #  include <io.h>
49 #  ifdef _WIN32_IE
50 #    undef _WIN32_IE
51 #  endif
52 #  define _WIN32_IE 0x0501
53 #  include "BLI_winstuff.h"
54 #  include <shlobj.h>
55 #  include <windows.h>
56 #else /* non windows */
57 #  ifdef WITH_BINRELOC
58 #    include "binreloc.h"
59 #  endif
60 /* #mkdtemp on OSX (and probably all *BSD?), not worth making specific check for this OS. */
61 #  include <unistd.h>
62 #endif /* WIN32 */
63 
64 static const char _str_null[] = "(null)";
65 #define STR_OR_FALLBACK(a) ((a) ? (a) : _str_null)
66 
67 /* -------------------------------------------------------------------- */
68 /** \name Local Variables
69  * \{ */
70 
71 /* local */
72 static CLG_LogRef LOG = {"bke.appdir"};
73 
74 static struct {
75   /** Full path to program executable. */
76   char program_filename[FILE_MAX];
77   /** Full path to directory in which executable is located. */
78   char program_dirname[FILE_MAX];
79   /** Persistent temporary directory (defined by the preferences or OS). */
80   char temp_dirname_base[FILE_MAX];
81   /** Volatile temporary directory (owned by Blender, removed on exit). */
82   char temp_dirname_session[FILE_MAX];
83 } g_app = {
84     .temp_dirname_session = "",
85 };
86 
87 /** \} */
88 
89 /* -------------------------------------------------------------------- */
90 /** \name Initialization
91  * \{ */
92 
93 #ifndef NDEBUG
94 static bool is_appdir_init = false;
95 #  define ASSERT_IS_INIT() BLI_assert(is_appdir_init)
96 #else
97 #  define ASSERT_IS_INIT() ((void)0)
98 #endif
99 
100 /**
101  * Sanity check to ensure correct API use in debug mode.
102  *
103  * Run this once the first level of arguments has been passed so we can be sure
104  * `--env-system-datafiles`, and other `--env-*` arguments has been passed.
105  *
106  * Without this any callers to this module that run early on,
107  * will miss out on changes from parsing arguments.
108  */
BKE_appdir_init(void)109 void BKE_appdir_init(void)
110 {
111 #ifndef NDEBUG
112   BLI_assert(is_appdir_init == false);
113   is_appdir_init = true;
114 #endif
115 }
116 
117 /** \} */
118 
119 /* -------------------------------------------------------------------- */
120 /** \name Internal Utilities
121  * \{ */
122 
123 /**
124  * \returns a formatted representation of the specified version number. Non-re-entrant!
125  */
blender_version_decimal(const int version)126 static char *blender_version_decimal(const int version)
127 {
128   static char version_str[5];
129   BLI_assert(version < 1000);
130   BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", version / 100, version % 100);
131   return version_str;
132 }
133 
134 /** \} */
135 
136 /* -------------------------------------------------------------------- */
137 /** \name Default Directories
138  * \{ */
139 
140 /**
141  * This is now only used to really get the user's default document folder.
142  *
143  * \note On Windows `Users/{MyUserName}/Documents` is used
144  * as it's the default location to save documents.
145  */
BKE_appdir_folder_default(void)146 const char *BKE_appdir_folder_default(void)
147 {
148 #ifndef WIN32
149   const char *const xdg_documents_dir = BLI_getenv("XDG_DOCUMENTS_DIR");
150 
151   if (xdg_documents_dir) {
152     return xdg_documents_dir;
153   }
154 
155   return BLI_getenv("HOME");
156 #else  /* Windows */
157   static char documentfolder[MAXPATHLEN];
158   HRESULT hResult;
159 
160   /* Check for `%HOME%` environment variable. */
161   if (uput_getenv("HOME", documentfolder, MAXPATHLEN)) {
162     if (BLI_is_dir(documentfolder)) {
163       return documentfolder;
164     }
165   }
166 
167   /* Add user profile support for WIN 2K / NT.
168    * This is `%APPDATA%`, which translates to either:
169    * - `%USERPROFILE%\Application Data` or...
170    * - `%USERPROFILE%\AppData\Roaming` (since Vista).
171    */
172   hResult = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentfolder);
173 
174   if (hResult == S_OK) {
175     if (BLI_is_dir(documentfolder)) {
176       return documentfolder;
177     }
178   }
179 
180   return NULL;
181 #endif /* WIN32 */
182 }
183 
184 /**
185  * Gets a good default directory for fonts.
186  */
BKE_appdir_font_folder_default(char * dir)187 bool BKE_appdir_font_folder_default(
188     /* This parameter can only be `const` on non-windows platforms.
189      * NOLINTNEXTLINE: readability-non-const-parameter. */
190     char *dir)
191 {
192   bool success = false;
193 #ifdef WIN32
194   wchar_t wpath[FILE_MAXDIR];
195   success = SHGetSpecialFolderPathW(0, wpath, CSIDL_FONTS, 0);
196   if (success) {
197     wcscat(wpath, L"\\");
198     BLI_strncpy_wchar_as_utf8(dir, wpath, FILE_MAXDIR);
199   }
200 #endif
201   /* TODO: Values for other platforms. */
202   UNUSED_VARS(dir);
203   return success;
204 }
205 
206 /** \} */
207 
208 /* -------------------------------------------------------------------- */
209 /** \name Path Presets (Internal Helpers)
210  * \{ */
211 
212 /**
213  * Concatenates paths into \a targetpath,
214  * returning true if result points to a directory.
215  *
216  * \param path_base: Path base, never NULL.
217  * \param folder_name: First sub-directory (optional).
218  * \param subfolder_name: Second sub-directory (optional).
219  * \param check_is_dir: When false, return true even if the path doesn't exist.
220  *
221  * \note The names for optional paths only follow other usage in this file,
222  * the names don't matter for this function.
223  *
224  * \note If it's useful we could take an arbitrary number of paths.
225  * For now usage is limited and we don't need this.
226  */
test_path(char * targetpath,size_t targetpath_len,const bool check_is_dir,const char * path_base,const char * folder_name,const char * subfolder_name)227 static bool test_path(char *targetpath,
228                       size_t targetpath_len,
229                       const bool check_is_dir,
230                       const char *path_base,
231                       const char *folder_name,
232                       const char *subfolder_name)
233 {
234   ASSERT_IS_INIT();
235 
236   /* Only the last argument should be NULL. */
237   BLI_assert(!(folder_name == NULL && (subfolder_name != NULL)));
238   BLI_path_join(targetpath, targetpath_len, path_base, folder_name, subfolder_name, NULL);
239   if (check_is_dir == false) {
240     CLOG_INFO(&LOG, 3, "using without test: '%s'", targetpath);
241     return true;
242   }
243 
244   if (BLI_is_dir(targetpath)) {
245     CLOG_INFO(&LOG, 3, "found '%s'", targetpath);
246     return true;
247   }
248 
249   CLOG_INFO(&LOG, 3, "missing '%s'", targetpath);
250 
251   /* Path not found, don't accidentally use it,
252    * otherwise call this function with `check_is_dir` set to false. */
253   targetpath[0] = '\0';
254   return false;
255 }
256 
257 /**
258  * Puts the value of the specified environment variable into \a path if it exists.
259  *
260  * \param check_is_dir: When true, checks if it points at a directory.
261  *
262  * \returns true when the value of the environment variable is stored
263  * at the address \a path points to.
264  */
test_env_path(char * path,const char * envvar,const bool check_is_dir)265 static bool test_env_path(char *path, const char *envvar, const bool check_is_dir)
266 {
267   ASSERT_IS_INIT();
268 
269   const char *env_path = envvar ? BLI_getenv(envvar) : NULL;
270   if (!env_path) {
271     return false;
272   }
273 
274   BLI_strncpy(path, env_path, FILE_MAX);
275 
276   if (check_is_dir == false) {
277     CLOG_INFO(&LOG, 3, "using env '%s' without test: '%s'", envvar, env_path);
278     return true;
279   }
280 
281   if (BLI_is_dir(env_path)) {
282     CLOG_INFO(&LOG, 3, "env '%s' found: %s", envvar, env_path);
283     return true;
284   }
285 
286   CLOG_INFO(&LOG, 3, "env '%s' missing: %s", envvar, env_path);
287 
288   /* Path not found, don't accidentally use it,
289    * otherwise call this function with `check_is_dir` set to false. */
290   path[0] = '\0';
291   return false;
292 }
293 
294 /**
295  * Constructs in \a targetpath the name of a directory relative to a version-specific
296  * sub-directory in the parent directory of the Blender executable.
297  *
298  * \param targetpath: String to return path.
299  * \param folder_name: Optional folder name within version-specific directory.
300  * \param subfolder_name: Optional sub-folder name within folder_name.
301  *
302  * \param version: To construct name of version-specific directory within #g_app.program_dirname.
303  * \param check_is_dir: When false, return true even if the path doesn't exist.
304  * \return true if such a directory exists.
305  */
get_path_local_ex(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name,const int version,const bool check_is_dir)306 static bool get_path_local_ex(char *targetpath,
307                               size_t targetpath_len,
308                               const char *folder_name,
309                               const char *subfolder_name,
310                               const int version,
311                               const bool check_is_dir)
312 {
313   char relfolder[FILE_MAX];
314 
315   CLOG_INFO(&LOG,
316             3,
317             "folder='%s', subfolder='%s'",
318             STR_OR_FALLBACK(folder_name),
319             STR_OR_FALLBACK(subfolder_name));
320 
321   if (folder_name) { /* `subfolder_name` may be NULL. */
322     BLI_path_join(relfolder, sizeof(relfolder), folder_name, subfolder_name, NULL);
323   }
324   else {
325     relfolder[0] = '\0';
326   }
327 
328   /* Try `{g_app.program_dirname}/2.xx/{folder_name}` the default directory
329    * for a portable distribution. See `WITH_INSTALL_PORTABLE` build-option. */
330   const char *path_base = g_app.program_dirname;
331 #ifdef __APPLE__
332   /* Due new code-sign situation in OSX > 10.9.5
333    * we must move the blender_version dir with contents to Resources. */
334   char osx_resourses[FILE_MAX];
335   BLI_snprintf(osx_resourses, sizeof(osx_resourses), "%s../Resources", g_app.program_dirname);
336   /* Remove the '/../' added above. */
337   BLI_path_normalize(NULL, osx_resourses);
338   path_base = osx_resourses;
339 #endif
340   return test_path(targetpath,
341                    targetpath_len,
342                    check_is_dir,
343                    path_base,
344                    blender_version_decimal(version),
345                    relfolder);
346 }
get_path_local(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name)347 static bool get_path_local(char *targetpath,
348                            size_t targetpath_len,
349                            const char *folder_name,
350                            const char *subfolder_name)
351 {
352   const int version = BLENDER_VERSION;
353   const bool check_is_dir = true;
354   return get_path_local_ex(
355       targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
356 }
357 
358 /**
359  * Check if this is an install with user files kept together
360  * with the Blender executable and its installation files.
361  */
BKE_appdir_app_is_portable_install(void)362 bool BKE_appdir_app_is_portable_install(void)
363 {
364   /* Detect portable install by the existence of `config` folder. */
365   char path[FILE_MAX];
366   return get_path_local(path, sizeof(path), "config", NULL);
367 }
368 
369 /**
370  * Returns the path of a folder from environment variables.
371  *
372  * \param targetpath: String to return path.
373  * \param subfolder_name: optional name of sub-folder within folder.
374  * \param envvar: name of environment variable to check folder_name.
375  * \param check_is_dir: When false, return true even if the path doesn't exist.
376  * \return true if it was able to construct such a path and the path exists.
377  */
get_path_environment_ex(char * targetpath,size_t targetpath_len,const char * subfolder_name,const char * envvar,const bool check_is_dir)378 static bool get_path_environment_ex(char *targetpath,
379                                     size_t targetpath_len,
380                                     const char *subfolder_name,
381                                     const char *envvar,
382                                     const bool check_is_dir)
383 {
384   char user_path[FILE_MAX];
385 
386   if (test_env_path(user_path, envvar, check_is_dir)) {
387     /* Note that `subfolder_name` may be NULL, in this case we use `user_path` as-is. */
388     return test_path(targetpath, targetpath_len, check_is_dir, user_path, subfolder_name, NULL);
389   }
390   return false;
391 }
get_path_environment(char * targetpath,size_t targetpath_len,const char * subfolder_name,const char * envvar)392 static bool get_path_environment(char *targetpath,
393                                  size_t targetpath_len,
394                                  const char *subfolder_name,
395                                  const char *envvar)
396 {
397   const bool check_is_dir = true;
398   return get_path_environment_ex(targetpath, targetpath_len, subfolder_name, envvar, check_is_dir);
399 }
400 
401 /**
402  * Returns the path of a folder within the user-files area.
403  *
404  * \param targetpath: String to return path.
405  * \param folder_name: default name of folder within user area.
406  * \param subfolder_name: optional name of sub-folder within folder.
407  * \param version: Blender version, used to construct a sub-directory name.
408  * \param check_is_dir: When false, return true even if the path doesn't exist.
409  * \return true if it was able to construct such a path.
410  */
get_path_user_ex(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name,const int version,const bool check_is_dir)411 static bool get_path_user_ex(char *targetpath,
412                              size_t targetpath_len,
413                              const char *folder_name,
414                              const char *subfolder_name,
415                              const int version,
416                              const bool check_is_dir)
417 {
418   char user_path[FILE_MAX];
419   const char *user_base_path;
420 
421   /* for portable install, user path is always local */
422   if (BKE_appdir_app_is_portable_install()) {
423     return get_path_local_ex(
424         targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
425   }
426   user_path[0] = '\0';
427 
428   user_base_path = (const char *)GHOST_getUserDir(version, blender_version_decimal(version));
429   if (user_base_path) {
430     BLI_strncpy(user_path, user_base_path, FILE_MAX);
431   }
432 
433   if (!user_path[0]) {
434     return false;
435   }
436 
437   CLOG_INFO(&LOG,
438             3,
439             "'%s', folder='%s', subfolder='%s'",
440             user_path,
441             STR_OR_FALLBACK(folder_name),
442             STR_OR_FALLBACK(subfolder_name));
443 
444   /* `subfolder_name` may be NULL. */
445   return test_path(
446       targetpath, targetpath_len, check_is_dir, user_path, folder_name, subfolder_name);
447 }
get_path_user(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name)448 static bool get_path_user(char *targetpath,
449                           size_t targetpath_len,
450                           const char *folder_name,
451                           const char *subfolder_name)
452 {
453   const int version = BLENDER_VERSION;
454   const bool check_is_dir = true;
455   return get_path_user_ex(
456       targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
457 }
458 
459 /**
460  * Returns the path of a folder within the Blender installation directory.
461  *
462  * \param targetpath: String to return path.
463  * \param folder_name: default name of folder within installation area.
464  * \param subfolder_name: optional name of sub-folder within folder.
465  * \param version: Blender version, used to construct a sub-directory name.
466  * \param check_is_dir: When false, return true even if the path doesn't exist.
467  * \return true if it was able to construct such a path.
468  */
get_path_system_ex(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name,const int version,const bool check_is_dir)469 static bool get_path_system_ex(char *targetpath,
470                                size_t targetpath_len,
471                                const char *folder_name,
472                                const char *subfolder_name,
473                                const int version,
474                                const bool check_is_dir)
475 {
476   char system_path[FILE_MAX];
477   const char *system_base_path;
478   char relfolder[FILE_MAX];
479 
480   if (folder_name) { /* `subfolder_name` may be NULL. */
481     BLI_path_join(relfolder, sizeof(relfolder), folder_name, subfolder_name, NULL);
482   }
483   else {
484     relfolder[0] = '\0';
485   }
486 
487   system_path[0] = '\0';
488   system_base_path = (const char *)GHOST_getSystemDir(version, blender_version_decimal(version));
489   if (system_base_path) {
490     BLI_strncpy(system_path, system_base_path, FILE_MAX);
491   }
492 
493   if (!system_path[0]) {
494     return false;
495   }
496 
497   CLOG_INFO(&LOG,
498             3,
499             "'%s', folder='%s', subfolder='%s'",
500             system_path,
501             STR_OR_FALLBACK(folder_name),
502             STR_OR_FALLBACK(subfolder_name));
503 
504   /* Try `$BLENDERPATH/folder_name/subfolder_name`, `subfolder_name` may be NULL. */
505   return test_path(
506       targetpath, targetpath_len, check_is_dir, system_path, folder_name, subfolder_name);
507 }
508 
get_path_system(char * targetpath,size_t targetpath_len,const char * folder_name,const char * subfolder_name)509 static bool get_path_system(char *targetpath,
510                             size_t targetpath_len,
511                             const char *folder_name,
512                             const char *subfolder_name)
513 {
514   const int version = BLENDER_VERSION;
515   const bool check_is_dir = true;
516   return get_path_system_ex(
517       targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir);
518 }
519 
520 /** \} */
521 
522 /* -------------------------------------------------------------------- */
523 /** \name Path Presets API
524  * \{ */
525 
526 /**
527  * Get a folder out of the \a folder_id presets for paths.
528  *
529  * \param subfolder: The name of a directory to check for,
530  * this may contain path separators but must resolve to a directory, checked with #BLI_is_dir.
531  * \return The path if found, NULL string if not.
532  */
BKE_appdir_folder_id_ex(const int folder_id,const char * subfolder,char * path,size_t path_len)533 bool BKE_appdir_folder_id_ex(const int folder_id,
534                              const char *subfolder,
535                              char *path,
536                              size_t path_len)
537 {
538   switch (folder_id) {
539     case BLENDER_DATAFILES: /* general case */
540       if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) {
541         break;
542       }
543       if (get_path_user(path, path_len, "datafiles", subfolder)) {
544         break;
545       }
546       if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
547         break;
548       }
549       if (get_path_local(path, path_len, "datafiles", subfolder)) {
550         break;
551       }
552       if (get_path_system(path, path_len, "datafiles", subfolder)) {
553         break;
554       }
555       return false;
556 
557     case BLENDER_USER_DATAFILES:
558       if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) {
559         break;
560       }
561       if (get_path_user(path, path_len, "datafiles", subfolder)) {
562         break;
563       }
564       return false;
565 
566     case BLENDER_SYSTEM_DATAFILES:
567       if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
568         break;
569       }
570       if (get_path_system(path, path_len, "datafiles", subfolder)) {
571         break;
572       }
573       if (get_path_local(path, path_len, "datafiles", subfolder)) {
574         break;
575       }
576       return false;
577 
578     case BLENDER_USER_AUTOSAVE:
579       if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) {
580         break;
581       }
582       if (get_path_user(path, path_len, "autosave", subfolder)) {
583         break;
584       }
585       return false;
586 
587     case BLENDER_USER_CONFIG:
588       if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_CONFIG")) {
589         break;
590       }
591       if (get_path_user(path, path_len, "config", subfolder)) {
592         break;
593       }
594       return false;
595 
596     case BLENDER_USER_SCRIPTS:
597       if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_SCRIPTS")) {
598         break;
599       }
600       if (get_path_user(path, path_len, "scripts", subfolder)) {
601         break;
602       }
603       return false;
604 
605     case BLENDER_SYSTEM_SCRIPTS:
606       if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_SCRIPTS")) {
607         break;
608       }
609       if (get_path_system(path, path_len, "scripts", subfolder)) {
610         break;
611       }
612       if (get_path_local(path, path_len, "scripts", subfolder)) {
613         break;
614       }
615       return false;
616 
617     case BLENDER_SYSTEM_PYTHON:
618       if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_PYTHON")) {
619         break;
620       }
621       if (get_path_system(path, path_len, "python", subfolder)) {
622         break;
623       }
624       if (get_path_local(path, path_len, "python", subfolder)) {
625         break;
626       }
627       return false;
628 
629     default:
630       BLI_assert(0);
631       break;
632   }
633 
634   return true;
635 }
636 
BKE_appdir_folder_id(const int folder_id,const char * subfolder)637 const char *BKE_appdir_folder_id(const int folder_id, const char *subfolder)
638 {
639   static char path[FILE_MAX] = "";
640   if (BKE_appdir_folder_id_ex(folder_id, subfolder, path, sizeof(path))) {
641     return path;
642   }
643   return NULL;
644 }
645 
646 /**
647  * Returns the path to a folder in the user area without checking that it actually exists first.
648  */
BKE_appdir_folder_id_user_notest(const int folder_id,const char * subfolder)649 const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder)
650 {
651   const int version = BLENDER_VERSION;
652   static char path[FILE_MAX] = "";
653   const bool check_is_dir = false;
654 
655   switch (folder_id) {
656     case BLENDER_USER_DATAFILES:
657       if (get_path_environment_ex(
658               path, sizeof(path), subfolder, "BLENDER_USER_DATAFILES", check_is_dir)) {
659         break;
660       }
661       get_path_user_ex(path, sizeof(path), "datafiles", subfolder, version, check_is_dir);
662       break;
663     case BLENDER_USER_CONFIG:
664       if (get_path_environment_ex(
665               path, sizeof(path), subfolder, "BLENDER_USER_CONFIG", check_is_dir)) {
666         break;
667       }
668       get_path_user_ex(path, sizeof(path), "config", subfolder, version, check_is_dir);
669       break;
670     case BLENDER_USER_AUTOSAVE:
671       if (get_path_environment_ex(
672               path, sizeof(path), subfolder, "BLENDER_USER_AUTOSAVE", check_is_dir)) {
673         break;
674       }
675       get_path_user_ex(path, sizeof(path), "autosave", subfolder, version, check_is_dir);
676       break;
677     case BLENDER_USER_SCRIPTS:
678       if (get_path_environment_ex(
679               path, sizeof(path), subfolder, "BLENDER_USER_SCRIPTS", check_is_dir)) {
680         break;
681       }
682       get_path_user_ex(path, sizeof(path), "scripts", subfolder, version, check_is_dir);
683       break;
684     default:
685       BLI_assert(0);
686       break;
687   }
688 
689   if ('\0' == path[0]) {
690     return NULL;
691   }
692   return path;
693 }
694 
695 /**
696  * Returns the path to a folder in the user area, creating it if it doesn't exist.
697  */
BKE_appdir_folder_id_create(const int folder_id,const char * subfolder)698 const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
699 {
700   const char *path;
701 
702   /* Only for user folders. */
703   if (!ELEM(folder_id,
704             BLENDER_USER_DATAFILES,
705             BLENDER_USER_CONFIG,
706             BLENDER_USER_SCRIPTS,
707             BLENDER_USER_AUTOSAVE)) {
708     return NULL;
709   }
710 
711   path = BKE_appdir_folder_id(folder_id, subfolder);
712 
713   if (!path) {
714     path = BKE_appdir_folder_id_user_notest(folder_id, subfolder);
715     if (path) {
716       BLI_dir_create_recursive(path);
717     }
718   }
719 
720   return path;
721 }
722 
723 /**
724  * Returns the path of the top-level version-specific local, user or system directory.
725  * If check_is_dir, then the result will be NULL if the directory doesn't exist.
726  */
BKE_appdir_folder_id_version(const int folder_id,const int version,const bool check_is_dir)727 const char *BKE_appdir_folder_id_version(const int folder_id,
728                                          const int version,
729                                          const bool check_is_dir)
730 {
731   static char path[FILE_MAX] = "";
732   bool ok;
733   switch (folder_id) {
734     case BLENDER_RESOURCE_PATH_USER:
735       ok = get_path_user_ex(path, sizeof(path), NULL, NULL, version, check_is_dir);
736       break;
737     case BLENDER_RESOURCE_PATH_LOCAL:
738       ok = get_path_local_ex(path, sizeof(path), NULL, NULL, version, check_is_dir);
739       break;
740     case BLENDER_RESOURCE_PATH_SYSTEM:
741       ok = get_path_system_ex(path, sizeof(path), NULL, NULL, version, check_is_dir);
742       break;
743     default:
744       path[0] = '\0'; /* in case check_is_dir is false */
745       ok = false;
746       BLI_assert(!"incorrect ID");
747       break;
748   }
749   return ok ? path : NULL;
750 }
751 
752 /** \} */
753 
754 /* -------------------------------------------------------------------- */
755 /** \name Program Path Queries
756  *
757  * Access locations of Blender & Python.
758  * \{ */
759 
760 /**
761  * Checks if name is a fully qualified filename to an executable.
762  * If not it searches `$PATH` for the file. On Windows it also
763  * adds the correct extension (`.com` `.exe` etc) from
764  * `$PATHEXT` if necessary. Also on Windows it translates
765  * the name to its 8.3 version to prevent problems with
766  * spaces and stuff. Final result is returned in \a fullname.
767  *
768  * \param fullname: The full path and full name of the executable
769  * (must be #FILE_MAX minimum)
770  * \param name: The name of the executable (usually `argv[0]`) to be checked
771  */
where_am_i(char * fullname,const size_t maxlen,const char * name)772 static void where_am_i(char *fullname, const size_t maxlen, const char *name)
773 {
774 #ifdef WITH_BINRELOC
775   /* Linux uses `binreloc` since `argv[0]` is not reliable, call `br_init(NULL)` first. */
776   {
777     const char *path = NULL;
778     path = br_find_exe(NULL);
779     if (path) {
780       BLI_strncpy(fullname, path, maxlen);
781       free((void *)path);
782       return;
783     }
784   }
785 #endif
786 
787 #ifdef _WIN32
788   {
789     wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath");
790     if (GetModuleFileNameW(0, fullname_16, maxlen)) {
791       conv_utf_16_to_8(fullname_16, fullname, maxlen);
792       if (!BLI_exists(fullname)) {
793         CLOG_ERROR(&LOG, "path can't be found: \"%.*s\"", (int)maxlen, fullname);
794         MessageBox(
795             NULL, "path contains invalid characters or is too long (see console)", "Error", MB_OK);
796       }
797       MEM_freeN(fullname_16);
798       return;
799     }
800 
801     MEM_freeN(fullname_16);
802   }
803 #endif
804 
805   /* Unix and non Linux. */
806   if (name && name[0]) {
807 
808     BLI_strncpy(fullname, name, maxlen);
809     if (name[0] == '.') {
810       BLI_path_abs_from_cwd(fullname, maxlen);
811 #ifdef _WIN32
812       BLI_path_program_extensions_add_win32(fullname, maxlen);
813 #endif
814     }
815     else if (BLI_path_slash_rfind(name)) {
816       /* Full path. */
817       BLI_strncpy(fullname, name, maxlen);
818 #ifdef _WIN32
819       BLI_path_program_extensions_add_win32(fullname, maxlen);
820 #endif
821     }
822     else {
823       BLI_path_program_search(fullname, maxlen, name);
824     }
825     /* Remove "/./" and "/../" so string comparisons can be used on the path. */
826     BLI_path_normalize(NULL, fullname);
827 
828 #if defined(DEBUG)
829     if (!STREQ(name, fullname)) {
830       CLOG_INFO(&LOG, 2, "guessing '%s' == '%s'", name, fullname);
831     }
832 #endif
833   }
834 }
835 
BKE_appdir_program_path_init(const char * argv0)836 void BKE_appdir_program_path_init(const char *argv0)
837 {
838   where_am_i(g_app.program_filename, sizeof(g_app.program_filename), argv0);
839   BLI_split_dir_part(g_app.program_filename, g_app.program_dirname, sizeof(g_app.program_dirname));
840 }
841 
842 /**
843  * Path to executable
844  */
BKE_appdir_program_path(void)845 const char *BKE_appdir_program_path(void)
846 {
847   BLI_assert(g_app.program_filename[0]);
848   return g_app.program_filename;
849 }
850 
851 /**
852  * Path to directory of executable
853  */
BKE_appdir_program_dir(void)854 const char *BKE_appdir_program_dir(void)
855 {
856   BLI_assert(g_app.program_dirname[0]);
857   return g_app.program_dirname;
858 }
859 
BKE_appdir_program_python_search(char * fullpath,const size_t fullpath_len,const int version_major,const int version_minor)860 bool BKE_appdir_program_python_search(char *fullpath,
861                                       const size_t fullpath_len,
862                                       const int version_major,
863                                       const int version_minor)
864 {
865   ASSERT_IS_INIT();
866 
867 #ifdef PYTHON_EXECUTABLE_NAME
868   /* Passed in from the build-systems 'PYTHON_EXECUTABLE'. */
869   const char *python_build_def = STRINGIFY(PYTHON_EXECUTABLE_NAME);
870 #endif
871   const char *basename = "python";
872   char python_version[16];
873   /* Check both possible names. */
874   const char *python_names[] = {
875 #ifdef PYTHON_EXECUTABLE_NAME
876       python_build_def,
877 #endif
878       python_version,
879       basename,
880   };
881   bool is_found = false;
882 
883   SNPRINTF(python_version, "%s%d.%d", basename, version_major, version_minor);
884 
885   {
886     const char *python_bin_dir = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, "bin");
887     if (python_bin_dir) {
888 
889       for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
890         BLI_join_dirfile(fullpath, fullpath_len, python_bin_dir, python_names[i]);
891 
892         if (
893 #ifdef _WIN32
894             BLI_path_program_extensions_add_win32(fullpath, fullpath_len)
895 #else
896             BLI_exists(fullpath)
897 #endif
898         ) {
899           is_found = true;
900           break;
901         }
902       }
903     }
904   }
905 
906   if (is_found == false) {
907     for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
908       if (BLI_path_program_search(fullpath, fullpath_len, python_names[i])) {
909         is_found = true;
910         break;
911       }
912     }
913   }
914 
915   if (is_found == false) {
916     *fullpath = '\0';
917   }
918 
919   return is_found;
920 }
921 
922 /** \} */
923 
924 /* -------------------------------------------------------------------- */
925 /** \name Application Templates
926  * \{ */
927 
928 /** Keep in sync with `bpy.utils.app_template_paths()` */
929 static const char *app_template_directory_search[2] = {
930     "startup" SEP_STR "bl_app_templates_user",
931     "startup" SEP_STR "bl_app_templates_system",
932 };
933 
934 static const int app_template_directory_id[2] = {
935     /* Only 'USER' */
936     BLENDER_USER_SCRIPTS,
937     /* Covers 'LOCAL' & 'SYSTEM'. */
938     BLENDER_SYSTEM_SCRIPTS,
939 };
940 
941 /**
942  * Return true if templates exist
943  */
BKE_appdir_app_template_any(void)944 bool BKE_appdir_app_template_any(void)
945 {
946   char temp_dir[FILE_MAX];
947   for (int i = 0; i < 2; i++) {
948     if (BKE_appdir_folder_id_ex(app_template_directory_id[i],
949                                 app_template_directory_search[i],
950                                 temp_dir,
951                                 sizeof(temp_dir))) {
952       return true;
953     }
954   }
955   return false;
956 }
957 
BKE_appdir_app_template_id_search(const char * app_template,char * path,size_t path_len)958 bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len)
959 {
960   for (int i = 0; i < 2; i++) {
961     char subdir[FILE_MAX];
962     BLI_join_dirfile(subdir, sizeof(subdir), app_template_directory_search[i], app_template);
963     if (BKE_appdir_folder_id_ex(app_template_directory_id[i], subdir, path, path_len)) {
964       return true;
965     }
966   }
967   return false;
968 }
969 
BKE_appdir_app_template_has_userpref(const char * app_template)970 bool BKE_appdir_app_template_has_userpref(const char *app_template)
971 {
972   /* Test if app template provides a `userpref.blend`.
973    * If not, we will share user preferences with the rest of Blender. */
974   if (!app_template && app_template[0]) {
975     return false;
976   }
977 
978   char app_template_path[FILE_MAX];
979   if (!BKE_appdir_app_template_id_search(
980           app_template, app_template_path, sizeof(app_template_path))) {
981     return false;
982   }
983 
984   char userpref_path[FILE_MAX];
985   BLI_path_join(
986       userpref_path, sizeof(userpref_path), app_template_path, BLENDER_USERPREF_FILE, NULL);
987   return BLI_exists(userpref_path);
988 }
989 
BKE_appdir_app_templates(ListBase * templates)990 void BKE_appdir_app_templates(ListBase *templates)
991 {
992   BLI_listbase_clear(templates);
993 
994   for (int i = 0; i < 2; i++) {
995     char subdir[FILE_MAX];
996     if (!BKE_appdir_folder_id_ex(app_template_directory_id[i],
997                                  app_template_directory_search[i],
998                                  subdir,
999                                  sizeof(subdir))) {
1000       continue;
1001     }
1002 
1003     struct direntry *dir;
1004     uint totfile = BLI_filelist_dir_contents(subdir, &dir);
1005     for (int f = 0; f < totfile; f++) {
1006       if (!FILENAME_IS_CURRPAR(dir[f].relname) && S_ISDIR(dir[f].type)) {
1007         char *template = BLI_strdup(dir[f].relname);
1008         BLI_addtail(templates, BLI_genericNodeN(template));
1009       }
1010     }
1011 
1012     BLI_filelist_free(dir, totfile);
1013   }
1014 }
1015 
1016 /** \} */
1017 
1018 /* -------------------------------------------------------------------- */
1019 /** \name Temporary Directories
1020  * \{ */
1021 
1022 /**
1023  * Gets the temp directory when blender first runs.
1024  * If the default path is not found, use try $TEMP
1025  *
1026  * Also make sure the temp dir has a trailing slash
1027  *
1028  * \param tempdir: The full path to the temporary temp directory.
1029  * \param tempdir_len: The size of the \a tempdir buffer.
1030  * \param userdir: Directory specified in user preferences (may be NULL).
1031  * note that by default this is an empty string, only use when non-empty.
1032  */
where_is_temp(char * tempdir,const size_t tempdir_len,const char * userdir)1033 static void where_is_temp(char *tempdir, const size_t tempdir_len, const char *userdir)
1034 {
1035 
1036   tempdir[0] = '\0';
1037 
1038   if (userdir && BLI_is_dir(userdir)) {
1039     BLI_strncpy(tempdir, userdir, tempdir_len);
1040   }
1041 
1042   if (tempdir[0] == '\0') {
1043     const char *env_vars[] = {
1044 #ifdef WIN32
1045         "TEMP",
1046 #else
1047         /* Non standard (could be removed). */
1048         "TMP",
1049         /* Posix standard. */
1050         "TMPDIR",
1051 #endif
1052     };
1053     for (int i = 0; i < ARRAY_SIZE(env_vars); i++) {
1054       const char *tmp = BLI_getenv(env_vars[i]);
1055       if (tmp && (tmp[0] != '\0') && BLI_is_dir(tmp)) {
1056         BLI_strncpy(tempdir, tmp, tempdir_len);
1057         break;
1058       }
1059     }
1060   }
1061 
1062   if (tempdir[0] == '\0') {
1063     BLI_strncpy(tempdir, "/tmp/", tempdir_len);
1064   }
1065   else {
1066     /* add a trailing slash if needed */
1067     BLI_path_slash_ensure(tempdir);
1068   }
1069 }
1070 
tempdir_session_create(char * tempdir_session,const size_t tempdir_session_len,const char * tempdir)1071 static void tempdir_session_create(char *tempdir_session,
1072                                    const size_t tempdir_session_len,
1073                                    const char *tempdir)
1074 {
1075   tempdir_session[0] = '\0';
1076 
1077   const int tempdir_len = strlen(tempdir);
1078   /* 'XXXXXX' is kind of tag to be replaced by `mktemp-family` by an UUID. */
1079   const char *session_name = "blender_XXXXXX";
1080   const int session_name_len = strlen(session_name);
1081 
1082   /* +1 as a slash is added,
1083    * #_mktemp_s also requires the last null character is included. */
1084   const int tempdir_session_len_required = tempdir_len + session_name_len + 1;
1085 
1086   if (tempdir_session_len_required <= tempdir_session_len) {
1087     /* No need to use path joining utility as we know the last character of #tempdir is a slash. */
1088     BLI_string_join(tempdir_session, tempdir_session_len, tempdir, session_name);
1089 #ifdef WIN32
1090     const bool needs_create = (_mktemp_s(tempdir_session, tempdir_session_len_required) == 0);
1091 #else
1092     const bool needs_create = (mkdtemp(tempdir_session) == NULL);
1093 #endif
1094     if (needs_create) {
1095       BLI_dir_create_recursive(tempdir_session);
1096     }
1097     if (BLI_is_dir(tempdir_session)) {
1098       BLI_path_slash_ensure(tempdir_session);
1099       /* Success. */
1100       return;
1101     }
1102   }
1103 
1104   CLOG_WARN(&LOG,
1105             "Could not generate a temp file name for '%s', falling back to '%s'",
1106             tempdir_session,
1107             tempdir);
1108   BLI_strncpy(tempdir_session, tempdir, tempdir_session_len);
1109 }
1110 
1111 /**
1112  * Sets #g_app.temp_dirname_base to \a userdir if specified and is a valid directory,
1113  * otherwise chooses a suitable OS-specific temporary directory.
1114  * Sets #g_app.temp_dirname_session to a #mkdtemp
1115  * generated sub-dir of #g_app.temp_dirname_base.
1116  */
BKE_tempdir_init(const char * userdir)1117 void BKE_tempdir_init(const char *userdir)
1118 {
1119   where_is_temp(g_app.temp_dirname_base, sizeof(g_app.temp_dirname_base), userdir);
1120 
1121   /* Clear existing temp dir, if needed. */
1122   BKE_tempdir_session_purge();
1123   /* Now that we have a valid temp dir, add system-generated unique sub-dir. */
1124   tempdir_session_create(
1125       g_app.temp_dirname_session, sizeof(g_app.temp_dirname_session), g_app.temp_dirname_base);
1126 }
1127 
1128 /**
1129  * Path to temporary directory (with trailing slash)
1130  */
BKE_tempdir_session(void)1131 const char *BKE_tempdir_session(void)
1132 {
1133   return g_app.temp_dirname_session[0] ? g_app.temp_dirname_session : BKE_tempdir_base();
1134 }
1135 
1136 /**
1137  * Path to persistent temporary directory (with trailing slash)
1138  */
BKE_tempdir_base(void)1139 const char *BKE_tempdir_base(void)
1140 {
1141   return g_app.temp_dirname_base;
1142 }
1143 
1144 /**
1145  * Delete content of this instance's temp dir.
1146  */
BKE_tempdir_session_purge(void)1147 void BKE_tempdir_session_purge(void)
1148 {
1149   if (g_app.temp_dirname_session[0] && BLI_is_dir(g_app.temp_dirname_session)) {
1150     BLI_delete(g_app.temp_dirname_session, true, true);
1151   }
1152 }
1153 
1154 /** \} */
1155