Permalink
Browse files

Implement readlink -f with libc's realpath() (#147)

This is how busybox does it.  coreutils readlink implements its own algorithm in hundreds of lines of C.

Python's os.path.realpath() is too lenient.


Co-authored-by: Caroline Lin <caroline-lin@users.noreply.github.com>
Co-authored-by: Andy Chu <andychu@users.noreply.github.com>
  • Loading branch information...
3 people committed Jun 15, 2018
1 parent be9f859 commit a68ab70ece89a6cb8e3ba074c261c1ec2b8622ae
Showing with 56 additions and 3 deletions.
  1. +1 −0 bin/readlink
  2. +14 −0 gold/readlink.sh
  3. +22 −0 native/libc.c
  4. +15 −1 native/libc_test.py
  5. +4 −2 tools/readlink.py
View
View
@@ -8,11 +8,25 @@ set -o pipefail
#set -o errexit
dir-does-not-exist() {
readlink -f _tmp/gold-bin/readlink
echo $?
readlink -f libc.so
echo $?
readlink -f /nonexistent
echo $?
readlink -f /nonexistent/foo
echo $?
}
compare() {
PATH="_tmp/gold-bin:$PATH" $0 dir-does-not-exist > _tmp/busybox-readlink.txt
PATH="bin/:$PATH" $0 dir-does-not-exist > _tmp/osh-readlink.txt
diff -u _tmp/busybox-readlink.txt _tmp/osh-readlink.txt
}
"$@"
View
@@ -4,6 +4,8 @@
#include <stdarg.h> // va_list, etc.
#include <stdio.h> // printf
#include <limits.h>
#include <stdlib.h>
#include <fnmatch.h>
#include <glob.h>
@@ -26,6 +28,24 @@ static void debug(const char* fmt, ...) {
#endif
}
static PyObject *
func_realpath(PyObject *self, PyObject *args) {
const char *symlink;
if (!PyArg_ParseTuple(args, "s", &symlink)) {
return NULL;
}
char target[PATH_MAX + 1];
char *status = realpath(symlink, target);
if (status== NULL) {
debug("error occurred while resolving symlink");
return PyLong_FromLong(-1);
} else {
return PyString_FromString(target);
}
}
static PyObject *
func_fnmatch(PyObject *self, PyObject *args) {
const char *pattern;
@@ -309,6 +329,8 @@ func_regex_first_group_match(PyObject *self, PyObject *args) {
}
static PyMethodDef methods[] = {
{"realpath", func_realpath, METH_VARARGS,
"Return canonical path of a symlink."},
{"fnmatch", func_fnmatch, METH_VARARGS,
"Return whether a string matches a pattern."},
// Python's glob doesn't have char classes
View
@@ -8,7 +8,6 @@
"""
libc_test.py: Tests for libc.py
"""
import unittest
import libc # module under test
@@ -94,6 +93,21 @@ def testRegexFirstGroupMatch(self):
(8, 10),
libc.regex_first_group_match('(X.)', s, 6))
def testRealpathFailOnNonexistentDirectory(self):
# This behaviour is actually inconsistent with GNU readlink,
# but matches behaviour of busybox readlink
# (https://github.com/jgunthorpe/busybox)
self.assertEqual(
-1,
libc.realpath('_tmp/nonexistent')
)
# Consistent with GNU
self.assertEqual(
-1,
libc.realpath('_tmp/nonexistent/supernonexistent')
)
if __name__ == '__main__':
unittest.main()
View
@@ -4,7 +4,7 @@
readlink.py
"""
import os
import libc
from core import args, util
SPEC = args.BuiltinFlags()
@@ -17,6 +17,8 @@ def main(argv):
util.error("-f must be passed")
return 1
for arg in argv[i:]:
res = os.path.realpath(arg)
res = libc.realpath(arg)
if res == -1:
return 1
print(res)
return 0

0 comments on commit a68ab70

Please sign in to comment.