LCOV - code coverage report
Current view: top level - src/unix - upath.c (source / functions) Hit Total Coverage
Test: allegro_auto.info Lines: 18 188 9.6 %
Date: 2018-08-11 00:50:28 Functions: 3 6 50.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*         ______   ___    ___
       2             :  *        /\  _  \ /\_ \  /\_ \
       3             :  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
       4             :  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
       5             :  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
       6             :  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
       7             :  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
       8             :  *                                           /\____/
       9             :  *                                           \_/__/
      10             :  *
      11             :  *      List of system pathes for the Unix library.
      12             :  *
      13             :  *      By Michael Bukin.
      14             :  *
      15             :  *      See readme.txt for copyright information.
      16             :  */
      17             : 
      18             : 
      19             : #include <stdio.h>
      20             : #include <unistd.h>
      21             : #include <stdlib.h>
      22             : #include <string.h>
      23             : #include <sys/stat.h>
      24             : #include <pwd.h>
      25             : 
      26             : #include "allegro5/allegro.h"
      27             : #include "allegro5/internal/aintern.h"
      28             : #include "allegro5/platform/aintunix.h"
      29             : #include "allegro5/fshook.h"
      30             : #include "allegro5/path.h"
      31             : 
      32             : #ifdef ALLEGRO_HAVE_SYS_UTSNAME_H
      33             :    #include <sys/utsname.h>
      34             : #endif
      35             : 
      36             : #ifdef ALLEGRO_HAVE_SV_PROCFS_H
      37             :    #include <sys/procfs.h>
      38             :    #include <sys/ioctl.h>
      39             :    #include <fcntl.h>
      40             : #endif
      41             : 
      42             : ALLEGRO_DEBUG_CHANNEL("upath")
      43             : 
      44             : #ifndef PATH_MAX
      45             : #define PATH_MAX 4096
      46             : #endif
      47             : 
      48             : #ifndef ALLEGRO_MACOSX
      49             : /* _find_executable_file:
      50             :  *  Helper function: searches path and current directory for executable.
      51             :  *  Returns 1 on succes, 0 on failure.
      52             :  */
      53           0 : static ALLEGRO_PATH *_find_executable_file(const char *filename)
      54             : {
      55             :    char *env;
      56             : 
      57             :    /* If filename has an explicit path, search current directory */
      58           0 :    if (strchr(filename, '/')) {
      59           0 :       if (filename[0] == '/') {
      60             :          /* Full path; done */
      61           0 :          return al_create_path(filename);
      62             :       }
      63             :       else {
      64             :          struct stat finfo;
      65             :          char *cwd;
      66             : 
      67             :          /* Prepend current directory */
      68           0 :          cwd = al_get_current_directory();
      69           0 :          if (cwd) {
      70           0 :             ALLEGRO_PATH *path = al_create_path_for_directory(cwd);
      71           0 :             al_free(cwd);
      72           0 :             al_set_path_filename(path, filename);
      73             : 
      74           0 :             if (stat(al_path_cstr(path, '/'), &finfo) == 0
      75           0 :                   && !S_ISDIR(finfo.st_mode)) {
      76           0 :                return path;
      77             :             }
      78             : 
      79           0 :             al_destroy_path(path);
      80             :          }
      81             :       }
      82             :    }
      83             :    /* If filename has no explicit path, but we do have $PATH, search
      84             :     * there
      85             :     */
      86           0 :    else if ((env = getenv("PATH"))) {
      87             :       struct stat finfo;
      88           0 :       ALLEGRO_USTR *us = al_ustr_new(env);
      89           0 :       int start_pos = 0;
      90           0 :       while (start_pos >= 0) {
      91           0 :          int next_start_pos = al_ustr_find_chr(us, start_pos + 1, ':');
      92           0 :          int end_pos = next_start_pos;
      93           0 :          if (next_start_pos < 0)
      94           0 :             end_pos = al_ustr_size(us);
      95             :          ALLEGRO_USTR_INFO info;
      96           0 :          const ALLEGRO_USTR *sub = al_ref_ustr(&info, us, start_pos, end_pos);
      97             : 
      98           0 :          ALLEGRO_PATH *path = al_create_path_for_directory(al_cstr(sub));
      99           0 :          al_set_path_filename(path, filename);
     100             : 
     101           0 :          if (stat(al_path_cstr(path, '/'), &finfo) == 0 &&
     102           0 :                !S_ISDIR (finfo.st_mode)) {
     103           0 :             al_ustr_free(us);
     104           0 :             return path;
     105             :          }
     106             : 
     107           0 :          al_destroy_path(path);
     108             : 
     109           0 :          start_pos = next_start_pos;
     110             :       }
     111           0 :       al_ustr_free(us);
     112             :    }
     113             : 
     114             :    return NULL;
     115             : }
     116             : 
     117             : /* Return full path to the current executable, use proc fs if
     118             :  * available.
     119             :  */
     120          14 : static ALLEGRO_PATH *get_executable_name(void)
     121             : {
     122             :    ALLEGRO_PATH *path;
     123             : 
     124             :    #ifdef ALLEGRO_HAVE_GETEXECNAME
     125             :    {
     126             :       const char *s = getexecname();
     127             :       if (s) {
     128             :          if (s[0] == '/') {   /* Absolute path */
     129             :             return al_create_path(s);
     130             :          }
     131             :          else {               /* Not an absolute path */
     132             :             path = _find_executable_file(s);
     133             :             if (path)
     134             :                return path;
     135             :          }
     136             :       }
     137             :    }
     138             :    #endif
     139             : 
     140             :    /* We need the PID in order to query procfs */
     141          14 :    pid_t pid = getpid();
     142             : 
     143             :    /* Try a Linux-like procfs */   
     144             :    /* get symolic link to executable from proc fs */
     145             :    char linkname[1024];
     146             :    char filename[1024];
     147             :    struct stat finfo;
     148          14 :    sprintf(linkname, "/proc/%d/exe", (int)pid);
     149          14 :    if (stat(linkname, &finfo) == 0) {
     150          14 :       int len = readlink(linkname, filename, sizeof(filename) - 1);
     151          14 :       if (len > -1) {
     152          14 :          filename[len] = '\0';
     153          14 :          return al_create_path(filename);
     154             :       }
     155             :    }
     156             : 
     157             :    /* Use System V procfs calls if available */
     158             : #ifdef ALLEGRO_HAVE_SV_PROCFS_H
     159             :    struct prpsinfo psinfo;
     160             :    int fd;
     161             :    sprintf(linkname, "/proc/%d/exe", (int)pid);
     162             :    fd = open(linkname, O_RDONLY);
     163             :    if (fd != -1) {
     164             :       ioctl(fd, PIOCPSINFO, &psinfo);
     165             :       close(fd);
     166             :    
     167             :       /* Use argv[0] directly if we can */
     168             : #ifdef ALLEGRO_HAVE_PROCFS_ARGCV
     169             :       if (psinfo.pr_argv && psinfo.pr_argc) {
     170             :           path = _find_executable_file(psinfo.pr_argv[0]);
     171             :           if (path)
     172             :             return path;
     173             :       }
     174             :       else
     175             : #endif
     176             :       {
     177             :          /* Emulate it */
     178             :          /* We use the pr_psargs field to find argv[0]
     179             :           * This is better than using the pr_fname field because we need
     180             :           * the additional path information that may be present in argv[0]
     181             :           */
     182             :     
     183             :          /* Skip other args */
     184             :          char *s = strchr(psinfo.pr_psargs, ' ');
     185             :          if (s)
     186             :             s[0] = '\0';
     187             :          path = _find_executable_file(psinfo.pr_psargs);
     188             :          if (path)
     189             :             return path;
     190             :       }
     191             :    
     192             :       /* Try the pr_fname just for completeness' sake if argv[0] fails */
     193             :       path = _find_executable_file(psinfo.pr_fname);
     194             :       if (path)
     195             :          return path;
     196             :    }
     197             : #endif
     198             : 
     199             :    /* Last resort: try using the output of the ps command to at least find */
     200             :    /* the name of the file if not the full path */
     201             :    char command[1024];
     202           0 :    sprintf(command, "ps -p %d", (int)pid);
     203           0 :    FILE *pipe = popen(command, "r");
     204           0 :    if (pipe) {
     205             :       char* ret;
     206             :       /* The first line of output is a header */
     207           0 :       ret = fgets(linkname, sizeof(linkname), pipe);
     208           0 :       if (!ret)
     209           0 :          ALLEGRO_ERROR("Failed to read the name of the executable file.\n");
     210             :       
     211             :       /* The information we want is in the last column; find it */
     212           0 :       int len = strlen(linkname);
     213           0 :       while (linkname[len] != ' ' && linkname[len] != '\t')
     214           0 :          len--;
     215             : 
     216             :       /* The second line contains the info we want */
     217           0 :       ret = fgets(linkname, sizeof(linkname), pipe);
     218           0 :       if (!ret)
     219           0 :          ALLEGRO_ERROR("Failed to read the name of the executable file.\n");
     220           0 :       pclose(pipe);
     221             : 
     222             :       /* Treat special cases: filename between [] and - for login shell */
     223           0 :       if (linkname[len] == '-')
     224           0 :          len++;
     225             : 
     226           0 :       if (linkname[len] == '[' && linkname[strlen(linkname)] == ']') {
     227           0 :          len++;
     228           0 :          linkname[strlen(linkname)] = '\0';
     229             :       }         
     230             :       
     231             :       /* Now, the filename should be in the last column */
     232           0 :       _al_sane_strncpy(filename, linkname+len+1, strlen(linkname)-len+1);
     233             : 
     234           0 :       path = _find_executable_file(filename);
     235           0 :       if (path)
     236             :          return path;
     237             : 
     238             :       /* Just return the output from ps... */         
     239           0 :       return al_create_path(filename);
     240             :    }
     241             : 
     242             :    /* Give up; return empty string */
     243           0 :    return al_create_path("");
     244             : }
     245             : 
     246           0 : static ALLEGRO_PATH *follow_symlinks(ALLEGRO_PATH *path)
     247             : {
     248           0 :    for (;;) {
     249           0 :       const char *path_str = al_path_cstr(path, '/');
     250             :       char buf[PATH_MAX];
     251             :       int len;
     252             : 
     253           0 :       len = readlink(path_str, buf, sizeof(buf) - 1);
     254           0 :       if (len <= 0)
     255             :          break;
     256           0 :       buf[len] = '\0';
     257           0 :       al_destroy_path(path);
     258           0 :       path = al_create_path(buf);
     259             :    }
     260             : 
     261             :    /* Make absolute path. */
     262             :    {
     263           0 :       const char *cwd = al_get_current_directory();
     264           0 :       ALLEGRO_PATH *cwd_path = al_create_path_for_directory(cwd);
     265           0 :       if (al_rebase_path(cwd_path, path))
     266           0 :          al_make_path_canonical(path);
     267           0 :       al_destroy_path(cwd_path);
     268           0 :       al_free((void *) cwd);
     269             :    }
     270             : 
     271           0 :    return path;
     272             : }
     273             : 
     274             : #endif
     275             : 
     276             : #define XDG_MAX_PATH_LEN 1000
     277             : 
     278             : /* get_xdg_path - locate an XDG user dir
     279             :  */
     280           0 : static ALLEGRO_PATH *_get_xdg_path(const char *location)
     281             : {
     282           0 :    ALLEGRO_PATH *location_path = NULL;
     283           0 :    ALLEGRO_PATH *xdg_config_path = NULL;
     284           0 :    ALLEGRO_FILE *xdg_config_file = NULL;   
     285           0 :    const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
     286             :    int fd;
     287             : 
     288           0 :    if (xdg_config_home) {
     289             :       /* use $XDG_CONFIG_HOME since it exists */
     290           0 :       xdg_config_path = al_create_path_for_directory(xdg_config_home);
     291             :    }
     292             :    else {
     293             :       /* the default XDG location is ~/.config */
     294           0 :       xdg_config_path = al_get_standard_path(ALLEGRO_USER_HOME_PATH);
     295           0 :       if (!xdg_config_path) return NULL;      
     296           0 :       al_append_path_component(xdg_config_path, ".config");
     297             :    }   
     298             :    
     299           0 :    al_set_path_filename(xdg_config_path, "user-dirs.dirs");
     300           0 :    fd = open(al_path_cstr(xdg_config_path, '/'), O_RDONLY);
     301           0 :    if (fd != -1) {
     302           0 :      xdg_config_file = al_fopen_fd(fd, "r");
     303             :    }
     304           0 :    al_destroy_path(xdg_config_path);
     305             :    
     306           0 :    if (!xdg_config_file) return NULL;
     307             :       
     308           0 :    while (!al_feof(xdg_config_file)) {
     309             :       char line[XDG_MAX_PATH_LEN];      /* one line of the config file */
     310           0 :       const char *p = line;             /* where we're at in the line */
     311             :       char component[XDG_MAX_PATH_LEN]; /* the path component being parsed */      
     312           0 :       int i = 0;                        /* how long the current component is */
     313             : 
     314           0 :       al_fgets(xdg_config_file, line, XDG_MAX_PATH_LEN);
     315             :       
     316             :       /* skip leading white space */
     317           0 :       while (*p == ' ' || *p == '\t') p++;
     318             :    
     319             :       /* skip the line if it does not begin with XDG_location_DIR */            
     320           0 :       if (strncmp(p, "XDG_", 4)) continue;
     321           0 :       p += 4;
     322             :       
     323           0 :       if (strncmp(p, location, strlen(location))) continue;
     324           0 :       p += strlen(location);
     325             :       
     326           0 :       if (strncmp(p, "_DIR", 4)) continue;
     327           0 :       p += 4;
     328             :       
     329             :       /* skip past the =", allowing for white space */
     330           0 :       while (*p == ' ' || *p == '\t') p++;      
     331           0 :       if (*p++ != '=') continue;
     332           0 :       while (*p == ' ' || *p == '\t') p++;
     333           0 :       if (*p++ != '"') continue;
     334             :       
     335             :       /* We've found the right line. Now parse it, basically assuming
     336             :          that it is in a sane format. 
     337             :        */
     338           0 :       if (!strncmp(p, "$HOME", 5)) {
     339             :          /* $HOME is the only environment variable that the path is 
     340             :             allowed to use, and it must be first, by specification. */
     341           0 :          location_path = al_get_standard_path(ALLEGRO_USER_HOME_PATH);
     342           0 :          p += 5;
     343             :       }
     344             :       else {
     345           0 :          location_path = al_create_path("/");
     346             :       }
     347             :       
     348           0 :       while (*p) {
     349           0 :          if (*p == '"' || *p == '/') {
     350             :             /* add the component (if non-empty) to the path */
     351           0 :             if (i > 0) {
     352           0 :                component[i] = 0;
     353           0 :                al_append_path_component(location_path, component);
     354           0 :                i = 0;
     355             :             }
     356           0 :             if (*p == '"') break;
     357             :          }
     358             :          else {
     359           0 :             if (*p == '\\') {
     360             :                /* treat any escaped character as a literal */
     361           0 :                p++;
     362           0 :                if (!*p) break;
     363             :             }            
     364           0 :             component[i++] = *p;
     365             :          }
     366             :          
     367           0 :          p++;
     368             :       }
     369             :       
     370             :       /* Finished parsing the path. */
     371           0 :       break;
     372             :    }
     373             :    
     374           0 :    al_fclose(xdg_config_file);
     375             :    
     376           0 :    return location_path;
     377             : }
     378             : 
     379           6 : static ALLEGRO_PATH *_unix_find_home(void)
     380             : {
     381           6 :    char *home_env = getenv("HOME");
     382             : 
     383           6 :    if (!home_env || home_env[0] == '\0') {
     384             :       /* since HOME isn't set, we have to ask libc for the info */
     385             : 
     386             :       /* get user id */
     387           0 :       uid_t uid = getuid();
     388             : 
     389             :       /* grab user information */
     390           0 :       struct passwd *pass = getpwuid(uid);
     391           0 :       if (!pass) {
     392           0 :          al_set_errno(errno);
     393           0 :          return NULL;
     394             :       }
     395             : 
     396           0 :       if (pass->pw_dir) {
     397             :          /* hey, we got our home directory */
     398           0 :          return al_create_path_for_directory(pass->pw_dir);
     399             :       }
     400           0 :       al_set_errno(ENOENT);
     401           0 :       return NULL;
     402             :    }
     403             :    else {
     404           6 :       return al_create_path_for_directory(home_env);
     405             :    }
     406             : }
     407             : 
     408          20 : ALLEGRO_PATH *_al_unix_get_path(int id)
     409             : {
     410          20 :    switch (id) {
     411           0 :       case ALLEGRO_TEMP_PATH: {
     412             :          /* Check: TMP, TMPDIR, TEMP or TEMPDIR */
     413           0 :          char *envs[] = { "TMP", "TMPDIR", "TEMP", "TEMPDIR", NULL};
     414           0 :          uint32_t i = 0;
     415           0 :          for (; envs[i] != NULL; ++i) {
     416           0 :             char *tmp = getenv(envs[i]);
     417           0 :             if (tmp) {
     418           0 :                return al_create_path_for_directory(tmp);
     419             :             }
     420             :          }
     421             : 
     422             :          /* next try: /tmp /var/tmp /usr/tmp */
     423           0 :          char *paths[] = { "/tmp/", "/var/tmp/", "/usr/tmp/", NULL };
     424           0 :          for (i=0; paths[i] != NULL; ++i) {
     425           0 :             ALLEGRO_FS_ENTRY *fse = al_create_fs_entry(paths[i]);
     426           0 :             bool found = (al_get_fs_entry_mode(fse) & ALLEGRO_FILEMODE_ISDIR) != 0;
     427           0 :             al_destroy_fs_entry(fse);
     428           0 :             if (found) {
     429           0 :                return al_create_path_for_directory(paths[i]);
     430             :             }
     431             :          }
     432             : 
     433             :          /* Give up? */
     434             :          return NULL;
     435             :       } break;
     436             : 
     437           0 :       case ALLEGRO_RESOURCES_PATH: {
     438           0 :          ALLEGRO_PATH *exe = get_executable_name();
     439           0 :          exe = follow_symlinks(exe);
     440           0 :          al_set_path_filename(exe, NULL);
     441           0 :          return exe;
     442             : 
     443             :       } break;
     444             : 
     445           0 :       case ALLEGRO_USER_DATA_PATH:
     446             :       case ALLEGRO_USER_SETTINGS_PATH: {
     447           0 :          ALLEGRO_PATH *local_path = NULL;
     448           0 :          const char *org_name = al_get_org_name();
     449           0 :          const char *app_name = al_get_app_name();
     450             :          
     451             :          /* to avoid writing directly into the user's directory, require at least an app name */
     452           0 :          if (!app_name)
     453             :             return NULL;
     454             :          
     455             :          /* find the appropriate path from the xdg environment variables, if possible */
     456           0 :          if (id == ALLEGRO_USER_DATA_PATH) {
     457           0 :             const char *xdg_data_home = getenv("XDG_DATA_HOME");
     458           0 :             local_path = al_create_path_for_directory(xdg_data_home ? xdg_data_home : ".local/share");
     459             :          }
     460             :          else {
     461           0 :             const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
     462           0 :             local_path = al_create_path_for_directory(xdg_config_home ? xdg_config_home : ".config");
     463             :          }
     464             :          
     465           0 :          if (!local_path) 
     466             :             return NULL;
     467             :          
     468             :          /* if the path is relative, prepend the user's home directory */
     469           0 :          if (al_path_cstr(local_path, '/')[0] != '/') {
     470           0 :             ALLEGRO_PATH *home_path = _unix_find_home();
     471           0 :             if (!home_path)
     472             :                return NULL;
     473             :             
     474           0 :             al_rebase_path(home_path, local_path);
     475           0 :             al_destroy_path(home_path);
     476             :          }
     477             : 
     478             :          /* only add org name if not blank */
     479           0 :          if (org_name && org_name[0]) {              
     480           0 :             al_append_path_component(local_path, al_get_org_name());
     481             :          }
     482             :          
     483           0 :          al_append_path_component(local_path, al_get_app_name());
     484             : 
     485           0 :         return local_path;
     486             :       } break;
     487             : 
     488           6 :       case ALLEGRO_USER_HOME_PATH:
     489           6 :          return _unix_find_home();
     490             :          
     491           0 :       case ALLEGRO_USER_DOCUMENTS_PATH: {
     492           0 :          ALLEGRO_PATH *local_path = _get_xdg_path("DOCUMENTS");
     493           0 :          return local_path ? local_path : _unix_find_home();
     494             :       } break;
     495             : 
     496          14 :       case ALLEGRO_EXENAME_PATH:
     497          14 :          return get_executable_name();
     498             :          break;
     499             : 
     500             :       default:
     501             :          return NULL;
     502             :    }
     503             : 
     504             :    return NULL;
     505             : }
     506             : 
     507             : /* vim: set sts=3 sw=3 et: */

Generated by: LCOV version 1.13