Login

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download | RSS feed

/* platform.c - platform-specific library.

   This is free and unencumbered software released into the public domain.
   For more information, please refer to <http://unlicense.org>. */


#include "defs.h"

#include <stdbool.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "platform.h"

#include "debug.h"

bool check_path_abs (const char *path)
{
#if !defined (_WIN32) && !defined(_WIN64)
    return (path && (path[0] == '/' || path[0] == '\\'));
#else
    return (path && (isalpha (path[0])) && path[1] == ':' && (path[2] == '/' || path[2] == '\\'));
#endif
}

#if defined (_WIN32) || defined(_WIN64)
# define ROOT_START 2    // skip disk name "A:"
#else
# define ROOT_START 0
#endif

char *resolve_full_path (const char *path)
{
    char *s;
    unsigned len, i, j;
    bool last_sep, f;

    if (!path || path[0] == '\0')
    {
        errno = path ? ENOENT : EINVAL;
        return (char *) NULL;
    }
    if (!check_path_abs (path))
    {
        errno = EINVAL;
        return (char *) NULL;
    }

    len = strlen (path);
    s = malloc (len+3); // + terminating zero + 2 chars (=3)
    if (!s)
        return (char *) NULL;
    strcpy (s, path);   // fine (sizeof (s) > sizeof (path))

    // Replace '\\' or '/' with PATHSEP. From here all '/' in comments must be treated as PATHSEP.
    for (i = ROOT_START; s[i] != '\0'; i++)
#if defined (_WIN32) || defined(_WIN64)
        if (s[i] == '/')
#else
        if (s[i] == '\\')
#endif
            s[i] = PATHSEP;

    last_sep = s[len-1] == PATHSEP;     // remember if last char is PATHSEP to keep it

    // Replace multiple '/' with single '/'
    i = ROOT_START;
    j = ROOT_START;
    while (s[j] != '\0')
    {
        if (s[j] == PATHSEP)
            while (s[j+1] == PATHSEP)
                j++;    // skip one char
        s[i] = s[j];
        i++;
        j++;
    }
    s[i] = '\0';
    len = i;

    // Replace '/.' with '/./', and '/..' with '/../' at end
    if (len >= (ROOT_START+2) && s[len-1] == '.')
    {
        if ((len >= (ROOT_START+3) && s[len-3] == PATHSEP && s[len-2] == '.')
        ||  (s[len-2] == PATHSEP))
        {
            s[len] = PATHSEP;
            len++;
            s[len] = '\0';
        }
    }

    // Replace "/./" with '/'
    i = ROOT_START;
    j = ROOT_START;
    while (s[j] != '\0')
    {
        if (s[j] == PATHSEP && s[j+1] == '.' && s[j+2] == PATHSEP)
            j += 2;     // skip two chars
        s[i] = s[j];
        i++;
        j++;
    }
    s[i] = '\0';
    len = i;

    // Remove all "/../"
    do
    {
        f = false;
        i = ROOT_START;
        while (s[i] != '\0'
        &&     (!(s[i] == PATHSEP && s[i+1] == '.' && s[i+2] == '.' && s[i+3] == PATHSEP)))
            i++;
        if (s[i] != '\0')
        {
            // check bad path at start: "/../", "A:/../"
            if (i == ROOT_START)
            {
                free (s);
                errno = EINVAL;
                return (char *) NULL;
            }
            j = i - 1;
            while (j > ROOT_START && s[j] != PATHSEP)
                j--;
            // remove part of string [j, i+2]
            i += 3;
            while (s[i] != '\0')
            {
                s[j] = s[i];
                i++;
                j++;
            }
            s[j] = '\0';
            f = true;
        }
    } while (f);

    // Restore '/' at end if it was and remove it if there wasn't
    len = strlen (s);
    if (last_sep && len > ROOT_START && s[len-1] != PATHSEP)
    {
        s[len] = PATHSEP;
        len++;
        s[len] = '\0';
    }
    else if (!last_sep && len > ROOT_START && len != ROOT_START+1 && s[len-1] == PATHSEP)
    {
        len--;
        s[len] = '\0';
    }

    return realloc (s, len+1);  // + terminating zero (strlen (s) <= strlen (path))
}

bool check_path_exists (const char *path)
{
    char *s;
    struct stat st;
    bool ok;

    s = resolve_full_path (path);
    if (!s)
        return false;

    ok = stat (s, &st) >= 0;

    free (s);

    if (!ok)
        return false;

    return (S_ISDIR (st.st_mode)) != 0;
}

bool check_file_exists (const char *path)
{
    char *s;
    struct stat st;
    bool ok;

    s = resolve_full_path (path);
    if (!s)
        return false;

    ok = stat (s, &st) >= 0;

    free (s);

    if (!ok)
        return false;

    return (S_ISREG (st.st_mode)) || ((st.st_mode & S_IFMT) == 0);
}

char *get_current_dir (void)
{
    return getcwd (NULL, 0);
}

char *get_dir_name (const char *path)
{
    char *pathc, *d, *result;

    pathc = strdup (path);
    if (!pathc)
        return (char *) NULL;
    d = dirname (pathc);
    result = strdup (d);
    free (pathc);
    return result;
}