Commit f52f0246 authored by binji@chromium.org's avatar binji@chromium.org

[NaCl SDK] Add realpath(3) implementation.

This really should be in nacl-newlib, but it's easier to land here now and
remove it later when it lands in newlib and rolls into the SDK.

This implementation does not support symlinks, but neither does nacl_io
currently.

BUG=none
R=sbc@chromium.org

Review URL: https://codereview.chromium.org/295933009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@272551 0039d316-1c4b-4281-b951-d872f2087c98
parent 45cba27e
/* Copyright (c) 2014 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
#ifndef LIBRARIES_NACL_IO_INCLUDE_STDLIB_H_
#define LIBRARIES_NACL_IO_INCLUDE_STDLIB_H_
#include <sys/cdefs.h>
#include_next <stdlib.h>
__BEGIN_DECLS
char* realpath(const char* path, char* resolved_path);
__END_DECLS
#endif
...@@ -68,9 +68,9 @@ ...@@ -68,9 +68,9 @@
"syscalls/bind.c", "syscalls/bind.c",
"syscalls/cfgetispeed.c", "syscalls/cfgetispeed.c",
"syscalls/cfgetospeed.c", "syscalls/cfgetospeed.c",
"syscalls/cfsetspeed.c",
"syscalls/cfsetispeed.c", "syscalls/cfsetispeed.c",
"syscalls/cfsetospeed.c", "syscalls/cfsetospeed.c",
"syscalls/cfsetspeed.c",
"syscalls/chdir.c", "syscalls/chdir.c",
"syscalls/chmod.c", "syscalls/chmod.c",
"syscalls/chown.c", "syscalls/chown.c",
...@@ -118,19 +118,13 @@ ...@@ -118,19 +118,13 @@
"syscalls/pipe.c", "syscalls/pipe.c",
"syscalls/poll.c", "syscalls/poll.c",
"syscalls/readlink.c", "syscalls/readlink.c",
"syscalls/rmdir.c", "syscalls/realpath.c",
"syscalls/recv.c", "syscalls/recv.c",
"syscalls/recvfrom.c", "syscalls/recvfrom.c",
"syscalls/recvmsg.c", "syscalls/recvmsg.c",
"syscalls/remove.c", "syscalls/remove.c",
"syscalls/rename.c", "syscalls/rename.c",
"syscalls/tcdrain.c", "syscalls/rmdir.c",
"syscalls/tcflow.c",
"syscalls/tcflush.c",
"syscalls/tcgetattr.c",
"syscalls/tcsendbreak.c",
"syscalls/tcsetattr.c",
"syscalls/truncate.c",
"syscalls/select.c", "syscalls/select.c",
"syscalls/send.c", "syscalls/send.c",
"syscalls/sendmsg.c", "syscalls/sendmsg.c",
...@@ -151,9 +145,16 @@ ...@@ -151,9 +145,16 @@
"syscalls/socket.c", "syscalls/socket.c",
"syscalls/socketpair.c", "syscalls/socketpair.c",
"syscalls/symlink.c", "syscalls/symlink.c",
"syscalls/unlink.c", "syscalls/tcdrain.c",
"syscalls/tcflow.c",
"syscalls/tcflush.c",
"syscalls/tcgetattr.c",
"syscalls/tcsendbreak.c",
"syscalls/tcsetattr.c",
"syscalls/truncate.c",
"syscalls/umount.c", "syscalls/umount.c",
"syscalls/uname.c", "syscalls/uname.c",
"syscalls/unlink.c",
"syscalls/utime.c", "syscalls/utime.c",
"syscalls/utimes.c", "syscalls/utimes.c",
], ],
...@@ -241,6 +242,7 @@ ...@@ -241,6 +242,7 @@
"netinet/tcp.h", "netinet/tcp.h",
"netinet6/in6.h", "netinet6/in6.h",
"poll.h", "poll.h",
"stdlib.h",
"sys/ioctl.h", "sys/ioctl.h",
"sys/mount.h", "sys/mount.h",
"sys/poll.h", "sys/poll.h",
...@@ -261,6 +263,7 @@ ...@@ -261,6 +263,7 @@
"netinet/tcp.h", "netinet/tcp.h",
"netinet6/in6.h", "netinet6/in6.h",
"poll.h", "poll.h",
"stdlib.h",
"sys/ioctl.h", "sys/ioctl.h",
"sys/mount.h", "sys/mount.h",
"sys/poll.h", "sys/poll.h",
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
#include "nacl_io/kernel_intercept.h" #include "nacl_io/kernel_intercept.h"
#include "nacl_io/kernel_wrap.h" #include "nacl_io/kernel_wrap.h"
#if !defined(__native_client__) || defined(__GLIBC__) #if defined(__native_client__) && defined(__GLIBC__)
// GLIBC-only entry point. // GLIBC-only entry point.
// TODO(sbc): remove once this bug gets fixed: // TODO(sbc): remove once this bug gets fixed:
// https://code.google.com/p/nativeclient/issues/detail?id=3709 // https://code.google.com/p/nativeclient/issues/detail?id=3709
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
#include "nacl_io/kernel_intercept.h" #include "nacl_io/kernel_intercept.h"
#include "nacl_io/kernel_wrap.h" #include "nacl_io/kernel_wrap.h"
#if defined(__native_client__)
// Don't wrap mkdir on host builds. This allows us to test against the real
// "mkdir" on Linux standalone builds.
int mkdir(const char* pathname, mode_t mode) { int mkdir(const char* pathname, mode_t mode) {
return ki_mkdir(pathname, mode); return ki_mkdir(pathname, mode);
} }
#endif
/* Copyright 2013 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sdk_util/macros.h"
EXTERN_C_BEGIN
#if defined(__native_client__)
// TODO(binji): glibc has realpath, but it fails for all tests. Investigate.
char* realpath(const char* path, char* resolved_path) {
if (path == NULL) {
errno = EINVAL;
return NULL;
}
int needs_free = 0;
if (resolved_path == NULL) {
resolved_path = (char*)malloc(PATH_MAX);
needs_free = 1;
}
struct stat statbuf;
const char* in = path;
char* out = resolved_path;
char* out_end = resolved_path + PATH_MAX - 1;
int done = 0;
*out = 0;
if (*in == '/') {
// Absolute path.
strcat(out, "/");
in++;
out++;
} else {
// Relative path.
if (getcwd(out, out_end - out) == NULL)
goto fail;
out += strlen(out);
}
if (stat(resolved_path, &statbuf) != 0)
goto fail;
while (!done) {
const char* next_slash = strchr(in, '/');
size_t namelen;
const char* next_in;
if (next_slash) {
namelen = next_slash - in;
next_in = next_slash + 1;
} else {
namelen = strlen(in);
next_in = in + namelen; // Move to the '\0'
done = 1;
}
if (namelen == 0) {
// Empty name, do nothing.
} else if (namelen == 1 && strncmp(in, ".", 1) == 0) {
// Current directory, do nothing.
} else if (namelen == 2 && strncmp(in, "..", 2) == 0) {
// Parent directory, find previous slash in resolved_path.
char* prev_slash = strrchr(resolved_path, '/');
assert(prev_slash != NULL);
out = prev_slash;
if (prev_slash == resolved_path) {
// Moved to the root. Keep the slash.
++out;
}
*out = 0;
} else {
// Append a slash if not at root.
if (out != resolved_path + 1) {
if (out + 1 > out_end) {
errno = ENAMETOOLONG;
goto fail;
}
strncat(out, "/", namelen);
out++;
}
if (out + namelen > out_end) {
errno = ENAMETOOLONG;
goto fail;
}
strncat(out, in, namelen);
out += namelen;
}
in = next_in;
if (stat(resolved_path, &statbuf) != 0)
goto fail;
// If there is more to the path, then the current path must be a directory.
if (!done && !S_ISDIR(statbuf.st_mode)) {
errno = ENOTDIR;
goto fail;
}
}
return resolved_path;
fail:
if (needs_free) {
free(resolved_path);
}
return NULL;
}
EXTERN_C_END
#endif // defined(__native_client__)
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
'mock_node.cc', 'mock_node.cc',
'mock_node.h', 'mock_node.h',
'mock_util.h', 'mock_util.h',
'syscalls_test.cc',
'path_test.cc', 'path_test.cc',
'pepper_interface_mock.cc', 'pepper_interface_mock.cc',
'pepper_interface_mock.h', 'pepper_interface_mock.h',
......
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "gtest/gtest.h"
#include "nacl_io/kernel_intercept.h"
#include "nacl_io/kernel_proxy.h"
using namespace nacl_io;
namespace {
class SyscallsTest : public ::testing::Test {
public:
SyscallsTest() {}
void SetUp() {
ASSERT_EQ(0, ki_push_state_for_testing());
ASSERT_EQ(0, ki_init(&kp_));
// Unmount the passthrough FS and mount a memfs.
EXPECT_EQ(0, kp_.umount("/"));
EXPECT_EQ(0, kp_.mount("", "/", "memfs", 0, NULL));
}
void TearDown() { ki_uninit(); }
protected:
KernelProxy kp_;
};
} // namespace
#if defined(__native_client__) || defined(STANDALONE)
// The Linux standalone test is unique in that it calls the real Linux
// functions (realpath, mkdir, chdir, etc.), not the nacl_io functions. This is
// done to show that the tests match the behavior for a real implementation.
TEST_F(SyscallsTest, Realpath) {
char buffer[PATH_MAX];
int result;
#if defined(__native_client__)
ASSERT_EQ(0, mkdir("/tmp", S_IREAD | S_IWRITE));
#endif
result = mkdir("/tmp/bar", S_IREAD | S_IWRITE);
#if defined(__native_client__)
ASSERT_EQ(0, result);
#else
if (result == -1) {
ASSERT_EQ(EEXIST, errno);
} else {
ASSERT_EQ(0, result);
}
#endif
int fd = open("/tmp/file", O_CREAT | O_RDWR, 0644);
ASSERT_GT(fd, -1);
ASSERT_EQ(0, close(fd));
// Test absolute paths.
EXPECT_STREQ("/", realpath("/", buffer));
EXPECT_STREQ("/", realpath("/tmp/..", buffer));
EXPECT_STREQ("/tmp", realpath("/tmp", buffer));
EXPECT_STREQ("/tmp", realpath("/tmp/", buffer));
EXPECT_STREQ("/tmp", realpath("/tmp/bar/..", buffer));
EXPECT_STREQ("/tmp", realpath("/tmp/bar/../bar/../../tmp", buffer));
EXPECT_STREQ("/tmp", realpath("/tmp/././", buffer));
EXPECT_STREQ("/tmp", realpath("///tmp", buffer));
EXPECT_STREQ("/tmp/bar", realpath("/tmp/bar", buffer));
EXPECT_EQ(NULL, realpath("/blah", buffer));
EXPECT_EQ(ENOENT, errno);
EXPECT_EQ(NULL, realpath("/blah/blah", buffer));
EXPECT_EQ(ENOENT, errno);
EXPECT_EQ(NULL, realpath("/tmp/baz/..", buffer));
EXPECT_EQ(ENOENT, errno);
EXPECT_EQ(NULL, realpath("/tmp/file/", buffer));
EXPECT_EQ(ENOTDIR, errno);
EXPECT_EQ(NULL, realpath(NULL, buffer));
EXPECT_EQ(EINVAL, errno);
// Test relative paths.
EXPECT_EQ(0, chdir("/tmp"));
EXPECT_STREQ("/", realpath("..", buffer));
EXPECT_STREQ("/tmp", realpath(".", buffer));
EXPECT_STREQ("/tmp", realpath("./", buffer));
EXPECT_STREQ("/tmp", realpath("bar/..", buffer));
EXPECT_STREQ("/tmp", realpath("bar/../../tmp", buffer));
EXPECT_STREQ("/tmp", realpath(".///", buffer));
EXPECT_STREQ("/tmp/bar", realpath("bar", buffer));
// Test when resolved_path is allocated.
char* allocated = realpath("/tmp", NULL);
EXPECT_STREQ("/tmp", allocated);
free(allocated);
}
#endif // defined(__native_client__) || defined(STANDALONE)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment