Make improvements

- Invent openatemp() API
- Invent O_UNLINK open flag
- Introduce getenv_secure() API
- Remove `git pull` from cosmocc
- Fix utimes() when path is NULL
- Fix mktemp() to never return NULL
- Fix utimensat() UTIME_OMIT on XNU
- Improve utimensat() code for RHEL5
- Turn `argv[0]` C:/ to /C/ on Windows
- Introduce tmpnam() and tmpnam_r() APIs
- Fix more const issues with internal APIs
- Permit utimes() on WIN32 in O_RDONLY mode
- Fix fdopendir() to check fd is a directory
- Fix recent crash regression in landlock make
- Fix futimens(AT_FDCWD, NULL) to return EBADF
- Use workaround so `make -j` doesn't fork bomb
- Rename dontdiscard to __wur (just like glibc)
- Fix st_size for WIN32 symlinks containing UTF-8
- Introduce stdio ext APIs needed by GNU coreutils
- Fix lstat() on WIN32 for symlinks to directories
- Move some constants from normalize.inc to limits.h
- Fix segv with memchr() and memcmp() overlapping page
- Implement POSIX fflush() behavior for reader streams
- Implement AT_SYMLINK_NOFOLLOW for utimensat() on WIN32
- Don't change read-only status of existing files on WIN32
- Correctly handle `0x[^[:xdigit:]]` case in strtol() functions
This commit is contained in:
Justine Tunney 2023-09-06 03:54:42 -07:00
parent 8596e83cce
commit f531acc8f9
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
297 changed files with 1920 additions and 1681 deletions

View file

@ -26,11 +26,6 @@
#define _POSIX_MEMLOCK_RANGE _POSIX_VERSION
#define _POSIX_SPAWN _POSIX_VERSION
#define EOF -1 /* end of file */
#define WEOF -1u /* end of file (multibyte) */
#define _IOFBF 0 /* fully buffered */
#define _IOLBF 1 /* line buffered */
#define _IONBF 2 /* no buffering */
#define SEEK_SET 0 /* relative to beginning */
#define SEEK_CUR 1 /* relative to current position */
#define SEEK_END 2 /* relative to end */
@ -99,7 +94,6 @@ int execv(const char *, char *const[]);
int execve(const char *, char *const[], char *const[]);
int execvp(const char *, char *const[]);
int faccessat(int, const char *, int, int);
int fadvise(int, uint64_t, uint64_t, int);
int fchdir(int);
int fchmod(int, unsigned) dontthrow;
int fchmodat(int, const char *, unsigned, int);
@ -136,7 +130,6 @@ int mkdirat(int, const char *, unsigned);
int mkfifo(const char *, unsigned);
int mkfifoat(int, const char *, unsigned);
int mknod(const char *, unsigned, uint64_t);
int mknodat(int, const char *, int, uint64_t);
int nice(int);
int open(const char *, int, ...);
int openat(int, const char *, int, ...);
@ -205,7 +198,7 @@ int setresgid(unsigned, unsigned, unsigned);
int setresuid(unsigned, unsigned, unsigned);
int getresgid(unsigned *, unsigned *, unsigned *);
int getresuid(unsigned *, unsigned *, unsigned *);
char *get_current_dir_name(void) dontdiscard;
char *get_current_dir_name(void) __wur;
int sync_file_range(int, int64_t, int64_t, unsigned);
ssize_t splice(int, int64_t *, int, int64_t *, size_t, unsigned);
int memfd_create(const char *, unsigned int);
@ -245,6 +238,7 @@ int tmpfd(void);
int touch(const char *, unsigned);
int unveil(const char *, const char *);
long ptrace(int, ...);
int fadvise(int, uint64_t, uint64_t, int);
ssize_t copyfd(int, int, size_t);
ssize_t readansi(int, char *, size_t);
ssize_t tinyprint(int, const char *, ...) nullterminated();

View file

@ -33,7 +33,7 @@
#define _O_EXCL 0x00000080 // kNtCreateNew
#define _O_TRUNC 0x00000200 // kNtCreateAlways
#define _O_DIRECTORY 0x00010000 // kNtFileFlagBackupSemantics
#define _O_TMPFILE 0x00410000 // AttributeTemporary|FlagDeleteOnClose
#define _O_UNLINK 0x04000100 // kNtFileAttributeTemporary|DeleteOnClose
#define _O_DIRECT 0x00004000 // kNtFileFlagNoBuffering
#define _O_NONBLOCK 0x00000800 // kNtFileFlagWriteThrough (not sent to win32)
#define _O_RANDOM 0x80000000 // kNtFileFlagRandomAccess
@ -51,11 +51,18 @@ textwindows int GetNtOpenFlags(int flags, int mode, uint32_t *out_perm,
if (flags &
~(O_ACCMODE | _O_APPEND | _O_CREAT | _O_EXCL | _O_TRUNC | _O_DIRECTORY |
_O_TMPFILE | _O_NONBLOCK | _O_RANDOM | _O_SEQUENTIAL | _O_COMPRESSED |
_O_UNLINK | _O_NONBLOCK | _O_RANDOM | _O_SEQUENTIAL | _O_COMPRESSED |
_O_INDEXED | _O_CLOEXEC | _O_DIRECT)) {
return einval();
}
// "Some of these flags should not be combined. For instance,
// combining kNtFileFlagRandomAccess with kNtFileFlagSequentialScan
// is self-defeating." -Quoth MSDN § CreateFileW
if ((flags & _O_SEQUENTIAL) && (flags & _O_RANDOM)) {
return einval();
}
switch (flags & O_ACCMODE) {
case O_RDONLY:
perm = kNtFileGenericRead;
@ -80,7 +87,7 @@ textwindows int GetNtOpenFlags(int flags, int mode, uint32_t *out_perm,
}
attr = 0;
is_creating_file = (flags & _O_CREAT) || (flags & _O_TMPFILE) == _O_TMPFILE;
is_creating_file = !!(flags & _O_CREAT);
// POSIX O_EXEC doesn't mean the same thing as kNtGenericExecute. We
// request execute access when we can determine it from mode's bits.
@ -125,33 +132,64 @@ textwindows int GetNtOpenFlags(int flags, int mode, uint32_t *out_perm,
disp = kNtOpenExisting;
}
// Please use tmpfd() or tmpfile() instead of O_TMPFILE.
if ((flags & _O_TMPFILE) == _O_TMPFILE) {
attr |= kNtFileAttributeTemporary | kNtFileFlagDeleteOnClose;
} else {
attr |= kNtFileAttributeNormal;
if (flags & _O_DIRECTORY) {
attr |= kNtFileFlagBackupSemantics;
}
}
// The Windows content indexing service ravages performance similar to
// Windows Defender. Please pass O_INDEXED to openat() to enable this.
if (~flags & _O_INDEXED) {
// The Windows content indexing service ravages performance similar to
// Windows Defender. Please pass O_INDEXED to openat() to enable this.
attr |= kNtFileAttributeNotContentIndexed;
}
// Windows' transparent filesystem compression is really cool, as such
// we've introduced a nonstandard O_COMPRESSED flag to help you use it
if (flags & _O_COMPRESSED) {
// Windows' transparent filesystem compression is really cool, as such
// we've introduced a nonstandard O_COMPRESSED flag to help you use it
attr |= kNtFileAttributeCompressed;
}
if (flags & kNtFileAttributeTemporary) { // subset of _O_UNLINK
// "Specifying the kNtFileAttributeTemporary attribute causes file
// systems to avoid writing data back to mass storage if sufficient
// cache memory is available, because an application deletes a
// temporary file after a handle is closed. In that case, the
// system can entirely avoid writing the data. Although it does not
// directly control data caching in the same way as the
// [kNtFileFlagNoBuffering, kNtFileFlagWriteThrough,
// kNtFileFlagSequentialScan, and kNtFileFlagRandomAccess] flags,
// the kNtFileAttributeTemporary attribute does tell the system to
// hold as much as possible in the system cache without writing and
// therefore may be of concern for certain applications." -MSDN
attr |= kNtFileAttributeTemporary;
}
if (!attr) {
// "All other file attributes override kNtFileAttributeNormal [...]
// [which] is valid only if used alone." -Quoth MSDN § CreateFileW
attr |= kNtFileAttributeNormal;
}
attr |= flags & kNtFileFlagDeleteOnClose; // subset of _O_UNLINK
if (flags & _O_DIRECTORY) {
// "You must set this flag to obtain a handle to a directory."
// -Quoth MSDN § CreateFileW
attr |= kNtFileFlagBackupSemantics;
}
// Not certain yet what benefit these flags offer.
if (flags & _O_SEQUENTIAL) attr |= kNtFileFlagSequentialScan;
if (flags & _O_RANDOM) attr |= kNtFileFlagRandomAccess;
if (flags & _O_DIRECT) attr |= kNtFileFlagNoBuffering;
// TODO(jart): Should we *always* open with write permission if the
// kernel will give it to us? We'd then deny write access
// in libc system call wrappers.
//
// "When an application creates a file across a network, it is better
// to use kNtGenericRead | kNtGenericWrite for dwDesiredAccess than
// to use kNtGenericWrite alone. The resulting code is faster,
// because the redirector can use the cache manager and send fewer
// SMBs with more data. This combination also avoids an issue where
// writing to a file across a network can occasionally return
// kNtErrorAccessDenied." -Quoth MSDN
if (out_perm) *out_perm = perm;
if (out_share) *out_share = share;
if (out_disp) *out_disp = disp;

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/limits.h"
#include "libc/mem/alloca.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"

View file

@ -26,6 +26,7 @@
#include "libc/errno.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/mem/alloca.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"

View file

@ -19,6 +19,7 @@
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/limits.h"
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"

View file

@ -33,6 +33,7 @@
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/limits.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h"

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/stat.h"
@ -25,6 +26,7 @@
#include "libc/intrin/bsr.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/alloca.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/fileinfobyhandleclass.h"
#include "libc/nt/enum/filetype.h"
@ -39,102 +41,119 @@
#include "libc/sysv/consts/s.h"
#include "libc/sysv/errfuns.h"
static textwindows uint32_t GetSizeOfReparsePoint(int64_t fh) {
wint_t x, y;
const char16_t *p;
uint32_t mem, i, n, z = 0;
struct NtReparseDataBuffer *rdb;
long buf[(sizeof(*rdb) + PATH_MAX * sizeof(char16_t)) / sizeof(long)];
mem = sizeof(buf);
rdb = (struct NtReparseDataBuffer *)buf;
if (DeviceIoControl(fh, kNtFsctlGetReparsePoint, 0, 0, rdb, mem, &n, 0)) {
i = 0;
n = rdb->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(char16_t);
p = (char16_t *)((char *)rdb->SymbolicLinkReparseBuffer.PathBuffer +
rdb->SymbolicLinkReparseBuffer.PrintNameOffset);
while (i < n) {
x = p[i++] & 0xffff;
if (!IsUcs2(x)) {
if (i < n) {
y = p[i++] & 0xffff;
x = MergeUtf16(x, y);
} else {
x = 0xfffd;
}
static textwindows long GetSizeOfReparsePoint(int64_t fh) {
uint32_t mem =
sizeof(struct NtReparseDataBuffer) + PATH_MAX * sizeof(char16_t);
void *buf = alloca(mem);
uint32_t dwBytesReturned;
struct NtReparseDataBuffer *rdb = (struct NtReparseDataBuffer *)buf;
if (!DeviceIoControl(fh, kNtFsctlGetReparsePoint, 0, 0, rdb, mem,
&dwBytesReturned, 0)) {
return -1;
}
const char16_t *p =
(const char16_t *)((char *)rdb->SymbolicLinkReparseBuffer.PathBuffer +
rdb->SymbolicLinkReparseBuffer.PrintNameOffset);
uint32_t n =
rdb->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(char16_t);
uint32_t i = 0;
uint32_t z = 0;
while (i < n) {
wint_t x = p[i++] & 0xffff;
if (!IsUcs2(x)) {
if (i < n) {
wint_t y = p[i++] & 0xffff;
x = MergeUtf16(x, y);
} else {
x = 0xfffd;
}
z += x < 0200 ? 1 : _bsrl(tpenc(x)) >> 3;
}
} else {
STRACE("%s failed %m", "GetSizeOfReparsePoint");
if (x >= 0200) {
z += _bsrl(tpenc(x)) >> 3;
}
++z;
}
return z;
}
textwindows int sys_fstat_nt(int64_t handle, struct stat *st) {
int filetype;
uint32_t umask;
uint64_t actualsize;
struct NtFileCompressionInfo fci;
struct NtByHandleFileInformation wst;
if (!st) return efault();
if ((filetype = GetFileType(handle))) {
bzero(st, sizeof(*st));
umask = atomic_load_explicit(&__umask, memory_order_acquire);
switch (filetype) {
case kNtFileTypeChar:
st->st_mode = S_IFCHR | (0666 & ~umask);
break;
case kNtFileTypePipe:
st->st_mode = S_IFIFO | (0666 & ~umask);
break;
case kNtFileTypeDisk:
if (GetFileInformationByHandle(handle, &wst)) {
st->st_mode = 0555 & ~umask;
st->st_flags = wst.dwFileAttributes;
if (!(wst.dwFileAttributes & kNtFileAttributeReadonly)) {
st->st_mode |= 0222 & ~umask;
}
if (wst.dwFileAttributes & kNtFileAttributeDirectory) {
st->st_mode |= S_IFDIR;
} else if (wst.dwFileAttributes & kNtFileAttributeReparsePoint) {
st->st_mode |= S_IFLNK;
} else {
st->st_mode |= S_IFREG;
}
st->st_atim = FileTimeToTimeSpec(wst.ftLastAccessFileTime);
st->st_mtim = FileTimeToTimeSpec(wst.ftLastWriteFileTime);
st->st_ctim = FileTimeToTimeSpec(wst.ftCreationFileTime);
st->st_birthtim = st->st_ctim;
st->st_gid = st->st_uid = __synthesize_uid();
st->st_size = (uint64_t)wst.nFileSizeHigh << 32 | wst.nFileSizeLow;
st->st_blksize = 4096;
st->st_dev = wst.dwVolumeSerialNumber;
st->st_rdev = 0;
st->st_ino = (uint64_t)wst.nFileIndexHigh << 32 | wst.nFileIndexLow;
st->st_nlink = wst.nNumberOfLinks;
if (S_ISLNK(st->st_mode)) {
if (!st->st_size) {
st->st_size = GetSizeOfReparsePoint(handle);
}
} else {
actualsize = st->st_size;
if (S_ISREG(st->st_mode) &&
GetFileInformationByHandleEx(handle, kNtFileCompressionInfo,
&fci, sizeof(fci))) {
actualsize = fci.CompressedFileSize;
}
st->st_blocks = ROUNDUP(actualsize, 4096) / 512;
}
} else {
STRACE("%s failed %m", "GetFileInformationByHandle");
textwindows int sys_fstat_nt(int64_t handle, struct stat *out_st) {
struct stat st = {0};
// Always set st_blksize to avoid divide by zero issues.
// The Linux kernel sets this for /dev/tty and similar too.
// TODO(jart): GetVolumeInformationByHandle?
st.st_blksize = 4096;
// We'll use the "umask" to fake out the mode bits.
uint32_t umask = atomic_load_explicit(&__umask, memory_order_acquire);
switch (GetFileType(handle)) {
case kNtFileTypeUnknown:
break;
case kNtFileTypeChar:
st.st_mode = S_IFCHR | (0666 & ~umask);
break;
case kNtFileTypePipe:
st.st_mode = S_IFIFO | (0666 & ~umask);
break;
case kNtFileTypeDisk: {
struct NtByHandleFileInformation wst;
if (!GetFileInformationByHandle(handle, &wst)) {
return __winerr();
}
st.st_mode = 0555 & ~umask;
st.st_flags = wst.dwFileAttributes;
if (!(wst.dwFileAttributes & kNtFileAttributeReadonly)) {
st.st_mode |= 0222 & ~umask;
}
if (wst.dwFileAttributes & kNtFileAttributeReparsePoint) {
st.st_mode |= S_IFLNK;
} else if (wst.dwFileAttributes & kNtFileAttributeDirectory) {
st.st_mode |= S_IFDIR;
} else {
st.st_mode |= S_IFREG;
}
st.st_atim = FileTimeToTimeSpec(wst.ftLastAccessFileTime);
st.st_mtim = FileTimeToTimeSpec(wst.ftLastWriteFileTime);
st.st_birthtim = FileTimeToTimeSpec(wst.ftCreationFileTime);
// compute time of last status change
if (timespec_cmp(st.st_atim, st.st_mtim) > 0) {
st.st_ctim = st.st_atim;
} else {
st.st_ctim = st.st_mtim;
}
st.st_gid = st.st_uid = __synthesize_uid();
st.st_size = (wst.nFileSizeHigh + 0ull) << 32 | wst.nFileSizeLow;
st.st_dev = wst.dwVolumeSerialNumber;
st.st_ino = (wst.nFileIndexHigh + 0ull) << 32 | wst.nFileIndexLow;
st.st_nlink = wst.nNumberOfLinks;
if (S_ISLNK(st.st_mode)) {
if (!st.st_size) {
long size = GetSizeOfReparsePoint(handle);
if (size == -1) return -1;
st.st_size = size;
}
break;
default:
break;
} else {
// st_size = uncompressed size
// st_blocks*512 = physical size
uint64_t physicalsize;
struct NtFileCompressionInfo fci;
if (!(wst.dwFileAttributes &
(kNtFileAttributeDirectory | kNtFileAttributeReparsePoint)) &&
GetFileInformationByHandleEx(handle, kNtFileCompressionInfo, &fci,
sizeof(fci))) {
physicalsize = fci.CompressedFileSize;
} else {
physicalsize = st.st_size;
}
st.st_blocks = ROUNDUP(physicalsize, st.st_blksize) / 512;
}
break;
}
return 0;
} else {
STRACE("%s failed %m", "GetFileType");
return __winerr();
default:
__builtin_unreachable();
}
memcpy(out_st, &st, sizeof(st));
return 0;
}

View file

@ -17,16 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/metastat.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Forms fstat() on System Five.
* @asyncsignalsafe
*/
int32_t sys_fstat(int32_t fd, struct stat *st) {
void *p;
union metastat ms;

View file

@ -34,7 +34,9 @@ textwindows int sys_fstatat_nt(int dirfd, const char *path, struct stat *st,
uint16_t path16[PATH_MAX];
if (__mkntpathat(dirfd, path, 0, path16) == -1) return -1;
if ((fh = CreateFile(
path16, kNtFileReadAttributes, 0, 0, kNtOpenExisting,
path16, kNtFileReadAttributes,
kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0,
kNtOpenExisting,
kNtFileAttributeNormal | kNtFileFlagBackupSemantics |
((flags & AT_SYMLINK_NOFOLLOW) ? kNtFileFlagOpenReparsePoint
: 0),

View file

@ -17,10 +17,8 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/metastat.internal.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Performs fstatat() on System Five.
@ -30,7 +28,6 @@ int32_t sys_fstatat(int32_t dirfd, const char *path, struct stat *st,
int32_t flags) {
void *p;
union metastat ms;
if (IsAsan() && !__asan_is_valid_str(path)) return efault();
if (st) {
p = &ms;
} else {

View file

@ -44,7 +44,11 @@
*/
int futimens(int fd, const struct timespec ts[2]) {
int rc;
rc = __utimens(fd, 0, ts, 0);
if (fd < 0) {
rc = ebadf(); // so we don't confuse __utimens if caller passes AT_FDCWD
} else {
rc = __utimens(fd, 0, ts, 0);
}
STRACE("futimens(%d, {%s, %s}) → %d% m", fd, DescribeTimespec(0, ts),
DescribeTimespec(0, ts ? ts + 1 : 0), rc);
return rc;

View file

@ -42,19 +42,16 @@
int futimes(int fd, const struct timeval tv[2]) {
int rc;
struct timespec ts[2];
if (tv) {
ts[0].tv_sec = tv[0].tv_sec;
ts[0].tv_nsec = tv[0].tv_usec * 1000;
ts[1].tv_sec = tv[1].tv_sec;
ts[1].tv_nsec = tv[1].tv_usec * 1000;
if (fd < 0) {
rc = ebadf(); // so we don't confuse __utimens if caller passes AT_FDCWD
} else if (tv) {
ts[0] = timeval_totimespec(tv[0]);
ts[1] = timeval_totimespec(tv[1]);
rc = __utimens(fd, 0, ts, 0);
} else {
rc = __utimens(fd, 0, 0, 0);
}
STRACE("futimes(%d, {%s, %s}) → %d% m", fd, DescribeTimeval(0, tv),
DescribeTimeval(0, tv ? tv + 1 : 0), rc);
return rc;
}

View file

@ -24,6 +24,7 @@
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/limits.h"
#include "libc/log/backtrace.internal.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"

View file

@ -22,6 +22,7 @@
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h"

View file

@ -16,21 +16,46 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/pr.h"
bool __is_linux_2_6_23(void) {
#ifdef __x86_64__
static struct {
atomic_uint once;
bool res;
} __is_linux_2_6_23_data;
static bool __is_linux_2_6_23_impl(void) {
int rc;
if (!IsLinux()) return false;
if (IsGenuineBlink()) return true;
asm volatile("syscall"
: "=a"(rc)
: "0"(157), "D"(PR_GET_SECCOMP)
: "rcx", "r11", "memory");
return rc != -EINVAL;
}
static void __is_linux_2_6_23_init(void) {
__is_linux_2_6_23_data.res = __is_linux_2_6_23_impl();
}
#endif /* x86 */
/**
* Returns true if we're running on non-ancient Linux.
* @note this function must only be called on Linux
*/
bool __is_linux_2_6_23(void) {
unassert(IsLinux()); // should be checked by caller
#ifdef __x86_64__
cosmo_once(&__is_linux_2_6_23_data.once, __is_linux_2_6_23_init);
return __is_linux_2_6_23_data.res;
#else
return true;
#endif

View file

@ -37,7 +37,7 @@ int issetugid(void) {
int rc;
if (IsLinux()) {
rc = !!__getauxval(AT_SECURE).value;
} else if (IsMetal()) {
} else if (IsMetal() || IsWindows()) {
rc = 0;
} else {
rc = sys_issetugid();

View file

@ -18,6 +18,7 @@
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
#include "libc/nt/systeminfo.h"
#include "libc/runtime/runtime.h"

View file

@ -27,10 +27,8 @@
int lutimes(const char *filename, const struct timeval tv[2]) {
struct timespec ts[2];
if (tv) {
ts[0].tv_sec = tv[0].tv_sec;
ts[0].tv_nsec = tv[0].tv_usec * 1000;
ts[1].tv_sec = tv[1].tv_sec;
ts[1].tv_nsec = tv[1].tv_usec * 1000;
ts[0] = timeval_totimespec(tv[0]);
ts[1] = timeval_totimespec(tv[1]);
return utimensat(AT_FDCWD, filename, ts, AT_SYMLINK_NOFOLLOW);
} else {
return utimensat(AT_FDCWD, filename, 0, AT_SYMLINK_NOFOLLOW);

View file

@ -19,6 +19,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/str/path.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/s.h"

62
libc/calls/mkdtemp.c Normal file
View file

@ -0,0 +1,62 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/intrin/bits.h"
#include "libc/stdio/rand.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
#include "libc/temp.h"
/**
* Creates temporary directory, e.g.
*
* char path[] = "/tmp/foo.XXXXXX";
* mkdtemp(path);
* rmdir(path);
*
* @param template must end with XXXXXX which will be replaced
* with random text on success (and not modified on error)
* @return pointer to template on success, or NULL w/ errno
* @raise EINVAL if template didn't end with XXXXXX
*/
char *mkdtemp(char *template) {
int n;
if ((n = strlen(template)) < 6 ||
READ32LE(template + n - 6) != READ32LE("XXXX") ||
READ16LE(template + n - 6 + 4) != READ16LE("XX")) {
einval();
return 0;
}
for (;;) {
int x = _rand64();
for (int i = 0; i < 6; ++i) {
template[n - 6 + i] = "0123456789abcdefghikmnpqrstvwxyz"[x & 31];
x >>= 5;
}
int e = errno;
if (!mkdir(template, 0700)) {
return template;
} else if (errno != EEXIST) {
memcpy(template + n - 6, "XXXXXX", 6);
return 0;
}
errno = e;
}
}

42
libc/calls/mkostemp.c Normal file
View file

@ -0,0 +1,42 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/temp.h"
#include "libc/sysv/consts/at.h"
/**
* Creates temporary file name and file descriptor, e.g.
*
* char path[] = "/tmp/foo.XXXXXX";
* int fd = mkostemp(path, O_CLOEXEC);
* printf("%s is opened as %d\n", path, fd);
*
* @param template is mutated to replace last six X's with rng
* @return open file descriptor r + w exclusive or -1 w/ errno
* @raise EINVAL if `template` didn't end with `XXXXXX`
* @see openatemp() for one temp roller to rule them all
* @see mkostemps() if you need a suffix
* @see mkstemp() if you don't need flags
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
*/
int mkostemp(char *template, unsigned flags) {
return openatemp(AT_FDCWD, template, 0, flags, 0);
}
__strong_reference(mkostemp, mkostemp64);

43
libc/calls/mkostemps.c Normal file
View file

@ -0,0 +1,43 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/temp.h"
#include "libc/sysv/consts/at.h"
/**
* Creates temporary file name and file descriptor, e.g.
*
* char path[] = "/tmp/foo.XXXXXX";
* int fd = mkostemp(path, O_CLOEXEC);
* printf("%s is opened as %d\n", path, fd);
*
* @param template is mutated to replace last six X's with rng
* @return open file descriptor r + w exclusive or -1 w/ errno
* @raise EINVAL if `template` didn't end with `XXXXXX`
* @see openatemp() for one temp roller to rule them all
* @see mkstemp() if you don't need suffix/flags
* @see mkstemps() if you don't need flags
* @see mkostemp() if you don't need suffix
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
*/
int mkostemps(char *template, int suffixlen, unsigned flags) {
return openatemp(AT_FDCWD, template, suffixlen, flags, 0);
}
__strong_reference(mkostemps, mkostemps64);

View file

@ -16,67 +16,27 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/bits.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/temp.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
int _mkstemp(char *template, int oflags) {
uint64_t w;
int i, n, e, fd;
if ((n = strlen(template)) < 6 ||
READ16LE(template + n - 2) != READ16LE("XX") ||
READ32LE(template + n - 6) != READ32LE("XXXX")) {
return einval();
}
for (;;) {
w = _rand64();
for (i = 0; i < 6; ++i) {
template[n - 6 + i] = "0123456789abcdefghijklmnopqrstuvwxyz"[w % 36];
w /= 36;
}
e = errno;
if ((fd = open(template, O_RDWR | O_CREAT | O_EXCL | oflags, 0600)) != -1) {
return fd;
} else if (errno == EEXIST) {
errno = e;
} else {
template[0] = 0;
return fd;
}
}
}
#include "libc/temp.h"
#include "libc/sysv/consts/at.h"
/**
* Creates temporary file name and file descriptor.
* Creates temporary file name and file descriptor, e.g.
*
* The best way to construct your path template is:
*
* char path[PATH_MAX+1];
* strlcat(path, kTmpDir, sizeof(path));
* strlcat(path, "sauce.XXXXXX", sizeof(path));
*
* This usage pattern makes mkstemp() equivalent to tmpfd():
*
* int fd;
* fd = mkstemp(path);
* unlink(path);
*
* This usage pattern makes mkstemp() equivalent to mktemp():
*
* close(mkstemp(path));
* puts(path);
* char path[] = "/tmp/foo.XXXXXX";
* int fd = mkstemp(path);
* printf("%s is opened as %d\n", path, fd);
*
* @param template is mutated to replace last six X's with rng
* @return open file descriptor r + w exclusive or -1 w/ errno
* @raise EINVAL if `template` didn't end with `XXXXXX`
* @see openatemp() for one temp roller to rule them all
* @see mkostemp() if you you need a `O_CLOEXEC`, `O_APPEND`, etc.
* @see mkstemps() if you you need a suffix
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
*/
int mkstemp(char *template) {
return _mkstemp(template, 0);
return openatemp(AT_FDCWD, template, 0, 0, 0);
}
__strong_reference(mkstemp, mkstemp64);

44
libc/calls/mkstemps.c Normal file
View file

@ -0,0 +1,44 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/temp.h"
#include "libc/sysv/consts/at.h"
/**
* Creates temporary file name and file descriptor, e.g.
*
* char path[] = "/tmp/foo.XXXXXX.txt";
* int fd = mkstemps(path, 4);
* printf("%s is opened as %d\n", path, fd);
*
* @param template is mutated to replace last six X's with rng
* @param suffixlen may be nonzero to permit characters after the "XXXXXX"
* @return open file descriptor r + w exclusive or -1 w/ errno
* @raise EINVAL if `template` (less the `suffixlen` region) didn't
* end with the string "XXXXXXX"
* @see mkostemp() if you you need a `O_CLOEXEC`, `O_APPEND`, etc.
* @see openatemp() for one temp roller to rule them all
* @see mkstemp() if you don't need `suffixlen`
* @see mktemp() if you don't need an fd
* @see tmpfd() if you don't need a path
*/
int mkstemps(char *template, int suffixlen) {
return openatemp(AT_FDCWD, template, suffixlen, 0, 0);
}
__strong_reference(mkstemps, mkstemps64);

View file

@ -16,8 +16,10 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/stdio/temp.h"
#include "libc/sysv/consts/at.h"
#include "libc/temp.h"
/**
* Generates temporary filename.
@ -27,15 +29,20 @@
*
* @param template is mutated to replace last six X's with rng
* @raise EINVAL if `template` didn't end with `XXXXXX`
* @return pointer to mutated `template`, or 0 w/ errno
* @see mkstemp()
* @return always `template` which on success will have its XXXXXX bytes
* mutated to a random value, otherwise on error it'll hold an empty
* string and `errno` will be set
* @see mkstemp() if you you need a file descriptor
* @see mkstemps() if you you need a file extension
* @see openatemp() for one temp roller to rule them all
* @see mkostemp() if you you need a `O_CLOEXEC`, `O_APPEND`, etc.
*/
char *mktemp(char *template) {
int fd;
if ((fd = mkstemp(template)) != -1) {
close(fd);
return template;
if ((fd = openatemp(AT_FDCWD, template, 0, 0, 0)) != -1) {
unassert(!close(fd));
} else {
return 0;
*template = 0;
}
return template;
}

View file

@ -66,9 +66,11 @@ struct SpawnBlock {
*/
textwindows int ntspawn(
const char *prog, char *const argv[], char *const envp[],
const char *extravar, struct NtSecurityAttributes *opt_lpProcessAttributes,
struct NtSecurityAttributes *opt_lpThreadAttributes, bool32 bInheritHandles,
uint32_t dwCreationFlags, const char16_t *opt_lpCurrentDirectory,
const char *extravar,
const struct NtSecurityAttributes *opt_lpProcessAttributes,
const struct NtSecurityAttributes *opt_lpThreadAttributes,
bool32 bInheritHandles, uint32_t dwCreationFlags,
const char16_t *opt_lpCurrentDirectory,
const struct NtStartupInfo *lpStartupInfo,
struct NtProcessInformation *opt_out_lpProcessInformation) {
int rc;

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_NTSPAWN_H_
#define COSMOPOLITAN_LIBC_CALLS_NTSPAWN_H_
#include "libc/limits.h"
#include "libc/nt/struct/processinformation.h"
#include "libc/nt/struct/securityattributes.h"
#include "libc/nt/struct/startupinfo.h"
@ -10,8 +11,9 @@ int mkntcmdline(char16_t[ARG_MAX / 2], char *const[]);
int mkntenvblock(char16_t[ARG_MAX / 2], char *const[], const char *,
char[ARG_MAX]);
int ntspawn(const char *, char *const[], char *const[], const char *,
struct NtSecurityAttributes *, struct NtSecurityAttributes *,
bool32, uint32_t, const char16_t *, const struct NtStartupInfo *,
const struct NtSecurityAttributes *,
const struct NtSecurityAttributes *, bool32, uint32_t,
const char16_t *, const struct NtStartupInfo *,
struct NtProcessInformation *);
COSMOPOLITAN_C_END_

View file

@ -23,14 +23,17 @@
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/accessmask.h"
#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/filesharemode.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/files.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/fileno.h"
@ -98,6 +101,16 @@ static textwindows int64_t sys_open_nt_impl(int dirfd, const char *path,
return kNtInvalidHandleValue;
}
if (fattr != -1u) {
// "We have been asked to create a read-only file. "If the file
// already exists, the semantics of the Unix open system call is to
// preserve the existing permissions. If we pass CREATE_ALWAYS and
// FILE_ATTRIBUTE_READONLY to CreateFile, and the file already
// exists, CreateFile will change the file permissions. Avoid that to
// preserve the Unix semantics." -Quoth GoLang syscall_windows.go
attr &= ~kNtFileAttributeReadonly;
}
// kNtTruncateExisting always returns kNtErrorInvalidParameter :'(
if (disp == kNtTruncateExisting) {
if (fattr != -1u) {
@ -107,10 +120,24 @@ static textwindows int64_t sys_open_nt_impl(int dirfd, const char *path,
}
}
// We optimistically request some write permissions in O_RDONLY mode.
// But that might prevent opening some files. So reactively back off.
int extra_perm = 0;
if ((flags & O_ACCMODE) == O_RDONLY) {
extra_perm = kNtFileWriteAttributes | kNtFileWriteEa;
}
// open the file, following symlinks
return __fix_enotdir(CreateFile(path16, perm, share, &kNtIsInheritable, disp,
attr | extra_attr, 0),
path16);
int e = errno;
int64_t hand = CreateFile(path16, perm | extra_perm, share, &kNtIsInheritable,
disp, attr | extra_attr, 0);
if (hand == -1 && errno == EACCES && (flags & O_ACCMODE) == O_RDONLY) {
errno = e;
hand = CreateFile(path16, perm, share, &kNtIsInheritable, disp,
attr | extra_attr, 0);
}
return __fix_enotdir(hand, path16);
}
static textwindows int sys_open_nt_console(int dirfd,

View file

@ -25,6 +25,7 @@
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
@ -92,6 +93,7 @@
* - `O_APPEND` open file for appending only
* - `O_NOFOLLOW` fail with ELOOP if it's a symlink
* - `O_NONBLOCK` asks read/write to fail with `EAGAIN` rather than block
* - `O_UNLINK` delete file automatically on close
* - `O_EXEC` open file for execution only; see fexecve()
* - `O_NOCTTY` prevents `path` from becoming the controlling terminal
* - `O_DIRECTORY` advisory feature for avoiding accidentally opening files
@ -107,7 +109,7 @@
* - `O_SEQUENTIAL` hint sequential access intent (zero on non-Windows)
* - `O_COMPRESSED` ask fs to abstract compression (zero on non-Windows)
* - `O_INDEXED` turns on that slow performance (zero on non-Windows)
* - `O_TMPFILE` should not be used; use tmpfd() or tmpfile() instead
* - `O_TMPFILE` EINVALs on non-Linux; please use tmpfd() / tmpfile()
* There are three regular combinations for the above flags:
* - `O_RDONLY`: Opens existing file for reading. If it doesn't
* exist then nil is returned and errno will be `ENOENT` (or in
@ -148,6 +150,7 @@
* @raise EINTR if we needed to block and a signal was delivered instead
* @raise EEXIST if `O_CREAT|O_EXCL` are used and `path` already existed
* @raise EINVAL if ASCII control codes are used in `path` on Windows
* @raise EINVAL if `O_UNLINK` is used without `O_CREAT|O_EXCL`
* @raise EINVAL if `O_TRUNC` is specified in `O_RDONLY` mode
* @raise EINVAL if `flags` contains unsupported bits
* @raise ECANCELED if thread was cancelled in masked mode
@ -180,6 +183,15 @@ int openat(int dirfd, const char *path, int flags, ...) {
if (!path || (IsAsan() && !__asan_is_valid_str(path))) {
rc = efault();
} else if ((flags & O_UNLINK) &&
(flags & (O_CREAT | O_EXCL)) != (O_CREAT | O_EXCL)) {
// O_UNLINK is a non-standard cosmo extension; we've chosen bits for
// this magic number which we believe are unlikely to interfere with
// the bits chosen by operating systems both today and in the future
// however, due to the risks here and the irregularity of using this
// feature for anything but temporary files, we are going to prevent
// the clever use cases for now; please file an issue if you want it
rc = einval();
} else if (__isfdkind(dirfd, kFdZip)) {
rc = enotsup(); // TODO
} else if (_weaken(__zipos_open) &&
@ -190,20 +202,29 @@ int openat(int dirfd, const char *path, int flags, ...) {
rc = enotsup(); // TODO
}
} else if ((flags & O_ACCMODE) == O_RDONLY && (flags & O_TRUNC)) {
rc = einval(); // Every OS except OpenBSD actually does this D:
// Every operating system we've tested (with the notable exception
// of OpenBSD) will gladly truncate files opened in read-only mode
rc = einval();
} else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) {
rc = sys_openat(dirfd, path, flags, mode);
if (IsFreebsd()) {
// Address FreeBSD divergence from IEEE Std 1003.1-2008 (POSIX.1)
// in the case when O_NOFOLLOW is used, but fails due to symlink.
if (rc == -1 && errno == EMLINK) {
// openat unix userspace
rc = sys_openat(dirfd, path, flags & ~O_UNLINK, mode);
if (rc != -1) {
// openat succeeded
if (flags & O_UNLINK) {
// Implement Cosmopolitan O_UNLINK extension for UNIX
// This cannot fail since we require O_CREAT / O_EXCL
unassert(!sys_unlinkat(dirfd, path, 0));
}
} else {
// openat failed
if (IsFreebsd() && errno == EMLINK) {
// Address FreeBSD divergence from IEEE Std 1003.1-2008 (POSIX.1)
// in the case when O_NOFOLLOW is used, but fails due to symlink.
errno = ELOOP;
}
}
if (IsNetbsd()) {
// Address NetBSD divergence from IEEE Std 1003.1-2008 (POSIX.1)
// in the case when O_NOFOLLOW is used but fails due to symlink.
if (rc == -1 && errno == EFTYPE) {
if (IsNetbsd() && errno == EFTYPE) {
// Address NetBSD divergence from IEEE Std 1003.1-2008 (POSIX.1)
// in the case when O_NOFOLLOW is used but fails due to symlink.
errno = ELOOP;
}
}
@ -216,9 +237,8 @@ int openat(int dirfd, const char *path, int flags, ...) {
}
END_CANCELLATION_POINT;
STRACE("openat(%s, %#s, %s, %#o) → %d% m", DescribeDirfd(dirfd), path,
DescribeOpenFlags(flags), (flags & (O_CREAT | O_TMPFILE)) ? mode : 0,
rc);
STRACE("openat(%s, %#s, %s%s) → %d% m", DescribeDirfd(dirfd), path,
DescribeOpenFlags(flags), DescribeOpenMode(flags, mode), rc);
return rc;
}

127
libc/calls/openatemp.c Normal file
View file

@ -0,0 +1,127 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/intrin/bits.h"
#include "libc/stdio/rand.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
/**
* Opens unique temporary file with maximum generality.
*
* This function is similar to mkstemp() in that it does two things:
*
* 1. Generate a unique filename by mutating `template`
* 2. Return a newly opened file descriptor to the name
*
* Exclusive secure access is assured even if `/tmp` is being used on a
* UNIX system like Super Dimensional Fortress or CPanel where multiple
* hostile adverserial users may exist on a single multi-tenant system.
*
* The substring XXXXXX is replaced with 30 bits of base32 entropy and a
* hundred retries are attempted in the event of collisions. The XXXXXXX
* pattern must be present at the end of the supplied template string.
*
* If the generated filename needs to have a file extension (rather than
* ending with random junk) then this API has the helpful `suffixlen` to
* specify exactly how long that suffix in the template actually is. For
* example if the template is `"/tmp/notes.XXXXXX.txt"` then `suffixlen`
* should be `4`.
*
* The flags `O_RDWR | O_CREAT | O_EXCL` are always set and don't need
* to be specified by the caller. It's a good idea to pass `O_CLOEXEC`
* and some applications may want `O_APPEND`. Cosmopolitan also offers
* `O_UNLINK` which will ensure the created file will delete itself on
* close similar to calling unlink() after this function on `template`
* which is mutated on success, except `O_UNLINK` will work right when
* running on Windows and it's polyfilled automatically on UNIX.
*
* The `mode` parameter should usually be `0600` to ensure owner-only
* read/write access. However it may be useful to set this to `0700`
* when creating executable files. Please note that sometimes `/tmp` is
* mounted by system administrators as `noexec`. It's also permissible
* to pass `0` here, since the `0600` bits are always set implicitly.
*
* ### Examples
*
* Here's an example of how to replicate the functionality of tmpfile()
* which creates an unnamed temporary file as an stdio handle, which is
* guaranteed to either not have a name (unlinked on UNIX), or shall be
* deleted once closed (will perform kNtFileFlagDeleteOnClose on WIN32)
*
* char path[] = "/tmp/XXXXXX";
* int fd = openatemp(AT_FDCWD, path, 0, O_UNLINK, 0);
* FILE *tmp = fdopen(fd, "w+");
*
* Here's an example of how to do mktemp() does, where a temporary file
* name is generated with pretty good POSIX and security best practices
*
* char path[PATH_MAX+1];
* const char *tmpdir = getenv("TMPDIR");
* strlcpy(path, tmpdir ? tmpdir : "/tmp", sizeof(path));
* strlcat(path, "/notes.XXXXXX.txt", sizeof(path));
* close(openatemp(AT_FDCWD, path, 4, O_UNLINK, 0));
* printf("you can use %s to store your notes\n", path);
*
* @param dirfd is open directory file descriptor, which is ignored if
* `template` is an absolute path; or `AT_FDCWD` to specify getcwd
* @param template is a pathname relative to current directory by default,
* that needs to have "XXXXXX" at the end of the string; this memory
* must be mutable and should be owned by the calling thread; it will
* be modified (only on success) to return the generated filename; it
* is recommended that the caller use `kTmpPath` at the beginning of
* the generated `template` path and then set `dirfd` to `AT_FDCWD`
* @param suffixlen may be nonzero to permit characters after the "XXXXXX"
* @param mode is conventionally 0600, for owner-only non-exec access
* @param flags could have O_APPEND, O_CLOEXEC, O_UNLINK, O_SYNC, etc.
* @return exclusive open file descriptor for file at the generated path
* stored to `template`, or -1 w/ errno
* @raise EINVAL if `template` (less the `suffixlen` region) didn't
* end with the string "XXXXXXX"
* @raise EINVAL if `suffixlen` was negative or too large
*/
int openatemp(int dirfd, char *template, int suffixlen, int flags, int mode) {
flags &= ~O_ACCMODE;
flags |= O_RDWR | O_CREAT | O_EXCL;
int len = strlen(template);
if (6 + suffixlen < 6 || 6 + suffixlen > len ||
READ32LE(template + len - suffixlen - 6) != READ32LE("XXXX") ||
READ16LE(template + len - suffixlen - 6 + 4) != READ16LE("XX")) {
return einval();
}
for (;;) {
int w = _rand64();
for (int i = 0; i < 6; ++i) {
template[len - suffixlen - 6 + i] =
"0123456789abcdefghikmnpqrstvwxyz"[w & 31];
w >>= 5;
}
int fd, e = errno;
if ((fd = openat(dirfd, template, flags, mode | 0600)) != -1) {
return fd;
} else if (errno == EEXIST) {
errno = e;
} else {
memcpy(template + len - suffixlen - 6, "XXXXXX", 6);
return -1;
}
}
}

View file

@ -0,0 +1,36 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/runtime/runtime.h"
/**
* Returns environment variable, securely.
*
* This is the same as getenv() except it'll return null if current
* process is a setuid / setgid program.
*
* @param name is environment variable key name, which may not be null
*/
char *secure_getenv(const char *name) {
if (!issetugid()) {
return getenv(name);
} else {
return 0;
}
}

View file

@ -14,8 +14,8 @@ struct dirent { /* linux getdents64 abi */
struct dirstream;
typedef struct dirstream DIR;
DIR *fdopendir(int) dontdiscard;
DIR *opendir(const char *) dontdiscard;
DIR *fdopendir(int) __wur;
DIR *opendir(const char *) __wur;
int closedir(DIR *);
int dirfd(DIR *);
long telldir(DIR *);

View file

@ -25,7 +25,7 @@ int sys_nanosleep_xnu(const struct timespec *, struct timespec *);
int sys_sem_timedwait(int64_t, const struct timespec *);
int sys_utimensat(int, const char *, const struct timespec[2], int);
int sys_utimensat_nt(int, const char *, const struct timespec[2], int);
int sys_utimensat_xnu(int, const char *, const struct timespec[2], int);
int sys_utimensat_old(int, const char *, const struct timespec[2], int);
const char *DescribeTimespec(char[45], int, const struct timespec *);
#define DescribeTimespec(rc, ts) DescribeTimespec(alloca(45), rc, ts)

View file

@ -26,7 +26,7 @@ int sys_linkat_nt(int, const char *, int, const char *);
int sys_madvise_nt(void *, size_t, int);
int sys_mkdirat_nt(int, const char *, uint32_t);
int sys_msync_nt(char *, size_t, int);
int sys_open_nt(int, const char *, uint32_t, int32_t) dontdiscard;
int sys_open_nt(int, const char *, uint32_t, int32_t) __wur;
int sys_pipe_nt(int[hasatleast 2], unsigned);
int sys_renameat_nt(int, const char *, int, const char *);
int sys_sched_yield_nt(void);

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_SYSCALL_SUPPORT_NT_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_SYSCALL_SUPPORT_NT_INTERNAL_H_
#include "libc/limits.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_

View file

@ -15,13 +15,13 @@ int tcsetattr(int, int, const struct termios *);
int openpty(int *, int *, char *, const struct termios *,
const struct winsize *) paramsnonnull((1, 2));
int forkpty(int *, char *, const struct termios *, const struct winsize *)
paramsnonnull((1, 2)) dontdiscard;
paramsnonnull((1, 2)) __wur;
char *ptsname(int);
errno_t ptsname_r(int, char *, size_t);
int grantpt(int);
int unlockpt(int);
int posix_openpt(int) dontdiscard;
int posix_openpt(int) __wur;
int tcdrain(int);
int tcgetsid(int);

View file

@ -20,12 +20,14 @@
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/temp.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/o.h"
#include "libc/temp.h"
#define _O_TMPFILE 000020200000
#define O_TMPFILE_LINUX 0x00410000
int _mkstemp(char *, int);
@ -79,7 +81,7 @@ int tmpfd(void) {
char path[PATH_MAX + 1];
if (IsLinux()) {
e = errno;
if ((fd = open(kTmpPath, O_RDWR | _O_TMPFILE, 0600)) != -1) {
if ((fd = open(kTmpPath, O_RDWR | O_TMPFILE_LINUX, 0600)) != -1) {
return fd;
} else {
errno = e;
@ -90,7 +92,6 @@ int tmpfd(void) {
if (!(prog = program_invocation_short_name)) prog = "tmp";
strlcat(path, prog, sizeof(path));
strlcat(path, ".XXXXXX", sizeof(path));
if ((fd = _mkstemp(path, IsWindows() ? 0x00410000 : 0)) == -1) return -1;
if (!IsWindows()) unassert(!unlink(path));
if ((fd = openatemp(AT_FDCWD, path, 0, O_UNLINK, 0)) == -1) return -1;
return fd;
}

View file

@ -32,6 +32,7 @@
#include "libc/fmt/conv.h"
#include "libc/fmt/libgen.h"
#include "libc/intrin/strace.internal.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
#include "libc/nexgen32e/vendor.internal.h"
#include "libc/runtime/internal.h"
@ -175,7 +176,7 @@ static int unveil_init(void) {
.handled_access_fs = State.fs_mask,
};
// [undocumented] landlock_create_ruleset() always returns O_CLOEXEC
// assert(__sys_fcntl(rc, F_GETFD, 0) == FD_CLOEXEC);
// assert(__sys_fcntl(rc, F_GETFD) == FD_CLOEXEC);
if ((rc = landlock_create_ruleset(&attr, sizeof(attr), 0)) < 0) return -1;
// grant file descriptor a higher number that's less likely to interfere
if ((fd = __sys_fcntl(rc, F_DUPFD_CLOEXEC, 100)) == -1) {

View file

@ -48,12 +48,16 @@ int __utimens(int fd, const char *path, const struct timespec ts[2],
(path && (_weaken(__zipos_parseuri) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1))) {
rc = erofs();
} else if (IsLinux() && !__is_linux_2_6_23() && fd == AT_FDCWD && !flags) {
rc = sys_utimes(path, (void *)ts); // rhel5 truncates to seconds
} else if (!IsWindows()) {
} else if (IsXnu() || (IsLinux() && !__is_linux_2_6_23())) {
rc = sys_utimensat_old(fd, path, ts, flags);
} else if (IsLinux() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) {
rc = sys_utimensat(fd, path, ts, flags);
} else {
} else if (IsWindows()) {
rc = sys_utimensat_nt(fd, path, ts, flags);
} else if (IsMetal()) {
rc = enosys();
} else {
rc = enosys();
}
return rc;
}

View file

@ -41,8 +41,14 @@ textwindows int sys_utimensat_nt(int dirfd, const char *path,
if (path) {
if (__mkntpathat(dirfd, path, 0, path16) == -1) return -1;
if ((fh = CreateFile(path16, kNtFileWriteAttributes, kNtFileShareRead, NULL,
kNtOpenExisting, kNtFileAttributeNormal, 0)) != -1) {
if ((fh = CreateFile(
path16, kNtFileWriteAttributes,
kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, NULL,
kNtOpenExisting,
kNtFileAttributeNormal | kNtFileFlagBackupSemantics |
((flags & AT_SYMLINK_NOFOLLOW) ? kNtFileFlagOpenReparsePoint
: 0),
0)) != -1) {
closeme = fh;
} else {
return __winerr();

View file

@ -16,27 +16,78 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/fmt/conv.h"
#include "libc/nexgen32e/nexgen32e.h"
#include "libc/dce.h"
#include "libc/fmt/itoa.h"
#include "libc/limits.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/utime.h"
#include "libc/sysv/errfuns.h"
#include "libc/time/time.h"
int sys_utimensat_xnu(int dirfd, const char *path, const struct timespec ts[2],
int sys_utimensat_old(int dirfd, const char *path, const struct timespec ts[2],
int flags) {
struct stat st;
struct timeval now, tv[2];
if (flags) return einval();
char buf[PATH_MAX + 1];
// validate usage
if (path) {
if (dirfd != AT_FDCWD) {
return enotsup();
}
} else {
unassert(dirfd >= 0);
}
if (flags) {
return einval();
}
// xnu has futimes(), but rhel5 only has utimes()
// on linux we'll try to get the path from procfs
if (IsLinux() && !path) {
char procpath[36];
char *p = procpath;
p = stpcpy(p, "/proc/");
p = FormatInt32(p, getpid());
p = stpcpy(p, "/fd/");
p = FormatInt32(p, dirfd);
ssize_t got;
if ((got = readlink(procpath, buf, sizeof(buf))) == -1) {
return -1;
}
if (got == sizeof(buf)) {
return enametoolong();
}
unassert(buf[0] == '/');
buf[got] = 0;
path = buf;
}
// perform preliminary system calls ahead of time
struct timeval now;
if (!ts || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) {
gettimeofday(&now, NULL);
unassert(!gettimeofday(&now, 0));
}
if (ts && (ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)) {
if (fstatat(dirfd, path, &st, flags) == -1) return -1;
struct stat st;
if (ts && (ts[0].tv_nsec == UTIME_OMIT || ts[1].tv_nsec == UTIME_OMIT)) {
if (path) {
if (sys_fstatat(AT_FDCWD, path, &st, 0) == -1) {
return -1;
}
} else {
if (sys_fstat(dirfd, &st) == -1) {
return -1;
}
}
}
// change the timestamps
struct timeval tv[2];
if (ts) {
if (ts[0].tv_nsec == UTIME_NOW) {
tv[0] = now;
@ -56,17 +107,11 @@ int sys_utimensat_xnu(int dirfd, const char *path, const struct timespec ts[2],
tv[0] = now;
tv[1] = now;
}
// apply the new timestamps
if (path) {
if (dirfd == AT_FDCWD) {
return sys_utimes(path, tv);
} else {
return enosys();
}
return sys_utimes(path, tv);
} else {
if (dirfd != AT_FDCWD) {
return sys_futimes(dirfd, tv);
} else {
return einval();
}
return sys_futimes(dirfd, tv);
}
}

View file

@ -16,39 +16,37 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/runtime/zipos.internal.h"
#include "libc/sysv/consts/at.h"
#include "libc/time/time.h"
#include "libc/runtime/zipos.internal.h"
int sys_utimensat(int dirfd, const char *path, const struct timespec ts[2],
int flags) {
int rc, olderr;
struct timeval tv[2];
if (!IsXnu()) {
if (!path && (IsFreebsd() || IsNetbsd() || IsOpenbsd())) {
rc = sys_futimens(dirfd, ts);
} else {
olderr = errno;
rc = __sys_utimensat(dirfd, path, ts, flags);
// TODO(jart): How does RHEL5 do futimes()?
if (rc == -1 && errno == ENOSYS && path) {
errno = olderr;
if (ts) {
tv[0] = timespec_totimeval(ts[0]);
tv[1] = timespec_totimeval(ts[1]);
rc = sys_utimes(path, tv);
} else {
rc = sys_utimes(path, NULL);
}
unassert(!IsWindows() && !IsXnu());
if (!path && (IsFreebsd() || IsNetbsd() || IsOpenbsd())) {
rc = sys_futimens(dirfd, ts);
} else {
olderr = errno;
rc = __sys_utimensat(dirfd, path, ts, flags);
// TODO(jart): How does RHEL5 do futimes()?
if (rc == -1 && errno == ENOSYS && path) {
errno = olderr;
if (ts) {
tv[0] = timespec_totimeval(ts[0]);
tv[1] = timespec_totimeval(ts[1]);
rc = sys_utimes(path, tv);
} else {
rc = sys_utimes(path, NULL);
}
}
return rc;
} else {
return sys_utimensat_xnu(dirfd, path, ts, flags);
}
return rc;
}

View file

@ -16,17 +16,10 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/asan.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/runtime/zipos.internal.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h"
/**
@ -43,9 +36,10 @@
* @param dirfd can be `AT_FDCWD` or an open directory
* @param path is filename whose timestamps should be modified
* @param ts is {access, modified} timestamps, or null for current time
* @param flags can have `AT_SYMLINK_NOFOLLOW` when `path` is specified
* @param flags can have `AT_SYMLINK_NOFOLLOW`
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if `flags` had an unrecognized value
* @raise EINVAL on XNU or RHEL5 when any `flags` are used
* @raise EPERM if pledge() is in play without `fattr` promise
* @raise EACCES if unveil() is in play and `path` isn't unveiled
* @raise EINVAL if `ts` specifies a nanosecond value that's out of range
@ -54,23 +48,21 @@
* @raise EBADF if `dirfd` isn't a valid fd or `AT_FDCWD`
* @raise EFAULT if `path` or `ts` memory was invalid
* @raise EROFS if `path` is on read-only filesystem (e.g. zipos)
* @raise ENOSYS on bare metal or on rhel5 when `dirfd` or `flags` is used
* @raise ENOTSUP on XNU or RHEL5 when `dirfd` isn't `AT_FDCWD`
* @raise ENOSYS on bare metal
* @asyncsignalsafe
* @threadsafe
*/
int utimensat(int dirfd, const char *path, const struct timespec ts[2],
int flags) {
int rc;
if (!path) {
rc = efault(); // linux kernel abi behavior isn't supported
} else {
rc = __utimens(dirfd, path, ts, flags);
}
STRACE("utimensat(%s, %#s, {%s, %s}, %#o) → %d% m", DescribeDirfd(dirfd),
path, DescribeTimespec(0, ts), DescribeTimespec(0, ts ? ts + 1 : 0),
flags, rc);
return rc;
}

View file

@ -22,6 +22,7 @@
#include "libc/calls/struct/timeval.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h"
/**
* Changes last accessed/modified timestamps on file.
@ -36,7 +37,9 @@
int utimes(const char *path, const struct timeval tv[2]) {
int rc;
struct timespec ts[2];
if (tv) {
if (!path) {
rc = efault();
} else if (tv) {
ts[0] = timeval_totimespec(tv[0]);
ts[1] = timeval_totimespec(tv[1]);
rc = __utimens(AT_FDCWD, path, ts, 0);