This is analogous to the nxt_file_stderr() function and will be used in a subsequent commit. This function redirects stdout to a given file descriptor. Reviewed-by: Alejandro Colomar <alx@nginx.com> Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
696 lines
15 KiB
C
696 lines
15 KiB
C
|
|
/*
|
|
* Copyright (C) Igor Sysoev
|
|
* Copyright (C) NGINX, Inc.
|
|
*/
|
|
|
|
#include <nxt_main.h>
|
|
|
|
|
|
nxt_int_t
|
|
nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode,
|
|
nxt_uint_t create, nxt_file_access_t access)
|
|
{
|
|
#ifdef __CYGWIN__
|
|
mode |= O_BINARY;
|
|
#endif
|
|
|
|
/* O_NONBLOCK is to prevent blocking on FIFOs, special devices, etc. */
|
|
mode |= (O_NONBLOCK | create);
|
|
|
|
file->fd = open((char *) file->name, mode, access);
|
|
|
|
file->error = (file->fd == -1) ? nxt_errno : 0;
|
|
|
|
#if (NXT_DEBUG)
|
|
nxt_thread_time_update(task->thread);
|
|
#endif
|
|
|
|
nxt_debug(task, "open(\"%FN\", 0x%uXi, 0x%uXi): %FD err:%d",
|
|
file->name, mode, access, file->fd, file->error);
|
|
|
|
if (file->fd != -1) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
if (file->log_level != 0) {
|
|
nxt_log(task, file->log_level, "open(\"%FN\") failed %E",
|
|
file->name, file->error);
|
|
}
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
#if (NXT_HAVE_OPENAT2)
|
|
|
|
nxt_int_t
|
|
nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode,
|
|
nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd,
|
|
nxt_uint_t resolve)
|
|
{
|
|
struct open_how how;
|
|
|
|
nxt_memzero(&how, sizeof(how));
|
|
|
|
/* O_NONBLOCK is to prevent blocking on FIFOs, special devices, etc. */
|
|
mode |= (O_NONBLOCK | create);
|
|
|
|
how.flags = mode;
|
|
how.mode = access;
|
|
how.resolve = resolve;
|
|
|
|
file->fd = syscall(SYS_openat2, dfd, file->name, &how, sizeof(how));
|
|
|
|
file->error = (file->fd == -1) ? nxt_errno : 0;
|
|
|
|
#if (NXT_DEBUG)
|
|
nxt_thread_time_update(task->thread);
|
|
#endif
|
|
|
|
nxt_debug(task, "openat2(%FD, \"%FN\"): %FD err:%d", dfd, file->name,
|
|
file->fd, file->error);
|
|
|
|
if (file->fd != -1) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
if (file->log_level != 0) {
|
|
nxt_log(task, file->log_level, "openat2(%FD, \"%FN\") failed %E", dfd,
|
|
file->name, file->error);
|
|
}
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void
|
|
nxt_file_close(nxt_task_t *task, nxt_file_t *file)
|
|
{
|
|
nxt_debug(task, "close(%FD)", file->fd);
|
|
|
|
if (close(file->fd) != 0) {
|
|
nxt_alert(task, "close(%FD, \"%FN\") failed %E",
|
|
file->fd, file->name, nxt_errno);
|
|
}
|
|
}
|
|
|
|
|
|
ssize_t
|
|
nxt_file_write(nxt_file_t *file, const u_char *buf, size_t size,
|
|
nxt_off_t offset)
|
|
{
|
|
ssize_t n;
|
|
|
|
nxt_thread_debug(thr);
|
|
|
|
n = pwrite(file->fd, buf, size, offset);
|
|
|
|
file->error = (n < 0) ? nxt_errno : 0;
|
|
|
|
nxt_thread_time_debug_update(thr);
|
|
|
|
nxt_log_debug(thr->log, "pwrite(%FD, %p, %uz, %O): %z",
|
|
file->fd, buf, size, offset, n);
|
|
|
|
if (nxt_fast_path(n >= 0)) {
|
|
return n;
|
|
}
|
|
|
|
nxt_thread_log_alert("pwrite(%FD, \"%FN\", %p, %uz, %O) failed %E",
|
|
file->fd, file->name, buf, size,
|
|
offset, file->error);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
nxt_file_read(nxt_file_t *file, u_char *buf, size_t size, nxt_off_t offset)
|
|
{
|
|
ssize_t n;
|
|
|
|
nxt_thread_debug(thr);
|
|
|
|
n = pread(file->fd, buf, size, offset);
|
|
|
|
file->error = (n <= 0) ? nxt_errno : 0;
|
|
|
|
nxt_thread_time_debug_update(thr);
|
|
|
|
nxt_log_debug(thr->log, "pread(%FD, %p, %uz, %O): %z",
|
|
file->fd, buf, size, offset, n);
|
|
|
|
if (nxt_fast_path(n >= 0)) {
|
|
return n;
|
|
}
|
|
|
|
nxt_thread_log_alert("pread(%FD, \"%FN\", %p, %uz, %O) failed %E",
|
|
file->fd, file->name, buf, size,
|
|
offset, file->error);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
#if (NXT_HAVE_READAHEAD)
|
|
|
|
/* FreeBSD 8.0 fcntl(F_READAHEAD, size) enables read ahead up to the size. */
|
|
|
|
void
|
|
nxt_file_read_ahead(nxt_file_t *file, nxt_off_t offset, size_t size)
|
|
{
|
|
int ret;
|
|
u_char buf;
|
|
|
|
ret = fcntl(file->fd, F_READAHEAD, (int) size);
|
|
|
|
nxt_thread_log_debug("fcntl(%FD, F_READAHEAD, %uz): %d",
|
|
file->fd, size, ret);
|
|
|
|
if (nxt_fast_path(ret != -1)) {
|
|
(void) nxt_file_read(file, &buf, 1, offset);
|
|
return;
|
|
}
|
|
|
|
nxt_thread_log_alert("fcntl(%FD, \"%FN\", F_READAHEAD, %uz) failed %E",
|
|
file->fd, file->name, size, nxt_errno);
|
|
}
|
|
|
|
#elif (NXT_HAVE_POSIX_FADVISE)
|
|
|
|
/*
|
|
* POSIX_FADV_SEQUENTIAL
|
|
* Linux doubles the default readahead window size of a backing device
|
|
* which is usually 128K.
|
|
*
|
|
* FreeBSD does nothing.
|
|
*
|
|
* POSIX_FADV_WILLNEED
|
|
* Linux preloads synchronously up to 2M of specified file region in
|
|
* the kernel page cache. Linux-specific readahead(2) syscall does
|
|
* the same. Both operations are blocking despite posix_fadvise(2)
|
|
* claims the opposite.
|
|
*
|
|
* FreeBSD does nothing.
|
|
*/
|
|
|
|
void
|
|
nxt_file_read_ahead(nxt_file_t *file, nxt_off_t offset, size_t size)
|
|
{
|
|
nxt_err_t err;
|
|
|
|
err = posix_fadvise(file->fd, offset, size, POSIX_FADV_WILLNEED);
|
|
|
|
nxt_thread_log_debug("posix_fadvise(%FD, \"%FN\", %O, %uz, %d): %d",
|
|
file->fd, file->name, offset, size,
|
|
POSIX_FADV_WILLNEED, err);
|
|
|
|
if (nxt_fast_path(err == 0)) {
|
|
return;
|
|
}
|
|
|
|
nxt_thread_log_alert("posix_fadvise(%FD, \"%FN\", %O, %uz, %d) failed %E",
|
|
file->fd, file->name, offset, size,
|
|
POSIX_FADV_WILLNEED, err);
|
|
}
|
|
|
|
#elif (NXT_HAVE_RDAHEAD)
|
|
|
|
/* MacOSX fcntl(F_RDAHEAD). */
|
|
|
|
void
|
|
nxt_file_read_ahead(nxt_file_t *file, nxt_off_t offset, size_t size)
|
|
{
|
|
int ret;
|
|
u_char buf;
|
|
|
|
ret = fcntl(file->fd, F_RDAHEAD, 1);
|
|
|
|
nxt_thread_log_debug("fcntl(%FD, F_RDAHEAD, 1): %d", file->fd, ret);
|
|
|
|
if (nxt_fast_path(ret != -1)) {
|
|
(void) nxt_file_read(file, &buf, 1, offset);
|
|
return;
|
|
}
|
|
|
|
nxt_thread_log_alert("fcntl(%FD, \"%FN\", F_RDAHEAD, 1) failed %E",
|
|
file->fd, file->name, nxt_errno);
|
|
}
|
|
|
|
#else
|
|
|
|
void
|
|
nxt_file_read_ahead(nxt_file_t *file, nxt_off_t offset, size_t size)
|
|
{
|
|
u_char buf;
|
|
|
|
(void) nxt_file_read(file, &buf, 1, offset);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
nxt_int_t
|
|
nxt_file_info(nxt_file_t *file, nxt_file_info_t *fi)
|
|
{
|
|
int n;
|
|
|
|
if (file->fd == NXT_FILE_INVALID) {
|
|
n = stat((char *) file->name, fi);
|
|
|
|
file->error = (n != 0) ? nxt_errno : 0;
|
|
|
|
nxt_thread_log_debug("stat(\"%FN)\": %d", file->name, n);
|
|
|
|
if (n == 0) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
if (file->log_level != 0) {
|
|
nxt_thread_log_error(file->log_level, "stat(\"%FN\") failed %E",
|
|
file->name, file->error);
|
|
}
|
|
|
|
return NXT_ERROR;
|
|
|
|
} else {
|
|
n = fstat(file->fd, fi);
|
|
|
|
file->error = (n != 0) ? nxt_errno : 0;
|
|
|
|
nxt_thread_log_debug("fstat(%FD): %d", file->fd, n);
|
|
|
|
if (n == 0) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
/* Use NXT_LOG_ALERT because fstat() error on open file is strange. */
|
|
|
|
nxt_thread_log_alert("fstat(%FD, \"%FN\") failed %E",
|
|
file->fd, file->name, file->error);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_file_delete(nxt_file_name_t *name)
|
|
{
|
|
nxt_thread_log_debug("unlink(\"%FN\")", name);
|
|
|
|
if (nxt_fast_path(unlink((char *) name) == 0)) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_thread_log_alert("unlink(\"%FN\") failed %E", name, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_file_set_access(nxt_file_name_t *name, nxt_file_access_t access)
|
|
{
|
|
if (nxt_fast_path(chmod((char *) name, access) == 0)) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_thread_log_alert("chmod(\"%FN\") failed %E", name, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_file_rename(nxt_file_name_t *old_name, nxt_file_name_t *new_name)
|
|
{
|
|
int ret;
|
|
|
|
nxt_thread_log_debug("rename(\"%FN\", \"%FN\")", old_name, new_name);
|
|
|
|
ret = rename((char *) old_name, (char *) new_name);
|
|
if (nxt_fast_path(ret == 0)) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_thread_log_alert("rename(\"%FN\", \"%FN\") failed %E",
|
|
old_name, new_name, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* ioctl(FIONBIO) sets a non-blocking mode using one syscall,
|
|
* thereas fcntl(F_SETFL, O_NONBLOCK) needs to learn the current state
|
|
* using fcntl(F_GETFL).
|
|
*
|
|
* ioctl() and fcntl() are syscalls at least in Linux 2.2, FreeBSD 2.x,
|
|
* and Solaris 7.
|
|
*
|
|
* Linux 2.4 uses BKL for ioctl() and fcntl(F_SETFL).
|
|
* Linux 2.6 does not use BKL.
|
|
*/
|
|
|
|
#if (NXT_HAVE_FIONBIO)
|
|
|
|
nxt_int_t
|
|
nxt_fd_nonblocking(nxt_task_t *task, nxt_fd_t fd)
|
|
{
|
|
int nb;
|
|
|
|
nb = 1;
|
|
|
|
if (nxt_fast_path(ioctl(fd, FIONBIO, &nb) != -1)) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_alert(task, "ioctl(%d, FIONBIO) failed %E", fd, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_fd_blocking(nxt_task_t *task, nxt_fd_t fd)
|
|
{
|
|
int nb;
|
|
|
|
nb = 0;
|
|
|
|
if (nxt_fast_path(ioctl(fd, FIONBIO, &nb) != -1)) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_alert(task, "ioctl(%d, !FIONBIO) failed %E", fd, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
#else /* !(NXT_HAVE_FIONBIO) */
|
|
|
|
nxt_int_t
|
|
nxt_fd_nonblocking(nxt_task_t *task, nxt_fd_t fd)
|
|
{
|
|
int flags;
|
|
|
|
flags = fcntl(fd, F_GETFL);
|
|
|
|
if (nxt_slow_path(flags == -1)) {
|
|
nxt_alert(task, "fcntl(%d, F_GETFL) failed %E", fd, nxt_errno);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
flags |= O_NONBLOCK;
|
|
|
|
if (nxt_slow_path(fcntl(fd, F_SETFL, flags) == -1)) {
|
|
nxt_alert(task, "fcntl(%d, F_SETFL, O_NONBLOCK) failed %E",
|
|
fd, nxt_errno);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_fd_blocking(nxt_task_t *task, nxt_fd_t fd)
|
|
{
|
|
int flags;
|
|
|
|
flags = fcntl(fd, F_GETFL);
|
|
|
|
if (nxt_slow_path(flags == -1)) {
|
|
nxt_alert(task, "fcntl(%d, F_GETFL) failed %E", fd, nxt_errno);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
flags &= O_NONBLOCK;
|
|
|
|
if (nxt_slow_path(fcntl(fd, F_SETFL, flags) == -1)) {
|
|
nxt_alert(task, "fcntl(%d, F_SETFL, !O_NONBLOCK) failed %E",
|
|
fd, nxt_errno);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
#endif /* NXT_HAVE_FIONBIO */
|
|
|
|
|
|
ssize_t
|
|
nxt_fd_write(nxt_fd_t fd, u_char *buf, size_t size)
|
|
{
|
|
ssize_t n;
|
|
nxt_err_t err;
|
|
|
|
n = write(fd, buf, size);
|
|
|
|
err = (n == -1) ? nxt_errno : 0;
|
|
|
|
nxt_thread_log_debug("write(%FD, %p, %uz): %z", fd, buf, size, n);
|
|
|
|
if (nxt_slow_path(n <= 0)) {
|
|
nxt_thread_log_alert("write(%FD) failed %E", fd, err);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
nxt_fd_read(nxt_fd_t fd, u_char *buf, size_t size)
|
|
{
|
|
ssize_t n;
|
|
nxt_err_t err;
|
|
|
|
n = read(fd, buf, size);
|
|
|
|
err = (n == -1) ? nxt_errno : 0;
|
|
|
|
nxt_thread_log_debug("read(%FD, %p, %uz): %z", fd, buf, size, n);
|
|
|
|
if (nxt_slow_path(n <= 0)) {
|
|
|
|
if (err == NXT_EAGAIN) {
|
|
return 0;
|
|
}
|
|
|
|
nxt_thread_log_alert("read(%FD) failed %E", fd, err);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_fd_close(nxt_fd_t fd)
|
|
{
|
|
nxt_thread_log_debug("close(%FD)", fd);
|
|
|
|
if (nxt_slow_path(close(fd) != 0)) {
|
|
nxt_thread_log_alert("close(%FD) failed %E", fd, nxt_errno);
|
|
}
|
|
}
|
|
|
|
|
|
FILE *
|
|
nxt_file_fopen(nxt_task_t *task, const char *pathname, const char *mode)
|
|
{
|
|
int err;
|
|
FILE *fp;
|
|
|
|
#if (NXT_DEBUG)
|
|
nxt_thread_time_update(task->thread);
|
|
#endif
|
|
|
|
fp = fopen(pathname, mode);
|
|
err = (fp == NULL) ? nxt_errno : 0;
|
|
|
|
nxt_debug(task, "fopen(\"%s\", \"%s\"): fp:%p err:%d", pathname, mode, fp,
|
|
err);
|
|
|
|
if (nxt_fast_path(fp != NULL)) {
|
|
return fp;
|
|
}
|
|
|
|
nxt_alert(task, "fopen(\"%s\") failed %E", pathname, err);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_file_fclose(nxt_task_t *task, FILE *fp)
|
|
{
|
|
nxt_debug(task, "fclose(%p)", fp);
|
|
|
|
if (nxt_slow_path(fclose(fp) == -1)) {
|
|
nxt_alert(task, "fclose() failed %E", nxt_errno);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* nxt_file_redirect() redirects the file to the fd descriptor.
|
|
* Then the fd descriptor is closed.
|
|
*/
|
|
|
|
nxt_int_t
|
|
nxt_file_redirect(nxt_file_t *file, nxt_fd_t fd)
|
|
{
|
|
nxt_thread_log_debug("dup2(%FD, %FD, \"%FN\")", fd, file->fd, file->name);
|
|
|
|
if (dup2(fd, file->fd) == -1) {
|
|
nxt_thread_log_alert("dup2(%FD, %FD, \"%FN\") failed %E",
|
|
fd, file->fd, file->name, nxt_errno);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
if (close(fd) != 0) {
|
|
nxt_thread_log_alert("close(%FD, \"%FN\") failed %E",
|
|
fd, file->name, nxt_errno);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
/* nxt_file_stdout() redirects the stdout descriptor to the file. */
|
|
|
|
nxt_int_t
|
|
nxt_file_stdout(nxt_file_t *file)
|
|
{
|
|
nxt_thread_log_debug("dup2(%FD, %FD, \"%FN\")",
|
|
file->fd, STDOUT_FILENO, file->name);
|
|
|
|
if (dup2(file->fd, STDOUT_FILENO) != -1) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_thread_log_alert("dup2(%FD, %FD, \"%FN\") failed %E",
|
|
file->fd, STDOUT_FILENO, file->name, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
/* nxt_file_stderr() redirects the stderr descriptor to the file. */
|
|
|
|
nxt_int_t
|
|
nxt_file_stderr(nxt_file_t *file)
|
|
{
|
|
nxt_thread_log_debug("dup2(%FD, %FD, \"%FN\")",
|
|
file->fd, STDERR_FILENO, file->name);
|
|
|
|
if (dup2(file->fd, STDERR_FILENO) != -1) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
nxt_thread_log_alert("dup2(%FD, %FD, \"%FN\") failed %E",
|
|
file->fd, STDERR_FILENO, file->name, nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_stderr_start(void)
|
|
{
|
|
int flags, fd;
|
|
|
|
flags = fcntl(nxt_stderr, F_GETFL);
|
|
|
|
if (flags != -1) {
|
|
/*
|
|
* If the stderr output of a multithreaded application is
|
|
* redirected to a file:
|
|
* Linux, Solaris and MacOSX do not write atomically to the output;
|
|
* MacOSX besides adds zeroes to the output.
|
|
* O_APPEND fixes this.
|
|
*/
|
|
(void) fcntl(nxt_stderr, F_SETFL, flags | O_APPEND);
|
|
|
|
} else {
|
|
/*
|
|
* The stderr descriptor is closed before application start.
|
|
* Reserve the stderr descriptor for future use. Errors are
|
|
* ignored because anyway they could be written nowhere.
|
|
*/
|
|
fd = open("/dev/null", O_WRONLY | O_APPEND);
|
|
|
|
if (fd != -1) {
|
|
(void) dup2(fd, nxt_stderr);
|
|
|
|
if (fd != nxt_stderr) {
|
|
(void) close(fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_pipe_create(nxt_task_t *task, nxt_fd_t *pp, nxt_bool_t nbread,
|
|
nxt_bool_t nbwrite)
|
|
{
|
|
if (pipe(pp) != 0) {
|
|
nxt_alert(task, "pipe() failed %E", nxt_errno);
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
nxt_debug(task, "pipe(): %FD:%FD", pp[0], pp[1]);
|
|
|
|
if (nbread) {
|
|
if (nxt_fd_nonblocking(task, pp[0]) != NXT_OK) {
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (nbwrite) {
|
|
if (nxt_fd_nonblocking(task, pp[1]) != NXT_OK) {
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nxt_pipe_close(nxt_task_t *task, nxt_fd_t *pp)
|
|
{
|
|
nxt_debug(task, "pipe close(%FD:%FD)", pp[0], pp[1]);
|
|
|
|
if (close(pp[0]) != 0) {
|
|
nxt_alert(task, "pipe close(%FD) failed %E", pp[0], nxt_errno);
|
|
}
|
|
|
|
if (close(pp[1]) != 0) {
|
|
nxt_alert(task, "pipe close(%FD) failed %E", pp[1], nxt_errno);
|
|
}
|
|
}
|
|
|
|
|
|
size_t
|
|
nxt_dir_current(char *buf, size_t len)
|
|
{
|
|
if (nxt_fast_path(getcwd(buf, len) != NULL)) {
|
|
return nxt_strlen(buf);
|
|
}
|
|
|
|
nxt_thread_log_alert("getcwd(%uz) failed %E", len, nxt_errno);
|
|
|
|
return 0;
|
|
}
|