【问题标题】:realpath() without resolving symlinks?realpath() 不解析符号链接?
【发布时间】:2011-06-14 00:46:39
【问题描述】:

我已经阅读了关于realpath() 的信息,但是是否有一个函数可以传递一个基本目录和一个文件名,它可以在不解析符号链接或检查文件是否实际存在的情况下给我以下结果?还是我必须使用修改后的realpath()

"/var/", "../etc///././/passwd" => "/etc/passwd"

【问题讨论】:

  • “/dir/a_random_synlink/../hello”的结果应该是什么?请记住,如果 a_random_synlink 不指向同一目录中的目录,它可能与“/dir/hello”不同
  • @BatchyX:似乎是标准行为:readlink -v -m '/home/user/linktoslashtmp/../' 返回/home/user
  • 也许 readlink 会这样做,但底层操作系统不会。 ls /home/user/linktoslashtmp/../ 列出/的内容
  • @BatchyX 是正确的,执行此“规范化”将意味着前后路径不必再打开同一个文件。
  • @BatchyX 您显然有错误的工具。 ls 对我的工作方式与 readlink 相同。你必须检查readlink的来源。

标签: c normalization symlink realpath


【解决方案1】:

这是一个 normalize_path() 函数:

如果给定路径是相对路径,则函数首先将当前工作目录添加到它之前。

然后处理特殊路径组件如...或空组件,并返回结果。

对于..,如果有最后一个组件,则删除最后一个组件(/.. 将只返回/)。
对于. 或空组件(双/),这只是被跳过。

该函数确保不返回空路径(而是返回/)。

#define _GNU_SOURCE /* memrchr() */

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>

char * normalize_path(const char * src, size_t src_len) {

        char * res;
        size_t res_len;

        const char * ptr = src;
        const char * end = &src[src_len];
        const char * next;

        if (src_len == 0 || src[0] != '/') {

                // relative path

                char pwd[PATH_MAX];
                size_t pwd_len;

                if (getcwd(pwd, sizeof(pwd)) == NULL) {
                        return NULL;
                }

                pwd_len = strlen(pwd);
                res = malloc(pwd_len + 1 + src_len + 1);
                memcpy(res, pwd, pwd_len);
                res_len = pwd_len;
        } else {
                res = malloc((src_len > 0 ? src_len : 1) + 1);
                res_len = 0;
        }

        for (ptr = src; ptr < end; ptr=next+1) {
                size_t len;
                next = memchr(ptr, '/', end-ptr);
                if (next == NULL) {
                        next = end;
                }
                len = next-ptr;
                switch(len) {
                case 2:
                        if (ptr[0] == '.' && ptr[1] == '.') {
                                const char * slash = memrchr(res, '/', res_len);
                                if (slash != NULL) {
                                        res_len = slash - res;
                                }
                                continue;
                        }
                        break;
                case 1:
                        if (ptr[0] == '.') {
                                continue;

                        }
                        break;
                case 0:
                        continue;
                }
                res[res_len++] = '/';
                memcpy(&res[res_len], ptr, len);
                res_len += len;
        }

        if (res_len == 0) {
                res[res_len++] = '/';
        }
        res[res_len] = '\0';
        return res;
}

【讨论】:

  • +1:这似乎适用于相对于当前目录评估路径的情况。严格来说,我认为这个问题的解释是“评估路径../etc///././passwd相对于/var/”,这是您主题的一个简单变体(您不需要使用getcwd()建立当前目录;您使用用户传递的值)。
  • 谢谢,看起来不错 - 我稍微修改了函数以接受 pwd 参数。
  • 好的,我给你权限
  • @user576875 如果我写下这个函数是你写的,你能允许我在一个 GPL 许可的项目中使用它吗?
  • 谢谢!我已将其修改为独立于系统调用并修复以正确处理 cwd == '/' 的情况:gist.github.com/Eugeny/5127791
【解决方案2】:
function normalize_path($path, $pwd = '/') {
        if (!isset($path[0]) || $path[0] !== '/') {
                $result = explode('/', getcwd());
        } else {
                $result = array('');
        }
        $parts = explode('/', $path);
        foreach($parts as $part) {
            if ($part === '' || $part == '.') {
                    continue;
            } if ($part == '..') {
                    array_pop($result);
            } else {
                    $result[] = $part;
            }
        }
        return implode('/', $result);
}

(在我写这篇文章时,这个问题被标记为 PHP。)

无论如何,这是一个正则表达式版本:

function normalize_path($path, $pwd = '/') {
        if (!isset($path[0]) || $path[0] !== '/') {
                $path = "$pwd/$path";
        }
        return preg_replace('~
                ^(?P>sdotdot)?(?:(?P>sdot)*/\.\.)*
                |(?<sdotdot>(?:(?P>sdot)*/(?!\.\.)(?:[^/]+)(?P>sdotdot)?(?P>sdot)*/\.\.)+)
                |(?<sdot>/\.?(?=/|$))+
        ~sx', '', $path);
}

【讨论】:

  • 是的,它被标记为没有语言,有人在上面加了“php”,我把它改成了“c”——很抱歉忘记了那个标签。
  • @user576875 @thejh 我的错(我将其标记为 PHP)。应该先检查你最近的问题。向所有人道歉。
【解决方案3】:

我用Hardexsolution

#include <string.h>

char * normalizePath(char* pwd, const char * src, char* res) {
    size_t res_len;
    size_t src_len = strlen(src);

    const char * ptr = src;
    const char * end = &src[src_len];
    const char * next;

    if (src_len == 0 || src[0] != '/') {
        // relative path
        size_t pwd_len;

        pwd_len = strlen(pwd);
        memcpy(res, pwd, pwd_len);
        res_len = pwd_len;
    } else {
        res_len = 0;
    }

    for (ptr = src; ptr < end; ptr=next+1) {
        size_t len;
        next = (char*)memchr(ptr, '/', end-ptr);
        if (next == NULL) {
            next = end;
        }
        len = next-ptr;
        switch(len) {
        case 2:
            if (ptr[0] == '.' && ptr[1] == '.') {
                const char * slash = (char*)memrchr(res, '/', res_len);
                if (slash != NULL) {
                    res_len = slash - res;
                }
                continue;
            }
            break;
        case 1:
            if (ptr[0] == '.') {
                continue;
            }
            break;
        case 0:
            continue;
        }

        if (res_len != 1)
            res[res_len++] = '/';

        memcpy(&res[res_len], ptr, len);
        res_len += len;
    }

    if (res_len == 0) {
        res[res_len++] = '/';
    }
    res[res_len] = '\0';
    return res;
}

例子:

#include <stdio.h>

int main(){
    char path[FILENAME_MAX+1];
    printf("\n%s\n",normalizePath((char*)"/usr/share/local/apps",(char*)"./../../../",path));
    return 0;
}

输出:

/usr


注意:
  1. 第一个参数是相对于其他路径将被规范化的目录路径(绝对路径)。一般是当前目录的绝对路径。
  2. 第二个参数是要在不解析符号链接的情况下进行规范化的字符串。
  3. 第三个参数是char*,它必须具有包含规范化路径所需的内存/容量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-14
    • 2017-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多