feat(unistd): cwd/link/readlink/path ops
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef VLIBC_LIBGEN_H
|
||||||
|
#define VLIBC_LIBGEN_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <libgen.h>.
|
||||||
|
*
|
||||||
|
* basename() and dirname() split a pathname into its final component and
|
||||||
|
* the directory that contains it (XSI, declared when _XOPEN_SOURCE is
|
||||||
|
* enabled in glibc — level 2 here). They are the XSI forms: the input
|
||||||
|
* path is modified in place (callers must pass a writable buffer), and
|
||||||
|
* both are allowed to return pointers into a shared static area, so
|
||||||
|
* repeated calls overwrite earlier results.
|
||||||
|
*
|
||||||
|
* The two functions are deliberately declared here and nowhere else:
|
||||||
|
* POSIX places them in <libgen.h> only.
|
||||||
|
*
|
||||||
|
* Neither declaration carries an intent attribute: both functions write
|
||||||
|
* to and return pointers into their argument or a shared static buffer,
|
||||||
|
* so const/pure would be unsound.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the final component of path, modifying path in
|
||||||
|
* place: trailing '/' characters (other than a leading root '/') are
|
||||||
|
* overwritten with NUL so basename("/usr/lib") == "lib", basename("/")
|
||||||
|
* == "/" and basename("") == ".". The result may be a suffix of path or
|
||||||
|
* point into a shared static buffer (POSIX permits either); the special
|
||||||
|
* cases "/" and "" return static strings.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
basename(char *path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the directory portion of path, modifying path in
|
||||||
|
* place: the last '/' that is not the final character is overwritten
|
||||||
|
* with NUL so dirname("/usr/lib") == "/usr", dirname("/") == "/" and
|
||||||
|
* dirname("") == ".". The result may be a suffix of path or point into
|
||||||
|
* a shared static buffer (POSIX permits either); the special cases
|
||||||
|
* return static strings.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
dirname(char *path);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_LIBGEN_H */
|
||||||
+209
-1
@@ -31,7 +31,21 @@
|
|||||||
* configuration keys.
|
* configuration keys.
|
||||||
* Level 2 (muslmimic): gethostname, sethostname, gethostid (XSI/BSD
|
* Level 2 (muslmimic): gethostname, sethostname, gethostid (XSI/BSD
|
||||||
* system identification — NOT POSIX.1-2008 base),
|
* system identification — NOT POSIX.1-2008 base),
|
||||||
* and the _CS_* keys for todo 38's confstr().
|
* and the _CS_* keys for confstr().
|
||||||
|
*
|
||||||
|
* The current-working-directory, link/symlink/readlink, path-removal and
|
||||||
|
* *at operations, the terminal-identity and login functions, getopt and
|
||||||
|
* confstr (todo 38) come after the system-limits block:
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): getcwd, chdir, fchdir, readlink, symlink, link,
|
||||||
|
* unlink, rmdir, linkat, readlinkat, renameat,
|
||||||
|
* symlinkat, unlinkat, isatty, ttyname, ttyname_r,
|
||||||
|
* getlogin, getlogin_r, getopt plus the optarg/
|
||||||
|
* optind/opterr/optopt globals, and confstr (its
|
||||||
|
* _CS_* key constants are level 2, above).
|
||||||
|
* Level 2 (muslmimic): getwd (obsolescent), chroot, swab (XSI).
|
||||||
|
* basename/dirname live in <libgen.h> and realpath
|
||||||
|
* in <stdlib.h> (their POSIX homes), also XSI.
|
||||||
*
|
*
|
||||||
* uname() and struct utsname live in <sys/utsname.h> (todo 32).
|
* uname() and struct utsname live in <sys/utsname.h> (todo 32).
|
||||||
*
|
*
|
||||||
@@ -474,9 +488,203 @@ pathconf(const char *path, int name);
|
|||||||
long
|
long
|
||||||
fpathconf(int fildes, int name);
|
fpathconf(int fildes, int name);
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base): current directory, links, and path operations. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the absolute pathname of the current working directory into buf.
|
||||||
|
* When buf is NULL, the path is returned in a malloc'd buffer instead
|
||||||
|
* (size is then only a hint: 0 grows from a default of 128 bytes, and
|
||||||
|
* the buffer is enlarged as the kernel reports ERANGE); the caller must
|
||||||
|
* free it. Return buf (or the malloc'd buffer), or NULL with errno set
|
||||||
|
* (EINVAL for a non-NULL buf with size 0).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
getcwd(char *buf, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the current working directory to path (chdir) or to the
|
||||||
|
* directory referred to by the open descriptor fildes (fchdir). Return
|
||||||
|
* 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chdir(const char *path);
|
||||||
|
|
||||||
|
int
|
||||||
|
fchdir(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a symbolic link: path2 is created as a link whose contents are
|
||||||
|
* the string path1 (which need not exist). Return 0, or -1 with errno
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
symlink(const char *path1, const char *path2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like symlink(), but the link is created in the directory named by fd
|
||||||
|
* when path2 is relative (use AT_FDCWD for the current working
|
||||||
|
* directory). Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
symlinkat(const char *path1, int fd, const char *path2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the contents of the symbolic link path into buf (bufsize bytes,
|
||||||
|
* no NUL is appended) and return the number of bytes placed in buf, 0
|
||||||
|
* for an empty target, or -1 with errno set. The buffer is not
|
||||||
|
* terminated: callers sized for bufsize + 1 append the NUL themselves.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
readlink(const char *restrict path, char *restrict buf, size_t bufsize);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like readlink(), but path is resolved in the directory named by fd
|
||||||
|
* when relative. Return the number of bytes placed in buf, or -1 with
|
||||||
|
* errno set.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
readlinkat(int fd, const char *restrict path, char *restrict buf, size_t bufsize);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a hard link: new is created as a second name for the file old
|
||||||
|
* (which must not be a directory and must be on the same filesystem).
|
||||||
|
* Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
link(const char *old, const char *new);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like link(), but old and new are resolved against the directories
|
||||||
|
* named by fd1 and fd2 when relative (AT_FDCWD selects the current
|
||||||
|
* working directory) and flag may hold AT_SYMLINK_FOLLOW from <fcntl.h>.
|
||||||
|
* Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
linkat(int fd1, const char *old, int fd2, const char *new, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the name path (a hard link; symbolic links are removed, not
|
||||||
|
* followed). Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
unlink(const char *path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like unlink(), but path is resolved in the directory named by fd when
|
||||||
|
* relative and flag may hold AT_REMOVEDIR from <fcntl.h> (making it
|
||||||
|
* remove an empty directory instead). Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
unlinkat(int fd, const char *path, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the empty directory path. Return 0, or -1 with errno set
|
||||||
|
* (ENOTEMPTY/EEXIST when the directory is not empty).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
rmdir(const char *path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rename old to new within the filesystem: old is resolved against fd1
|
||||||
|
* and new against fd2 (AT_FDCWD for the current working directory). Both
|
||||||
|
* names may be directories. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
renameat(int fd1, const char *old, int fd2, const char *new);
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base): terminal identity, login name, getopt, confstr. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test whether fildes refers to a terminal. Return 1 when it does, and
|
||||||
|
* 0 otherwise (errno may be set — e.g. EBADF — but the caller must not
|
||||||
|
* rely on it after a 0 return).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
isatty(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the pathname of the terminal open on fildes in a shared static
|
||||||
|
* buffer (overwritten by the next call), or NULL with errno set when
|
||||||
|
* fildes is not a terminal. ttyname_r stores the path in the caller's
|
||||||
|
* name buffer instead and returns 0, or an error NUMBER (ENOTTY when
|
||||||
|
* fildes is not a terminal, ERANGE when the name does not fit in
|
||||||
|
* namesize).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
ttyname(int fildes);
|
||||||
|
|
||||||
|
int
|
||||||
|
ttyname_r(int fildes, char *name, size_t namesize);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the login name of the user associated with the calling process
|
||||||
|
* (the owner of its controlling terminal) in a shared static buffer, or
|
||||||
|
* NULL with errno set when the name cannot be determined. getlogin_r
|
||||||
|
* stores the name in the caller's buffer and returns 0, or an error
|
||||||
|
* number (ERANGE when the name does not fit in namesize).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
getlogin(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
getlogin_r(char *name, size_t namesize);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command-line option parsing (POSIX.1-2008). getopt returns the next
|
||||||
|
* option character from argv per the option characters in optstring; a
|
||||||
|
* character followed by ':' in optstring takes an argument (delivered in
|
||||||
|
* optarg). A leading ':' in optstring makes missing-argument errors
|
||||||
|
* return ':' silently instead of '?' with a diagnostic to stderr.
|
||||||
|
* Return -1 when all options have been consumed: optind then points at
|
||||||
|
* the first non-option argument, and "--" terminates the scan.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getopt(int argc, char *const argv[], const char *optstring);
|
||||||
|
|
||||||
|
/* The getopt option state (POSIX.1-2008 base). */
|
||||||
|
extern char *optarg; /* the argument of the option just returned */
|
||||||
|
extern int optind; /* index of the next argument to scan (init 1) */
|
||||||
|
extern int opterr; /* nonzero: write diagnostics to stderr (init 1) */
|
||||||
|
extern int optopt; /* the option character that caused an error */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query the implementation-defined string value of configuration name
|
||||||
|
* (one of the _CS_* keys above). When buf is not NULL and len is large
|
||||||
|
* enough, the value (NUL-terminated) is copied into buf; the return
|
||||||
|
* value is the length of the value including the terminating NUL, so a
|
||||||
|
* return greater than len means the buffer was too small and nothing was
|
||||||
|
* copied. For an unknown name, 0 is returned and errno is set to EINVAL.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
confstr(int name, char *buf, size_t len);
|
||||||
|
|
||||||
#if VLIBC_LEVEL_GE(2)
|
#if VLIBC_LEVEL_GE(2)
|
||||||
/* Level 2 (muslmimic): Linux extensions + XSI + obsolescent. */
|
/* Level 2 (muslmimic): Linux extensions + XSI + obsolescent. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the current working directory into pathname, which must hold at
|
||||||
|
* least PATH_MAX bytes. Return pathname, or NULL with errno set.
|
||||||
|
* Obsolescent: prefer getcwd().
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
getwd(char *pathname);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the process root directory to path (used with chdir by
|
||||||
|
* chroot-style confinement). Requires privilege. Return 0, or -1 with
|
||||||
|
* errno set. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chroot(const char *path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy nbytes bytes from from to to, swapping each adjacent pair of
|
||||||
|
* bytes (the odd byte of an odd count is not copied). Used for historic
|
||||||
|
* byte-order conversion; returns nothing. XSI.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
swab(const void *restrict from, void *restrict to, ssize_t nbytes);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Like dup2(), but with descriptor flags (O_CLOEXEC from <fcntl.h>) applied
|
* Like dup2(), but with descriptor flags (O_CLOEXEC from <fcntl.h>) applied
|
||||||
* atomically; return fildes2, or -1 with errno set. Linux-specific.
|
* atomically; return fildes2, or -1 with errno set. Linux-specific.
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <libgen.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — basename (todo 38), XSI form.
|
||||||
|
*
|
||||||
|
* Return the final component of path, modifying path in place: every
|
||||||
|
* trailing '/' (except one that is the entire name) is overwritten with
|
||||||
|
* NUL and the result is the text after the last remaining '/'. The two
|
||||||
|
* degenerate names return static strings — "/" (and "//") yields "/" and
|
||||||
|
* the empty string yields "." — everything else is a suffix of the
|
||||||
|
* (modified) argument. Callers must pass a writable buffer and must not
|
||||||
|
* hold the result across another libgen call, exactly as <libgen.h>
|
||||||
|
* documents.
|
||||||
|
*
|
||||||
|
* Whole file gated at level 2: basename/dirname are XSI, and the header
|
||||||
|
* deliberately declares them nowhere else (POSIX places them in
|
||||||
|
* <libgen.h> only, unlike the GNU string.h variants this libc does not
|
||||||
|
* provide).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
char *
|
||||||
|
basename(char *path)
|
||||||
|
{
|
||||||
|
static const char dot[2] = ".";
|
||||||
|
static const char slash[2] = "/";
|
||||||
|
char *end;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (path == NULL || *path == '\0')
|
||||||
|
{
|
||||||
|
return (char *)dot;
|
||||||
|
}
|
||||||
|
end = path + strlen(path) - 1;
|
||||||
|
while (end > path && *end == '/')
|
||||||
|
{
|
||||||
|
*end = '\0'; /* strip trailing slashes (never the leading one) */
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
if (*path == '\0')
|
||||||
|
{
|
||||||
|
return (char *)slash; /* the name was nothing but slashes */
|
||||||
|
}
|
||||||
|
p = strrchr(path, '/');
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return path; /* no slash: the whole name is the final component */
|
||||||
|
}
|
||||||
|
if (p[1] == '\0')
|
||||||
|
{
|
||||||
|
return (char *)slash; /* p is the lone leading root slash */
|
||||||
|
}
|
||||||
|
return p + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* chdir/fchdir: change the current working directory. chdir resolves
|
||||||
|
* path through the filesystem; fchdir switches to the directory the open
|
||||||
|
* descriptor fildes refers to (it need not have been opened with
|
||||||
|
* O_DIRECTORY — any descriptor of a directory works). Both report 0 on
|
||||||
|
* success and -1 with errno set through syscall_ret.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chdir(const char *path)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_chdir, (long)path));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fchdir(int fildes)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_fchdir, fildes));
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — chroot (todo 38).
|
||||||
|
*
|
||||||
|
* Change the process' notion of its filesystem root to path, which must
|
||||||
|
* be a directory. Requires privilege (CAP_SYS_CHROOT); an unprivileged
|
||||||
|
* call fails with EPERM. The directory becomes the "/" of a confined
|
||||||
|
* process that can no longer reach anything above it — the classic
|
||||||
|
* sandbox primitive, XSI. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
int
|
||||||
|
chroot(const char *path)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_chroot, (long)path));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — confstr (todo 38).
|
||||||
|
*
|
||||||
|
* Query an implementation-defined string configuration value. The whole
|
||||||
|
* file is gated at level 2 because every _CS_* key it serves lives in the
|
||||||
|
* level-2 block of <unistd.h> (confstr itself is POSIX base, but it is
|
||||||
|
* useless below the first key, so the TU is wired to the L2 source list).
|
||||||
|
*
|
||||||
|
* Values, per the key table in <unistd.h>:
|
||||||
|
*
|
||||||
|
* _CS_PATH: a usable default PATH. "/bin:/usr/bin" is the classic
|
||||||
|
* choice (musl and glibc both report it) and always finds
|
||||||
|
* the standard utilities on the filesystems this libc runs
|
||||||
|
* on, so programs get a sane environment when none is set.
|
||||||
|
* _CS_POSIX_V6/V7_WIDTH_RESTRICTED_ENVS: "1" — the width-restricted
|
||||||
|
* environment (which reserves argv/envp names for utilities
|
||||||
|
* that manipulate file descriptors) is provided.
|
||||||
|
* _CS_LFS_*: "" — 64-bit file offsets are the native ABI here, so no
|
||||||
|
* compile/link/lint flags are needed (glibc reports the same
|
||||||
|
* on 64-bit targets).
|
||||||
|
* _CS_GNU_LIBC_VERSION / _CS_GNU_LIBPTHREAD_VERSION: "" — vlibc is not
|
||||||
|
* glibc and carries no glibc-version compatibility claim;
|
||||||
|
* the keys exist so version-probing code gets a clean "not
|
||||||
|
* glibc" answer instead of garbage.
|
||||||
|
*
|
||||||
|
* Return semantics are POSIX: the length of the value including its
|
||||||
|
* terminating NUL; when buf is large enough the value is copied in.
|
||||||
|
* An unknown name returns 0 with errno set to EINVAL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
size_t
|
||||||
|
confstr(int name, char *buf, size_t len)
|
||||||
|
{
|
||||||
|
const char *value;
|
||||||
|
size_t need;
|
||||||
|
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case _CS_PATH:
|
||||||
|
value = "/bin:/usr/bin";
|
||||||
|
break;
|
||||||
|
case _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS:
|
||||||
|
case _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS:
|
||||||
|
value = "1";
|
||||||
|
break;
|
||||||
|
case _CS_LFS_CFLAGS:
|
||||||
|
case _CS_LFS_LDFLAGS:
|
||||||
|
case _CS_LFS_LIBS:
|
||||||
|
case _CS_LFS_LINTFLAGS:
|
||||||
|
case _CS_LFS64_CFLAGS:
|
||||||
|
case _CS_LFS64_LDFLAGS:
|
||||||
|
case _CS_LFS64_LIBS:
|
||||||
|
case _CS_LFS64_LINTFLAGS:
|
||||||
|
case _CS_GNU_LIBC_VERSION:
|
||||||
|
case _CS_GNU_LIBPTHREAD_VERSION:
|
||||||
|
value = "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
need = strlen(value) + 1;
|
||||||
|
if (buf != NULL && len >= need)
|
||||||
|
{
|
||||||
|
strcpy(buf, value);
|
||||||
|
}
|
||||||
|
return need;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <libgen.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — dirname (todo 38), XSI form.
|
||||||
|
*
|
||||||
|
* Return the directory portion of path, modifying path in place: the
|
||||||
|
* last '/' that is not the final character is overwritten with NUL, so
|
||||||
|
* the result is everything up to and including that '/'. A name with no
|
||||||
|
* '/' yields the static string "."; "/" (and "//") yields "/" and the
|
||||||
|
* empty string yields ".". Callers must pass a writable buffer and must
|
||||||
|
* not hold the result across another libgen call, exactly as <libgen.h>
|
||||||
|
* documents.
|
||||||
|
*
|
||||||
|
* Whole file gated at level 2: see src/unistd/basename.c.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
char *
|
||||||
|
dirname(char *path)
|
||||||
|
{
|
||||||
|
static const char dot[2] = ".";
|
||||||
|
char *end;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (path == NULL || *path == '\0')
|
||||||
|
{
|
||||||
|
return (char *)dot;
|
||||||
|
}
|
||||||
|
end = path + strlen(path) - 1;
|
||||||
|
while (end > path && *end == '/')
|
||||||
|
{
|
||||||
|
*end = '\0'; /* strip trailing slashes (never the leading one) */
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
if (*path == '\0')
|
||||||
|
{
|
||||||
|
/* The name was nothing but slashes: dirname("/") == "/". */
|
||||||
|
path[1] = '\0';
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
p = strrchr(path, '/');
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return (char *)dot; /* a single component has no directory part */
|
||||||
|
}
|
||||||
|
if (p == path)
|
||||||
|
{
|
||||||
|
p[1] = '\0'; /* "/name" -> "/" */
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
*p = '\0'; /* "/dir/name" -> "/dir" */
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getcwd: the absolute path of the current working directory via
|
||||||
|
* SYS_getcwd, which copies the path (NUL-terminated) into the caller's
|
||||||
|
* buffer and returns its length.
|
||||||
|
*
|
||||||
|
* Two calling forms:
|
||||||
|
* - buf != NULL: size must be nonzero (EINVAL otherwise); the kernel
|
||||||
|
* reports ERANGE when the buffer cannot hold the path.
|
||||||
|
* - buf == NULL: the path is returned in an allocation from the
|
||||||
|
* internal allocator (releasable with the public free). size is only
|
||||||
|
* a first-guess; the buffer doubles on each ERANGE until the path
|
||||||
|
* fits, starting from a default of 128 when size is 0.
|
||||||
|
*
|
||||||
|
* A successful SYS_getcwd result never needs post-processing: the kernel
|
||||||
|
* guarantees an absolute path with no "." or ".." components.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
getcwd(char *buf, size_t size)
|
||||||
|
{
|
||||||
|
if (buf != NULL)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (syscall_ret(__syscall2(SYS_getcwd, (long)buf, (long)size)) < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t cap = (size != 0) ? size : 128;;)
|
||||||
|
{
|
||||||
|
char *p = __libc_malloc(cap);
|
||||||
|
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
return NULL; /* errno ENOMEM from the allocator */
|
||||||
|
}
|
||||||
|
if (syscall_ret(__syscall2(SYS_getcwd, (long)p, (long)cap)) >= 0)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
if (errno != ERANGE || cap > (size_t)-1 / 2)
|
||||||
|
{
|
||||||
|
__libc_free(p);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
__libc_free(p);
|
||||||
|
cap *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getlogin/getlogin_r: the login name associated with the calling
|
||||||
|
* process — the user who owns its controlling terminal.
|
||||||
|
*
|
||||||
|
* POSIX.1-2008: getlogin() returns a pointer to the login name, or NULL
|
||||||
|
* when the process is not associated with a terminal; getlogin_r may
|
||||||
|
* fail with ENOTTY when the calling process has no controlling terminal.
|
||||||
|
* The "controlling terminal" is the terminal of the session the process
|
||||||
|
* belongs to; Linux reports it as field 7 (tty_nr) of /proc/self/stat
|
||||||
|
* (0 when the process has none).
|
||||||
|
*
|
||||||
|
* Resolving the NAME of that terminal's user requires the session/utmp
|
||||||
|
* database, which this libc does not yet carry (the passwd/group todo
|
||||||
|
* owns /etc/passwd and the session records). This stage therefore never
|
||||||
|
* fabricates a name: it distinguishes the no-terminal case (ENOTTY,
|
||||||
|
* fully specified by POSIX) from the has-a-terminal-but-no-database
|
||||||
|
* case (ENOENT), and getlogin() reports both as NULL with errno set.
|
||||||
|
* The name resolution is completed by the session-database todo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Static buffer for getlogin(); 256 bytes comfortably holds a login name. */
|
||||||
|
#define VLIBC_UNISTD_LOGIN_BUF 256
|
||||||
|
|
||||||
|
static char getlogin_buf[VLIBC_UNISTD_LOGIN_BUF];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read field 7 (tty_nr) of /proc/self/stat. After the parenthesized
|
||||||
|
* comm field — located by scanning back from the end for the last ')' —
|
||||||
|
* the fields run state(3) ppid(4) pgrp(5) session(6) tty_nr(7), so
|
||||||
|
* tty_nr is the token after four more whitespace-separated tokens.
|
||||||
|
* Returns 1 and the value when the file could be read, 0 otherwise.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
login_tty_nr(long *tty_out)
|
||||||
|
{
|
||||||
|
char buf[512];
|
||||||
|
char *p;
|
||||||
|
ssize_t n;
|
||||||
|
int fd;
|
||||||
|
int tok;
|
||||||
|
long value = 0;
|
||||||
|
int sign = 1;
|
||||||
|
int have_digit = 0;
|
||||||
|
|
||||||
|
fd = open("/proc/self/stat", O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
n = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
close(fd);
|
||||||
|
if (n <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
buf[n] = '\0';
|
||||||
|
|
||||||
|
p = buf + n;
|
||||||
|
while (p > buf && *p != ')')
|
||||||
|
{
|
||||||
|
p--;
|
||||||
|
}
|
||||||
|
if (*p != ')')
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
|
||||||
|
for (tok = 0; tok < 4; tok++)
|
||||||
|
{
|
||||||
|
while (*p == ' ' || *p == '\t')
|
||||||
|
{
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
while (*p != '\0' && *p != ' ' && *p != '\t' && *p != '\n')
|
||||||
|
{
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p == '\0')
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (*p == ' ' || *p == '\t')
|
||||||
|
{
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p == '-')
|
||||||
|
{
|
||||||
|
sign = -1;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
while (*p >= '0' && *p <= '9')
|
||||||
|
{
|
||||||
|
value = value * 10 + (*p - '0');
|
||||||
|
p++;
|
||||||
|
have_digit = 1;
|
||||||
|
}
|
||||||
|
if (!have_digit)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*tty_out = sign * value;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getlogin_r(char *name, size_t namesize)
|
||||||
|
{
|
||||||
|
long tty_nr;
|
||||||
|
|
||||||
|
if (name == NULL || namesize == 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
if (!login_tty_nr(&tty_nr) || tty_nr == 0)
|
||||||
|
{
|
||||||
|
/* No controlling terminal (or procfs cannot be read). */
|
||||||
|
errno = ENOTTY;
|
||||||
|
return ENOTTY;
|
||||||
|
}
|
||||||
|
/* A controlling terminal exists, but no session database to resolve
|
||||||
|
* its owner's login name yet — never fabricate one. */
|
||||||
|
errno = ENOENT;
|
||||||
|
return ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
getlogin(void)
|
||||||
|
{
|
||||||
|
int err = getlogin_r(getlogin_buf, sizeof(getlogin_buf));
|
||||||
|
|
||||||
|
if (err != 0)
|
||||||
|
{
|
||||||
|
errno = err;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return getlogin_buf;
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — getopt (todo 38).
|
||||||
|
*
|
||||||
|
* POSIX.1-2008 option scanning. getopt returns the next option character
|
||||||
|
* from argv per the letters of optstring; a letter followed by ':' in
|
||||||
|
* optstring takes an argument, delivered in optarg. Scanning rules:
|
||||||
|
*
|
||||||
|
* - "--" terminates the scan and is itself consumed (optind moves past
|
||||||
|
* it); a lone "-" or any argument not starting with '-' is a non-
|
||||||
|
* option argument that stops the scan with optind pointing at it.
|
||||||
|
* - Options and their arguments may be bundled ("-abc" is -a -b -c);
|
||||||
|
* an option that takes an argument consumes the rest of its element
|
||||||
|
* ("-oarg") or, when none remains, the whole next element ("-o arg").
|
||||||
|
* - A leading ':' in optstring selects the silent error mode: a missing
|
||||||
|
* argument returns ':' instead of '?' and no diagnostic is written.
|
||||||
|
* Otherwise errors return '?' and, when opterr is nonzero, print a
|
||||||
|
* diagnostic to standard error naming argv[0]. optopt holds the
|
||||||
|
* character that caused the error.
|
||||||
|
* - The scan is not permuted: the first non-option argument ends it,
|
||||||
|
* exactly as POSIX permits.
|
||||||
|
*
|
||||||
|
* optind is the index of the next argument to examine, starting at 1.
|
||||||
|
* This TU owns the four public option globals, which <unistd.h> declares
|
||||||
|
* extern; no other level gate applies — getopt is POSIX base.
|
||||||
|
*
|
||||||
|
* Diagnostics go to fd 2 through the raw syscall layer (the house idiom,
|
||||||
|
* see src/internal/assert_fail.c) so this TU needs no stdio.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The character to return next within the current element, or NULL when
|
||||||
|
* no element is being scanned (optind then names the next one). */
|
||||||
|
static char *getopt_place;
|
||||||
|
|
||||||
|
/* The argument of the option just returned (NULL when it takes none). */
|
||||||
|
char *optarg;
|
||||||
|
|
||||||
|
/* Index of the next argument to scan; starts at 1 (argv[0] is the
|
||||||
|
* program name). */
|
||||||
|
int optind = 1;
|
||||||
|
|
||||||
|
/* Nonzero: write a diagnostic to standard error on an error. */
|
||||||
|
int opterr = 1;
|
||||||
|
|
||||||
|
/* The option character that caused the most recent error. */
|
||||||
|
int optopt;
|
||||||
|
|
||||||
|
/* Append the NUL-terminated string s at p; return the next free slot. */
|
||||||
|
static char *
|
||||||
|
getopt_str(char *p, const char *s, const char *end)
|
||||||
|
{
|
||||||
|
while (*s != '\0' && p < end)
|
||||||
|
{
|
||||||
|
*p++ = *s++;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write a diagnostic of the standard shape to fd 2: "prog: invalid
|
||||||
|
* option -- c" or "prog: option requires an argument -- c", plus a
|
||||||
|
* newline. prog may be NULL (no name prefix then).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
getopt_diag(const char *prog, int missing_arg, int c)
|
||||||
|
{
|
||||||
|
char msg[160];
|
||||||
|
char *p = msg;
|
||||||
|
const char *const end = msg + sizeof(msg) - 1;
|
||||||
|
|
||||||
|
if (prog != NULL)
|
||||||
|
{
|
||||||
|
p = getopt_str(p, prog, end);
|
||||||
|
if (p < end - 1)
|
||||||
|
{
|
||||||
|
*p++ = ':';
|
||||||
|
*p++ = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = getopt_str(p, missing_arg ? "option requires an argument -- " : "invalid option -- ", end);
|
||||||
|
if (p < end)
|
||||||
|
{
|
||||||
|
*p++ = (char)c;
|
||||||
|
}
|
||||||
|
if (p < end)
|
||||||
|
{
|
||||||
|
*p++ = '\n';
|
||||||
|
}
|
||||||
|
(void)__syscall3(SYS_write, 2, (long)msg, (long)(p - msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getopt(int argc, char *const argv[], const char *optstring)
|
||||||
|
{
|
||||||
|
const char *oli;
|
||||||
|
char *place;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
if (optstring == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
optarg = NULL;
|
||||||
|
|
||||||
|
/* Begin scanning the element at argv[optind] when the previous one
|
||||||
|
* (if any) has been fully consumed; the consuming sites below have
|
||||||
|
* already advanced optind past it. */
|
||||||
|
if (getopt_place == NULL)
|
||||||
|
{
|
||||||
|
if (optind >= argc)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
place = argv[optind];
|
||||||
|
if (place == NULL || place[0] != '-' || place[1] == '\0')
|
||||||
|
{
|
||||||
|
/* A non-option argument (or a lone "-") ends the scan with
|
||||||
|
* optind left on it. */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (place[1] == '-' && place[2] == '\0')
|
||||||
|
{
|
||||||
|
optind++; /* "--" is consumed and ends the scan */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
getopt_place = place + 1; /* scan the letters after the '-' */
|
||||||
|
}
|
||||||
|
|
||||||
|
place = getopt_place;
|
||||||
|
c = (unsigned char)*place++;
|
||||||
|
getopt_place = place;
|
||||||
|
optopt = c;
|
||||||
|
|
||||||
|
/* Is c a listed option? (':' never is: it is reserved for the
|
||||||
|
* leading error flag.) */
|
||||||
|
if (c == ':')
|
||||||
|
{
|
||||||
|
oli = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
oli = strchr(optstring, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oli == NULL)
|
||||||
|
{
|
||||||
|
/* Unrecognized option. */
|
||||||
|
if (*place == '\0')
|
||||||
|
{
|
||||||
|
optind++; /* the element is fully consumed */
|
||||||
|
getopt_place = NULL;
|
||||||
|
}
|
||||||
|
if (opterr != 0 && optstring[0] != ':')
|
||||||
|
{
|
||||||
|
getopt_diag(argv[0], 0, c);
|
||||||
|
}
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oli[1] != ':')
|
||||||
|
{
|
||||||
|
/* c takes no argument. */
|
||||||
|
if (*place == '\0')
|
||||||
|
{
|
||||||
|
optind++;
|
||||||
|
getopt_place = NULL;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* c takes an argument. */
|
||||||
|
if (*place != '\0')
|
||||||
|
{
|
||||||
|
optarg = place; /* the rest of this element is the argument */
|
||||||
|
optind++; /* the element is fully consumed */
|
||||||
|
getopt_place = NULL;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
optind++; /* the option element is consumed; the argument is next */
|
||||||
|
if (optind >= argc)
|
||||||
|
{
|
||||||
|
/* No argument available. */
|
||||||
|
getopt_place = NULL;
|
||||||
|
if (optstring[0] == ':')
|
||||||
|
{
|
||||||
|
return ':';
|
||||||
|
}
|
||||||
|
if (opterr != 0)
|
||||||
|
{
|
||||||
|
getopt_diag(argv[0], 1, c);
|
||||||
|
}
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
optarg = argv[optind];
|
||||||
|
optind++; /* the argument element is consumed */
|
||||||
|
getopt_place = NULL;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — getwd (todo 38).
|
||||||
|
*
|
||||||
|
* Obsolescent [OB] form of getcwd: store the current working directory
|
||||||
|
* into the caller's pathname buffer and return it. XSI and legacy
|
||||||
|
* programs expect pathname to hold PATH_MAX bytes (this file uses 4096,
|
||||||
|
* the x86_64 _PC_PATH_MAX value; <limits.h> does not define PATH_MAX
|
||||||
|
* yet). getcwd performs the actual work and reports failures with NULL
|
||||||
|
* and errno set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* The caller's buffer is conventionally PATH_MAX bytes. */
|
||||||
|
#define VLIBC_GETWD_PATH_MAX 4096
|
||||||
|
|
||||||
|
char *
|
||||||
|
getwd(char *pathname)
|
||||||
|
{
|
||||||
|
if (pathname == NULL)
|
||||||
|
{
|
||||||
|
return NULL; /* getcwd(NULL, 0) semantics would malloc: do not */
|
||||||
|
}
|
||||||
|
return getcwd(pathname, VLIBC_GETWD_PATH_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* isatty: report whether fildes is associated with a terminal.
|
||||||
|
*
|
||||||
|
* The probe is the kernel's own: issue the TCGETS ioctl, which a real
|
||||||
|
* terminal answers with 0 and everything else rejects with -ENOTTY (a
|
||||||
|
* regular file, directory, pipe, socket, or a character device that is
|
||||||
|
* not a terminal such as /dev/null). A char-device fstat check is NOT
|
||||||
|
* sufficient — /dev/null is a character device but not a tty — so the
|
||||||
|
* ioctl is the correct test.
|
||||||
|
*
|
||||||
|
* The result is derived from the raw syscall return (negative -errno on
|
||||||
|
* failure), so isatty never writes errno: POSIX permits an errno set on
|
||||||
|
* a 0 return but does not require one, and skipping the write keeps the
|
||||||
|
* probe free of thread-local side effects.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
isatty(int fildes)
|
||||||
|
{
|
||||||
|
struct termios t;
|
||||||
|
|
||||||
|
return __syscall3(SYS_ioctl, fildes, 0x5401 /* TCGETS */, (long)&t) == 0 ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* link/linkat: create a hard link — a second directory entry for the
|
||||||
|
* same inode. link(old, new) passes both names to the kernel; linkat
|
||||||
|
* resolves old against fd1 and new against fd2 when they are relative
|
||||||
|
* (AT_FDCWD for the current working directory) and honors flag
|
||||||
|
* (AT_SYMLINK_FOLLOW makes the kernel follow an old that is a symbolic
|
||||||
|
* link; the default 0 links the link itself). Return 0, or -1 with
|
||||||
|
* errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
link(const char *old, const char *new)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_link, (long)old, (long)new));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
linkat(int fd1, const char *old, int fd2, const char *new, int flag)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall5(SYS_linkat, fd1, (long)old, fd2, (long)new, flag));
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* readlink/readlinkat: copy the contents of the symbolic link path (the
|
||||||
|
* string the link stores, which may name anything) into buf. readlinkat
|
||||||
|
* resolves a relative path against the directory fd. The kernel writes
|
||||||
|
* up to bufsize bytes and never appends a NUL; the return value is the
|
||||||
|
* byte count written (0 for an empty target), or -1 with errno set when
|
||||||
|
* path is not a symbolic link. When bufsize is 0, buf may be NULL.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
readlink(const char *restrict path, char *restrict buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_readlink, (long)path, (long)buf, (long)bufsize));
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
readlinkat(int fd, const char *restrict path, char *restrict buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_readlinkat, fd, (long)path, (long)buf, (long)bufsize));
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — realpath (todo 38), XSI, declared in <stdlib.h>.
|
||||||
|
*
|
||||||
|
* Resolve path to its canonical absolute form: no symbolic links, no
|
||||||
|
* "." or ".." components, no trailing slashes (except the root's own).
|
||||||
|
*
|
||||||
|
* The resolution walks path one component at a time, building the
|
||||||
|
* canonical directory prefix in a working buffer and validating every
|
||||||
|
* component against the kernel with lstat:
|
||||||
|
*
|
||||||
|
* - "." components are dropped and ".." pops the last built component
|
||||||
|
* (popping at the root keeps the root — ".." cannot climb above it);
|
||||||
|
* - any other component is appended and lstat'ed. A symbolic link is
|
||||||
|
* read with readlink, the appended component is unwound, and the
|
||||||
|
* link's target is walked in its place before the rest of path is
|
||||||
|
* resumed: an absolute target restarts the prefix at the root, a
|
||||||
|
* relative target extends it. The shared depth counter bounds the
|
||||||
|
* total links followed at 40 (the _SC_SYMLOOP_MAX value), which also
|
||||||
|
* caps the recursion.
|
||||||
|
*
|
||||||
|
* Every intermediate and final name is therefore stat'ed exactly as the
|
||||||
|
* kernel would see it, so a missing component fails with the kernel's
|
||||||
|
* own error (ENOENT, ENOTDIR, ...) and the result always names an
|
||||||
|
* existing directory chain.
|
||||||
|
*
|
||||||
|
* A relative path is anchored at the current working directory (from
|
||||||
|
* getcwd, itself canonical), so the leading ".." of e.g. "../../x" pops
|
||||||
|
* correctly against a full canonical prefix.
|
||||||
|
*
|
||||||
|
* Memory: resolved_path may be NULL (the result is then returned in a
|
||||||
|
* malloc'd buffer the caller frees) or a caller buffer of at least
|
||||||
|
* PATH_MAX bytes. The working buffer holds at most PATH_MAX (4096, the
|
||||||
|
* x86_64 _PC_PATH_MAX value); a longer intermediate name is reported as
|
||||||
|
* ENAMETOOLONG. The result is copied into resolved_path only at the end,
|
||||||
|
* so resolved_path may alias path.
|
||||||
|
*
|
||||||
|
* Whole file gated at level 2: realpath is XSI.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* The caller's buffer and the working buffer are conventionally
|
||||||
|
* PATH_MAX bytes; 4096 is the x86_64 _PC_PATH_MAX value. */
|
||||||
|
#define VLIBC_REALPATH_MAX 4096
|
||||||
|
|
||||||
|
/* _SC_SYMLOOP_MAX: the kernel's bound on symlinks followed per name. */
|
||||||
|
#define VLIBC_REALPATH_SYMLOOP 40
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drop the final "/component" from the absolute prefix out (length *len,
|
||||||
|
* which includes the NUL-terminator slot). Popping at the root leaves
|
||||||
|
* the root.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
rp_pop(char *out, size_t *len)
|
||||||
|
{
|
||||||
|
size_t i = *len - 1;
|
||||||
|
|
||||||
|
while (i > 0 && out[i] != '/')
|
||||||
|
{
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
out[1] = '\0';
|
||||||
|
*len = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out[i] = '\0';
|
||||||
|
*len = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append "/seg" (seglen bytes, no slash) to the prefix out. The root
|
||||||
|
* needs no separator. Return 0, or -1 with errno ENAMETOOLONG when the
|
||||||
|
* growing name would overflow the buffer. */
|
||||||
|
static int
|
||||||
|
rp_append(char *out, size_t *len, const char *seg, size_t seglen, size_t cap)
|
||||||
|
{
|
||||||
|
size_t need = (*len > 1 ? *len + 1 : 1) + seglen;
|
||||||
|
|
||||||
|
if (need + 1 > cap)
|
||||||
|
{
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (*len > 1)
|
||||||
|
{
|
||||||
|
out[(*len)++] = '/';
|
||||||
|
}
|
||||||
|
memcpy(out + *len, seg, seglen);
|
||||||
|
*len += seglen;
|
||||||
|
out[*len] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Consume the components of the path string in, appending the resolved
|
||||||
|
* result to the absolute prefix out (length *len). depth counts the
|
||||||
|
* symbolic links followed so far and bounds both the chain length and
|
||||||
|
* the recursion. Return 0 on success, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
rp_walk(char *out, size_t *len, const char *in, int *depth, size_t cap)
|
||||||
|
{
|
||||||
|
const char *p = in;
|
||||||
|
|
||||||
|
while (*p != '\0')
|
||||||
|
{
|
||||||
|
const char *seg;
|
||||||
|
size_t seglen;
|
||||||
|
|
||||||
|
while (*p == '/')
|
||||||
|
{
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
seg = p;
|
||||||
|
while (*p != '\0' && *p != '/')
|
||||||
|
{
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
seglen = (size_t)(p - seg);
|
||||||
|
if (seglen == 0)
|
||||||
|
{
|
||||||
|
break; /* nothing but trailing slashes */
|
||||||
|
}
|
||||||
|
if (seglen == 1 && seg[0] == '.')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seglen == 2 && seg[0] == '.' && seg[1] == '.')
|
||||||
|
{
|
||||||
|
rp_pop(out, len);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
size_t saved = *len;
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
if (rp_append(out, len, seg, seglen, cap) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (lstat(out, &st) != 0)
|
||||||
|
{
|
||||||
|
return -1; /* missing or unreadable: errno from the kernel */
|
||||||
|
}
|
||||||
|
if (S_ISLNK(st.st_mode))
|
||||||
|
{
|
||||||
|
char *target = __libc_malloc(cap);
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
if (target == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (*depth >= VLIBC_REALPATH_SYMLOOP)
|
||||||
|
{
|
||||||
|
errno = ELOOP;
|
||||||
|
__libc_free(target);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
(*depth)++;
|
||||||
|
n = readlink(out, target, cap);
|
||||||
|
if (n < 0 || (size_t)n >= cap)
|
||||||
|
{
|
||||||
|
if (n >= 0)
|
||||||
|
{
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
}
|
||||||
|
__libc_free(target);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
target[n] = '\0';
|
||||||
|
|
||||||
|
/* Unwind the appended component and walk the target in
|
||||||
|
* its place; an absolute target restarts the prefix. */
|
||||||
|
out[saved] = '\0';
|
||||||
|
*len = saved;
|
||||||
|
if (target[0] == '/')
|
||||||
|
{
|
||||||
|
out[0] = '/';
|
||||||
|
out[1] = '\0';
|
||||||
|
*len = 1;
|
||||||
|
}
|
||||||
|
if (rp_walk(out, len, target, depth, cap) != 0)
|
||||||
|
{
|
||||||
|
__libc_free(target);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
__libc_free(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
realpath(const char *restrict path, char *restrict resolved_path)
|
||||||
|
{
|
||||||
|
char *work;
|
||||||
|
size_t len;
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
if (path == NULL || *path == '\0')
|
||||||
|
{
|
||||||
|
errno = path == NULL ? EINVAL : ENOENT;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
work = (char *)__libc_malloc(VLIBC_REALPATH_MAX);
|
||||||
|
if (work == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path[0] == '/')
|
||||||
|
{
|
||||||
|
work[0] = '/';
|
||||||
|
work[1] = '\0';
|
||||||
|
len = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char *cwd = getcwd(NULL, 0);
|
||||||
|
|
||||||
|
if (cwd == NULL)
|
||||||
|
{
|
||||||
|
__libc_free(work);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
len = strlen(cwd);
|
||||||
|
if (len + 1 > VLIBC_REALPATH_MAX)
|
||||||
|
{
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
__libc_free(cwd);
|
||||||
|
__libc_free(work);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(work, cwd, len + 1);
|
||||||
|
__libc_free(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rp_walk(work, &len, path, &depth, VLIBC_REALPATH_MAX) != 0)
|
||||||
|
{
|
||||||
|
__libc_free(work);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolved_path == NULL)
|
||||||
|
{
|
||||||
|
return work; /* malloc'd result for the caller to free */
|
||||||
|
}
|
||||||
|
memcpy(resolved_path, work, len + 1);
|
||||||
|
__libc_free(work);
|
||||||
|
return resolved_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* renameat: rename old (resolved against fd1 when relative) to new
|
||||||
|
* (resolved against fd2 when relative); AT_FDCWD selects the current
|
||||||
|
* working directory. The kernel renames directories too and refuses to
|
||||||
|
* rename a directory onto a non-directory or across filesystems. Return
|
||||||
|
* 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
renameat(int fd1, const char *old, int fd2, const char *new)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_renameat, fd1, (long)old, fd2, (long)new));
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* rmdir: remove the empty directory path. The kernel refuses to remove
|
||||||
|
* a directory that still holds entries (ENOTEMPTY/EEXIST), a "." or
|
||||||
|
* ".." name, or a non-directory. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
rmdir(const char *path)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_rmdir, (long)path));
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — swab (todo 38).
|
||||||
|
*
|
||||||
|
* Copy nbytes bytes from from to to, swapping every adjacent pair (byte
|
||||||
|
* 0 with byte 1, byte 2 with byte 3, ...). A trailing odd byte is not
|
||||||
|
* copied. The historic byte-order converter is XSI, hence the whole-file
|
||||||
|
* level-2 gate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
void
|
||||||
|
swab(const void *restrict from, void *restrict to, ssize_t nbytes)
|
||||||
|
{
|
||||||
|
const char *f = (const char *)from;
|
||||||
|
char *t = (char *)to;
|
||||||
|
|
||||||
|
while (nbytes > 1)
|
||||||
|
{
|
||||||
|
char a = f[0];
|
||||||
|
char b = f[1];
|
||||||
|
|
||||||
|
t[0] = b;
|
||||||
|
t[1] = a;
|
||||||
|
f += 2;
|
||||||
|
t += 2;
|
||||||
|
nbytes -= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* symlink/symlinkat: create path2 (or, for symlinkat, path2 resolved
|
||||||
|
* against the directory fd when relative) as a symbolic link whose
|
||||||
|
* contents are the string path1. The kernel stores path1 verbatim; it is
|
||||||
|
* not required to name anything. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
symlink(const char *path1, const char *path2)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_symlink, (long)path1, (long)path2));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
symlinkat(const char *path1, int fd, const char *path2)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_symlinkat, (long)path1, fd, (long)path2));
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ttyname/ttyname_r: the pathname of the terminal open on fildes.
|
||||||
|
*
|
||||||
|
* The kernel does not expose an fd's path directly; Linux's procfs
|
||||||
|
* answers the question through the magic "/proc/self/fd/N" link, which
|
||||||
|
* readlink resolves to the path the descriptor was opened with (for a
|
||||||
|
* tty this is the device path such as "/dev/pts/3"). The result is
|
||||||
|
* verified against the kernel's own stat data: the path read back must
|
||||||
|
* still name the same device/inode as fildes (guards against the fd
|
||||||
|
* having been closed and reused between the readlink and the caller's
|
||||||
|
* use of the name).
|
||||||
|
*
|
||||||
|
* A descriptor that is not a terminal is reported with ENOTTY before
|
||||||
|
* procfs is consulted. Both forms return an errno NUMBER on failure
|
||||||
|
* (ttyname_r returns it directly; ttyname stores it in errno and
|
||||||
|
* returns NULL). ttyname's buffer is a shared static array overwritten
|
||||||
|
* by the next call — POSIX allows this and it is not thread-safe.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Static buffer for ttyname(); 256 covers any sane device path. */
|
||||||
|
#define VLIBC_UNISTD_TTYNAME_BUF 256
|
||||||
|
|
||||||
|
static char ttyname_buf[VLIBC_UNISTD_TTYNAME_BUF];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Format "/proc/self/fd/<decimal fd>" into out. fd is non-negative here
|
||||||
|
* (isatty rejected negative descriptors), so no sign is emitted.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
tty_proc_name(char *out, int fd)
|
||||||
|
{
|
||||||
|
static const char prefix[] = "/proc/self/fd/";
|
||||||
|
char digits[12];
|
||||||
|
int i = 0;
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
while (prefix[n] != '\0')
|
||||||
|
{
|
||||||
|
out[n] = prefix[n];
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
digits[i++] = (char)('0' + (fd % 10));
|
||||||
|
fd /= 10;
|
||||||
|
} while (fd != 0);
|
||||||
|
while (i > 0)
|
||||||
|
{
|
||||||
|
out[n++] = digits[--i];
|
||||||
|
}
|
||||||
|
out[n] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ttyname_r(int fildes, char *name, size_t namesize)
|
||||||
|
{
|
||||||
|
char procname[sizeof "/proc/self/fd/" + 11 + 1];
|
||||||
|
struct stat st_path;
|
||||||
|
struct stat st_fd;
|
||||||
|
ssize_t len;
|
||||||
|
|
||||||
|
if (!isatty(fildes))
|
||||||
|
{
|
||||||
|
return ENOTTY;
|
||||||
|
}
|
||||||
|
if (namesize == 0)
|
||||||
|
{
|
||||||
|
return ERANGE;
|
||||||
|
}
|
||||||
|
tty_proc_name(procname, fildes);
|
||||||
|
len = readlink(procname, name, namesize);
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
return errno; /* procfs unavailable or the link vanished */
|
||||||
|
}
|
||||||
|
if ((size_t)len >= namesize)
|
||||||
|
{
|
||||||
|
return ERANGE;
|
||||||
|
}
|
||||||
|
name[len] = '\0';
|
||||||
|
if (stat(name, &st_path) != 0 || fstat(fildes, &st_fd) != 0)
|
||||||
|
{
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
if (st_path.st_dev != st_fd.st_dev || st_path.st_ino != st_fd.st_ino)
|
||||||
|
{
|
||||||
|
return ENOENT; /* the path no longer names fildes' terminal */
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
ttyname(int fildes)
|
||||||
|
{
|
||||||
|
int err = ttyname_r(fildes, ttyname_buf, sizeof(ttyname_buf));
|
||||||
|
|
||||||
|
if (err != 0)
|
||||||
|
{
|
||||||
|
errno = err;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ttyname_buf;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* unlink/unlinkat: remove a directory entry. unlink(path) names the
|
||||||
|
* entry directly; unlinkat resolves a relative path against the
|
||||||
|
* directory fd and, when flag is AT_REMOVEDIR, removes the empty
|
||||||
|
* directory fd/path instead of a non-directory name. Symbolic links are
|
||||||
|
* themselves removed, never followed. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
unlink(const char *path)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_unlink, (long)path));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
unlinkat(int fd, const char *path, int flag)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_unlinkat, fd, (long)path, flag));
|
||||||
|
}
|
||||||
@@ -0,0 +1,415 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — unistd misc / libgen / realpath / confstr test (todo 38).
|
||||||
|
*
|
||||||
|
* Coverage:
|
||||||
|
*
|
||||||
|
* 1. getcwd correctness: the result matches the raw SYS_getcwd answer,
|
||||||
|
* and the getcwd(NULL, 0) malloc form returns the same string;
|
||||||
|
* 2. terminal/identity probes: isatty on invalid descriptors is 0,
|
||||||
|
* ttyname(1) is NULL or a "/..." device path, ttyname_r returns 0
|
||||||
|
* or ENOTTY;
|
||||||
|
* 3. getopt round-trips: bundled options, separate and attached
|
||||||
|
* arguments, "--" termination, silent ':' and '?' error modes, and
|
||||||
|
* optind landing on the first non-option;
|
||||||
|
* 4. basename/dirname golden cases including the trailing-slash and
|
||||||
|
* degenerate "/" / "" names;
|
||||||
|
* 5. confstr: _CS_PATH queries return a length, copy the value into a
|
||||||
|
* big buffer and report the same length when the buffer is small;
|
||||||
|
* _CS_GNU_LIBC_VERSION is the empty string;
|
||||||
|
* 6. realpath: "." and "/" resolve correctly (also in the malloc'd
|
||||||
|
* form); getwd matches getcwd; swab swaps adjacent bytes.
|
||||||
|
*
|
||||||
|
* -f mode: the failure shapes — realpath(NULL) and of a nonexistent
|
||||||
|
* name, and getcwd(buf, 0) — return NULL without crashing.
|
||||||
|
*
|
||||||
|
* The whole body is level 2 (libgen/realpath/confstr are XSI; only the
|
||||||
|
* L1 functions appear inside it); at level 1 this TU compiles to a SKIP
|
||||||
|
* runner. The main always leaves through a raw SYS_exit_group: several
|
||||||
|
* probes (ttyname, realpath, getcwd failures) write the library's errno
|
||||||
|
* slot, which collides with the host TCB, so host cleanup must never
|
||||||
|
* run after them (house pattern).
|
||||||
|
*
|
||||||
|
* No host headers are included (-Iinclude shadows them); argv arrays are
|
||||||
|
* plain writable char*[] so getopt can scan them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <vlibc/internal/test.h>
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Reset the getopt globals between scenarios. */
|
||||||
|
static void
|
||||||
|
uopt_reset(void)
|
||||||
|
{
|
||||||
|
optarg = NULL;
|
||||||
|
optind = 1;
|
||||||
|
opterr = 0;
|
||||||
|
optopt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getcwd(void)
|
||||||
|
{
|
||||||
|
char kern[4096];
|
||||||
|
char mine[4096];
|
||||||
|
char *dyn;
|
||||||
|
long n;
|
||||||
|
|
||||||
|
n = __syscall2(SYS_getcwd, (long)kern, (long)sizeof(kern));
|
||||||
|
TEST_ASSERT_TRUE(n > 0);
|
||||||
|
TEST_ASSERT_TRUE(getcwd(mine, sizeof(mine)) == mine);
|
||||||
|
TEST_ASSERT_STREQ(mine, kern);
|
||||||
|
dyn = getcwd(NULL, 0);
|
||||||
|
TEST_ASSERT_TRUE(dyn != NULL);
|
||||||
|
if (dyn != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_STREQ(dyn, kern);
|
||||||
|
free(dyn);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_isatty_ttyname(void)
|
||||||
|
{
|
||||||
|
char name[256];
|
||||||
|
char *t;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQ(isatty(-1), 0);
|
||||||
|
TEST_ASSERT_EQ(isatty(4096), 0);
|
||||||
|
/* fd 1 may or may not be a terminal in the harness; the contract is
|
||||||
|
* only that isatty answers 0 or 1 without crashing. */
|
||||||
|
TEST_ASSERT_TRUE(isatty(1) == 0 || isatty(1) == 1);
|
||||||
|
|
||||||
|
rc = ttyname_r(1, name, sizeof(name));
|
||||||
|
TEST_ASSERT_TRUE(rc == 0 || rc == ENOTTY || rc == ERANGE);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_TRUE(name[0] == '/');
|
||||||
|
}
|
||||||
|
t = ttyname(1);
|
||||||
|
TEST_ASSERT_TRUE(t == NULL || t[0] == '/');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getopt_basic(void)
|
||||||
|
{
|
||||||
|
char *av[] = {"prog", "-a", "-b", "rest", NULL};
|
||||||
|
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av, "ab"), 'a');
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av, "ab"), 'b');
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av, "ab"), -1);
|
||||||
|
TEST_ASSERT_EQ(optind, 3); /* optind sits on the first non-option */
|
||||||
|
TEST_ASSERT_TRUE(optarg == NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getopt_bundled(void)
|
||||||
|
{
|
||||||
|
char *av[] = {"prog", "-ab", "tail", NULL};
|
||||||
|
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(3, av, "ab"), 'a');
|
||||||
|
TEST_ASSERT_EQ(getopt(3, av, "ab"), 'b');
|
||||||
|
TEST_ASSERT_EQ(getopt(3, av, "ab"), -1);
|
||||||
|
TEST_ASSERT_EQ(optind, 2); /* the first non-option is "tail" */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getopt_args(void)
|
||||||
|
{
|
||||||
|
char *av1[] = {"prog", "-barg", "tail", NULL};
|
||||||
|
char *av2[] = {"prog", "-b", "arg", "tail", NULL};
|
||||||
|
|
||||||
|
/* Attached argument: "-barg" gives optarg "arg" from the same
|
||||||
|
* element. */
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(3, av1, "b:"), 'b');
|
||||||
|
TEST_ASSERT_STREQ(optarg, "arg");
|
||||||
|
TEST_ASSERT_EQ(getopt(3, av1, "b:"), -1);
|
||||||
|
|
||||||
|
/* Separate argument: "-b arg". */
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av2, "b:"), 'b');
|
||||||
|
TEST_ASSERT_STREQ(optarg, "arg");
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av2, "b:"), -1);
|
||||||
|
TEST_ASSERT_EQ(optind, 3);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getopt_errors(void)
|
||||||
|
{
|
||||||
|
char *av1[] = {"prog", "-b", NULL};
|
||||||
|
char *av2[] = {"prog", "-z", NULL};
|
||||||
|
char *av3[] = {"prog", "-azb", NULL};
|
||||||
|
|
||||||
|
/* Missing argument with a leading ':' returns ':' silently. */
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av1, ":b:"), ':');
|
||||||
|
TEST_ASSERT_EQ(optopt, 'b');
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av1, ":b:"), -1);
|
||||||
|
|
||||||
|
/* Unknown option returns '?', optopt carries the character. */
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av2, "ab"), '?');
|
||||||
|
TEST_ASSERT_EQ(optopt, 'z');
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av2, "ab"), -1);
|
||||||
|
|
||||||
|
/* An unknown option in the middle of a bundle does not stop the
|
||||||
|
* scan: the remaining letters are still returned. */
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av3, "ab"), 'a');
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av3, "ab"), '?');
|
||||||
|
TEST_ASSERT_EQ(optopt, 'z');
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av3, "ab"), 'b');
|
||||||
|
TEST_ASSERT_EQ(getopt(2, av3, "ab"), -1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getopt_dashdash(void)
|
||||||
|
{
|
||||||
|
char *av[] = {"prog", "-a", "--", "-b", NULL};
|
||||||
|
|
||||||
|
uopt_reset();
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av, "ab"), 'a');
|
||||||
|
TEST_ASSERT_EQ(getopt(4, av, "ab"), -1);
|
||||||
|
TEST_ASSERT_EQ(optind, 3); /* "--" consumed; "-b" is the first non-option */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_basename(void)
|
||||||
|
{
|
||||||
|
char s1[] = "/usr/lib";
|
||||||
|
char s2[] = "/usr/lib/";
|
||||||
|
char s3[] = "/";
|
||||||
|
char s4[] = "//";
|
||||||
|
char s5[] = "usr";
|
||||||
|
char s6[] = "";
|
||||||
|
char s7[] = "/usr";
|
||||||
|
|
||||||
|
TEST_ASSERT_STREQ(basename(s1), "lib");
|
||||||
|
TEST_ASSERT_STREQ(basename(s2), "lib");
|
||||||
|
TEST_ASSERT_STREQ(basename(s3), "/");
|
||||||
|
TEST_ASSERT_STREQ(basename(s4), "/");
|
||||||
|
TEST_ASSERT_STREQ(basename(s5), "usr");
|
||||||
|
TEST_ASSERT_STREQ(basename(s6), ".");
|
||||||
|
TEST_ASSERT_STREQ(basename(s7), "usr");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_dirname(void)
|
||||||
|
{
|
||||||
|
char s1[] = "/usr/lib";
|
||||||
|
char s2[] = "/usr/lib/";
|
||||||
|
char s3[] = "/";
|
||||||
|
char s4[] = "//";
|
||||||
|
char s5[] = "usr";
|
||||||
|
char s6[] = "";
|
||||||
|
char s7[] = "/usr";
|
||||||
|
char s8[] = "usr/";
|
||||||
|
|
||||||
|
TEST_ASSERT_STREQ(dirname(s1), "/usr");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s2), "/usr");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s3), "/");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s4), "/");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s5), ".");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s6), ".");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s7), "/");
|
||||||
|
TEST_ASSERT_STREQ(dirname(s8), ".");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_confstr(void)
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
char small[8];
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
n = confstr(_CS_PATH, NULL, 0);
|
||||||
|
TEST_ASSERT_TRUE(n > 1);
|
||||||
|
TEST_ASSERT_EQ(confstr(_CS_PATH, buf, sizeof(buf)), n);
|
||||||
|
TEST_ASSERT_TRUE(buf[0] == '/');
|
||||||
|
TEST_ASSERT_TRUE(buf[n - 1] == '\0');
|
||||||
|
|
||||||
|
/* A too-small buffer is left alone but the same length is reported. */
|
||||||
|
TEST_ASSERT_EQ(confstr(_CS_PATH, small, 2), n);
|
||||||
|
|
||||||
|
/* Not glibc: the version keys are the empty string. */
|
||||||
|
TEST_ASSERT_EQ(confstr(_CS_GNU_LIBC_VERSION, buf, sizeof(buf)), 1);
|
||||||
|
TEST_ASSERT_STREQ(buf, "");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_realpath(void)
|
||||||
|
{
|
||||||
|
char here[4096];
|
||||||
|
char in[4096];
|
||||||
|
char out[4096];
|
||||||
|
char parent[4096];
|
||||||
|
char *dyn;
|
||||||
|
char *slash;
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(realpath(".", out) == out);
|
||||||
|
TEST_ASSERT_TRUE(getcwd(here, sizeof(here)) == here);
|
||||||
|
TEST_ASSERT_STREQ(out, here);
|
||||||
|
|
||||||
|
TEST_ASSERT_STREQ(realpath("/", out), "/");
|
||||||
|
|
||||||
|
/* "/proc/self/cwd" is a symlink to the working directory: exercises
|
||||||
|
* readlink plus an absolute link target. NULL only when procfs is
|
||||||
|
* absent. */
|
||||||
|
if (realpath("/proc/self/cwd", out) != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_STREQ(out, here);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ".." pops one component: cwd/.. resolves to cwd's parent, found by
|
||||||
|
* truncating here at its last slash (the root has no parent). The
|
||||||
|
* input and resolved buffers are distinct: realpath reads the path
|
||||||
|
* while it rewrites the resolved buffer. */
|
||||||
|
slash = strrchr(here, '/');
|
||||||
|
if (slash != NULL && slash != here)
|
||||||
|
{
|
||||||
|
size_t plen = (size_t)(slash - here);
|
||||||
|
|
||||||
|
memcpy(parent, here, plen);
|
||||||
|
parent[plen] = '\0';
|
||||||
|
strcpy(in, here);
|
||||||
|
strcpy(in + strlen(in), "/..");
|
||||||
|
TEST_ASSERT_TRUE(realpath(in, out) == out);
|
||||||
|
TEST_ASSERT_STREQ(out, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
dyn = realpath(".", NULL);
|
||||||
|
TEST_ASSERT_TRUE(dyn != NULL);
|
||||||
|
if (dyn != NULL)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_STREQ(dyn, here);
|
||||||
|
free(dyn);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
test_getwd_swab(void)
|
||||||
|
{
|
||||||
|
char wd[4096];
|
||||||
|
char here[4096];
|
||||||
|
char out[7] = {0};
|
||||||
|
static const char six[7] = "abcdef";
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE(getwd(wd) == wd);
|
||||||
|
TEST_ASSERT_TRUE(getcwd(here, sizeof(here)) == here);
|
||||||
|
TEST_ASSERT_STREQ(wd, here);
|
||||||
|
|
||||||
|
swab(six, out, 6);
|
||||||
|
TEST_ASSERT_STREQ(out, "badcfe");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct vlibc_test tests[] = {
|
||||||
|
{"getcwd", test_getcwd},
|
||||||
|
{"isatty-ttyname", test_isatty_ttyname},
|
||||||
|
{"getopt-basic", test_getopt_basic},
|
||||||
|
{"getopt-bundled", test_getopt_bundled},
|
||||||
|
{"getopt-args", test_getopt_args},
|
||||||
|
{"getopt-errors", test_getopt_errors},
|
||||||
|
{"getopt-dashdash", test_getopt_dashdash},
|
||||||
|
{"basename", test_basename},
|
||||||
|
{"dirname", test_dirname},
|
||||||
|
{"confstr", test_confstr},
|
||||||
|
{"realpath", test_realpath},
|
||||||
|
{"getwd-swab", test_getwd_swab},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Own main (not TEST_MAIN): supports the -f failure mode and always
|
||||||
|
* leaves through the raw SYS_exit_group (see the file header).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const size_t count = sizeof tests / sizeof tests[0];
|
||||||
|
size_t passed = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
||||||
|
{
|
||||||
|
int before = vlibc_test_failures;
|
||||||
|
char out[64];
|
||||||
|
|
||||||
|
vlibc_test_say(1, "RUN failure-shapes: ");
|
||||||
|
TEST_ASSERT_TRUE(realpath(NULL, out) == NULL);
|
||||||
|
TEST_ASSERT_TRUE(realpath("/no/such/vlibc-xyz", out) == NULL);
|
||||||
|
TEST_ASSERT_TRUE(getcwd(out, 0) == NULL);
|
||||||
|
if (vlibc_test_failures == before)
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "PASS\n");
|
||||||
|
vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "FAIL\n");
|
||||||
|
}
|
||||||
|
__syscall1(SYS_exit_group, vlibc_test_failures == before ? 0 : 1);
|
||||||
|
/* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int before = vlibc_test_failures;
|
||||||
|
|
||||||
|
vlibc_test_say(1, "RUN ");
|
||||||
|
vlibc_test_say(1, tests[i].name);
|
||||||
|
vlibc_test_say(1, ": ");
|
||||||
|
if (tests[i].run() == 0 && vlibc_test_failures == before)
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "PASS\n");
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "FAIL\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vlibc_test_say(1, "SUMMARY: ");
|
||||||
|
vlibc_test_say_dec(1, (unsigned long)passed);
|
||||||
|
vlibc_test_say(1, "/");
|
||||||
|
vlibc_test_say_dec(1, (unsigned long)count);
|
||||||
|
vlibc_test_say(1, " passed, ");
|
||||||
|
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
|
||||||
|
vlibc_test_say(1, " assertion failure(s)\n");
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, passed == count ? 0 : 1);
|
||||||
|
/* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* !VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
vlibc_test_say(1, "SKIP: this surface is level 2, not available here\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
Reference in New Issue
Block a user