mirror of
https://github.com/nginx/nginx.git
synced 2024-12-22 15:13:28 -06:00
9e0d1236f1
This fixes wrong permissions and file time after cross-device MOVE in the DAV module (ticket #1577). Broken in 8101d9101ed8 (0.8.9) when cross-device copying was introduced in ngx_ext_rename_file(). With this change, ngx_copy_file() always calls ngx_set_file_time(), either with the time provided, or with the time from the original file. This is considered acceptable given that copying the file is costly anyway, and optimizing cases when we do not need to preserve time will require interface changes.
1129 lines
26 KiB
C
1129 lines
26 KiB
C
|
|
/*
|
|
* Copyright (C) Igor Sysoev
|
|
* Copyright (C) Nginx, Inc.
|
|
*/
|
|
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
|
|
|
|
static ngx_int_t ngx_test_full_name(ngx_str_t *name);
|
|
|
|
|
|
static ngx_atomic_t temp_number = 0;
|
|
ngx_atomic_t *ngx_temp_number = &temp_number;
|
|
ngx_atomic_int_t ngx_random_number = 123456;
|
|
|
|
|
|
ngx_int_t
|
|
ngx_get_full_name(ngx_pool_t *pool, ngx_str_t *prefix, ngx_str_t *name)
|
|
{
|
|
size_t len;
|
|
u_char *p, *n;
|
|
ngx_int_t rc;
|
|
|
|
rc = ngx_test_full_name(name);
|
|
|
|
if (rc == NGX_OK) {
|
|
return rc;
|
|
}
|
|
|
|
len = prefix->len;
|
|
|
|
#if (NGX_WIN32)
|
|
|
|
if (rc == 2) {
|
|
len = rc;
|
|
}
|
|
|
|
#endif
|
|
|
|
n = ngx_pnalloc(pool, len + name->len + 1);
|
|
if (n == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
p = ngx_cpymem(n, prefix->data, len);
|
|
ngx_cpystrn(p, name->data, name->len + 1);
|
|
|
|
name->len += len;
|
|
name->data = n;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_test_full_name(ngx_str_t *name)
|
|
{
|
|
#if (NGX_WIN32)
|
|
u_char c0, c1;
|
|
|
|
c0 = name->data[0];
|
|
|
|
if (name->len < 2) {
|
|
if (c0 == '/') {
|
|
return 2;
|
|
}
|
|
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
c1 = name->data[1];
|
|
|
|
if (c1 == ':') {
|
|
c0 |= 0x20;
|
|
|
|
if ((c0 >= 'a' && c0 <= 'z')) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
if (c1 == '/') {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (c0 == '/') {
|
|
return 2;
|
|
}
|
|
|
|
return NGX_DECLINED;
|
|
|
|
#else
|
|
|
|
if (name->data[0] == '/') {
|
|
return NGX_OK;
|
|
}
|
|
|
|
return NGX_DECLINED;
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
ssize_t
|
|
ngx_write_chain_to_temp_file(ngx_temp_file_t *tf, ngx_chain_t *chain)
|
|
{
|
|
ngx_int_t rc;
|
|
|
|
if (tf->file.fd == NGX_INVALID_FILE) {
|
|
rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool,
|
|
tf->persistent, tf->clean, tf->access);
|
|
|
|
if (rc != NGX_OK) {
|
|
return rc;
|
|
}
|
|
|
|
if (tf->log_level) {
|
|
ngx_log_error(tf->log_level, tf->file.log, 0, "%s %V",
|
|
tf->warn, &tf->file.name);
|
|
}
|
|
}
|
|
|
|
#if (NGX_THREADS && NGX_HAVE_PWRITEV)
|
|
|
|
if (tf->thread_write) {
|
|
return ngx_thread_write_chain_to_file(&tf->file, chain, tf->offset,
|
|
tf->pool);
|
|
}
|
|
|
|
#endif
|
|
|
|
return ngx_write_chain_to_file(&tf->file, chain, tf->offset, tf->pool);
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,
|
|
ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)
|
|
{
|
|
size_t levels;
|
|
u_char *p;
|
|
uint32_t n;
|
|
ngx_err_t err;
|
|
ngx_str_t name;
|
|
ngx_uint_t prefix;
|
|
ngx_pool_cleanup_t *cln;
|
|
ngx_pool_cleanup_file_t *clnf;
|
|
|
|
if (file->name.len) {
|
|
name = file->name;
|
|
levels = 0;
|
|
prefix = 1;
|
|
|
|
} else {
|
|
name = path->name;
|
|
levels = path->len;
|
|
prefix = 0;
|
|
}
|
|
|
|
file->name.len = name.len + 1 + levels + 10;
|
|
|
|
file->name.data = ngx_pnalloc(pool, file->name.len + 1);
|
|
if (file->name.data == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
#if 0
|
|
for (i = 0; i < file->name.len; i++) {
|
|
file->name.data[i] = 'X';
|
|
}
|
|
#endif
|
|
|
|
p = ngx_cpymem(file->name.data, name.data, name.len);
|
|
|
|
if (prefix) {
|
|
*p = '.';
|
|
}
|
|
|
|
p += 1 + levels;
|
|
|
|
n = (uint32_t) ngx_next_temp_number(0);
|
|
|
|
cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
|
|
if (cln == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
for ( ;; ) {
|
|
(void) ngx_sprintf(p, "%010uD%Z", n);
|
|
|
|
if (!prefix) {
|
|
ngx_create_hashed_filename(path, file->name.data, file->name.len);
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
|
|
"hashed path: %s", file->name.data);
|
|
|
|
file->fd = ngx_open_tempfile(file->name.data, persistent, access);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
|
|
"temp fd:%d", file->fd);
|
|
|
|
if (file->fd != NGX_INVALID_FILE) {
|
|
|
|
cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
|
|
clnf = cln->data;
|
|
|
|
clnf->fd = file->fd;
|
|
clnf->name = file->name.data;
|
|
clnf->log = pool->log;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
err = ngx_errno;
|
|
|
|
if (err == NGX_EEXIST_FILE) {
|
|
n = (uint32_t) ngx_next_temp_number(1);
|
|
continue;
|
|
}
|
|
|
|
if ((path->level[0] == 0) || (err != NGX_ENOPATH)) {
|
|
ngx_log_error(NGX_LOG_CRIT, file->log, err,
|
|
ngx_open_tempfile_n " \"%s\" failed",
|
|
file->name.data);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_create_path(file, path) == NGX_ERROR) {
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ngx_create_hashed_filename(ngx_path_t *path, u_char *file, size_t len)
|
|
{
|
|
size_t i, level;
|
|
ngx_uint_t n;
|
|
|
|
i = path->name.len + 1;
|
|
|
|
file[path->name.len + path->len] = '/';
|
|
|
|
for (n = 0; n < NGX_MAX_PATH_LEVEL; n++) {
|
|
level = path->level[n];
|
|
|
|
if (level == 0) {
|
|
break;
|
|
}
|
|
|
|
len -= level;
|
|
file[i - 1] = '/';
|
|
ngx_memcpy(&file[i], &file[len], level);
|
|
i += level + 1;
|
|
}
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_create_path(ngx_file_t *file, ngx_path_t *path)
|
|
{
|
|
size_t pos;
|
|
ngx_err_t err;
|
|
ngx_uint_t i;
|
|
|
|
pos = path->name.len;
|
|
|
|
for (i = 0; i < NGX_MAX_PATH_LEVEL; i++) {
|
|
if (path->level[i] == 0) {
|
|
break;
|
|
}
|
|
|
|
pos += path->level[i] + 1;
|
|
|
|
file->name.data[pos] = '\0';
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
|
|
"temp file: \"%s\"", file->name.data);
|
|
|
|
if (ngx_create_dir(file->name.data, 0700) == NGX_FILE_ERROR) {
|
|
err = ngx_errno;
|
|
if (err != NGX_EEXIST) {
|
|
ngx_log_error(NGX_LOG_CRIT, file->log, err,
|
|
ngx_create_dir_n " \"%s\" failed",
|
|
file->name.data);
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
file->name.data[pos] = '/';
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_err_t
|
|
ngx_create_full_path(u_char *dir, ngx_uint_t access)
|
|
{
|
|
u_char *p, ch;
|
|
ngx_err_t err;
|
|
|
|
err = 0;
|
|
|
|
#if (NGX_WIN32)
|
|
p = dir + 3;
|
|
#else
|
|
p = dir + 1;
|
|
#endif
|
|
|
|
for ( /* void */ ; *p; p++) {
|
|
ch = *p;
|
|
|
|
if (ch != '/') {
|
|
continue;
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
if (ngx_create_dir(dir, access) == NGX_FILE_ERROR) {
|
|
err = ngx_errno;
|
|
|
|
switch (err) {
|
|
case NGX_EEXIST:
|
|
err = 0;
|
|
case NGX_EACCES:
|
|
break;
|
|
|
|
default:
|
|
return err;
|
|
}
|
|
}
|
|
|
|
*p = '/';
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
ngx_atomic_uint_t
|
|
ngx_next_temp_number(ngx_uint_t collision)
|
|
{
|
|
ngx_atomic_uint_t n, add;
|
|
|
|
add = collision ? ngx_random_number : 1;
|
|
|
|
n = ngx_atomic_fetch_add(ngx_temp_number, add);
|
|
|
|
return n + add;
|
|
}
|
|
|
|
|
|
char *
|
|
ngx_conf_set_path_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|
{
|
|
char *p = conf;
|
|
|
|
ssize_t level;
|
|
ngx_str_t *value;
|
|
ngx_uint_t i, n;
|
|
ngx_path_t *path, **slot;
|
|
|
|
slot = (ngx_path_t **) (p + cmd->offset);
|
|
|
|
if (*slot) {
|
|
return "is duplicate";
|
|
}
|
|
|
|
path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
|
|
if (path == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
value = cf->args->elts;
|
|
|
|
path->name = value[1];
|
|
|
|
if (path->name.data[path->name.len - 1] == '/') {
|
|
path->name.len--;
|
|
}
|
|
|
|
if (ngx_conf_full_name(cf->cycle, &path->name, 0) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
path->conf_file = cf->conf_file->file.name.data;
|
|
path->line = cf->conf_file->line;
|
|
|
|
for (i = 0, n = 2; n < cf->args->nelts; i++, n++) {
|
|
level = ngx_atoi(value[n].data, value[n].len);
|
|
if (level == NGX_ERROR || level == 0) {
|
|
return "invalid value";
|
|
}
|
|
|
|
path->level[i] = level;
|
|
path->len += level + 1;
|
|
}
|
|
|
|
if (path->len > 10 + i) {
|
|
return "invalid value";
|
|
}
|
|
|
|
*slot = path;
|
|
|
|
if (ngx_add_path(cf, slot) == NGX_ERROR) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
|
|
char *
|
|
ngx_conf_merge_path_value(ngx_conf_t *cf, ngx_path_t **path, ngx_path_t *prev,
|
|
ngx_path_init_t *init)
|
|
{
|
|
ngx_uint_t i;
|
|
|
|
if (*path) {
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
if (prev) {
|
|
*path = prev;
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
*path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
|
|
if (*path == NULL) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
(*path)->name = init->name;
|
|
|
|
if (ngx_conf_full_name(cf->cycle, &(*path)->name, 0) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < NGX_MAX_PATH_LEVEL; i++) {
|
|
(*path)->level[i] = init->level[i];
|
|
(*path)->len += init->level[i] + (init->level[i] ? 1 : 0);
|
|
}
|
|
|
|
if (ngx_add_path(cf, path) != NGX_OK) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
|
|
char *
|
|
ngx_conf_set_access_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|
{
|
|
char *confp = conf;
|
|
|
|
u_char *p;
|
|
ngx_str_t *value;
|
|
ngx_uint_t i, right, shift, *access, user;
|
|
|
|
access = (ngx_uint_t *) (confp + cmd->offset);
|
|
|
|
if (*access != NGX_CONF_UNSET_UINT) {
|
|
return "is duplicate";
|
|
}
|
|
|
|
value = cf->args->elts;
|
|
|
|
*access = 0;
|
|
user = 0600;
|
|
|
|
for (i = 1; i < cf->args->nelts; i++) {
|
|
|
|
p = value[i].data;
|
|
|
|
if (ngx_strncmp(p, "user:", sizeof("user:") - 1) == 0) {
|
|
shift = 6;
|
|
p += sizeof("user:") - 1;
|
|
user = 0;
|
|
|
|
} else if (ngx_strncmp(p, "group:", sizeof("group:") - 1) == 0) {
|
|
shift = 3;
|
|
p += sizeof("group:") - 1;
|
|
|
|
} else if (ngx_strncmp(p, "all:", sizeof("all:") - 1) == 0) {
|
|
shift = 0;
|
|
p += sizeof("all:") - 1;
|
|
|
|
} else {
|
|
goto invalid;
|
|
}
|
|
|
|
if (ngx_strcmp(p, "rw") == 0) {
|
|
right = 6;
|
|
|
|
} else if (ngx_strcmp(p, "r") == 0) {
|
|
right = 4;
|
|
|
|
} else {
|
|
goto invalid;
|
|
}
|
|
|
|
*access |= right << shift;
|
|
}
|
|
|
|
*access |= user;
|
|
|
|
return NGX_CONF_OK;
|
|
|
|
invalid:
|
|
|
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\"", &value[i]);
|
|
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_add_path(ngx_conf_t *cf, ngx_path_t **slot)
|
|
{
|
|
ngx_uint_t i, n;
|
|
ngx_path_t *path, **p;
|
|
|
|
path = *slot;
|
|
|
|
p = cf->cycle->paths.elts;
|
|
for (i = 0; i < cf->cycle->paths.nelts; i++) {
|
|
if (p[i]->name.len == path->name.len
|
|
&& ngx_strcmp(p[i]->name.data, path->name.data) == 0)
|
|
{
|
|
if (p[i]->data != path->data) {
|
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
|
"the same path name \"%V\" "
|
|
"used in %s:%ui and",
|
|
&p[i]->name, p[i]->conf_file, p[i]->line);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
for (n = 0; n < NGX_MAX_PATH_LEVEL; n++) {
|
|
if (p[i]->level[n] != path->level[n]) {
|
|
if (path->conf_file == NULL) {
|
|
if (p[i]->conf_file == NULL) {
|
|
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
|
|
"the default path name \"%V\" has "
|
|
"the same name as another default path, "
|
|
"but the different levels, you need to "
|
|
"redefine one of them in http section",
|
|
&p[i]->name);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
|
|
"the path name \"%V\" in %s:%ui has "
|
|
"the same name as default path, but "
|
|
"the different levels, you need to "
|
|
"define default path in http section",
|
|
&p[i]->name, p[i]->conf_file, p[i]->line);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
|
"the same path name \"%V\" in %s:%ui "
|
|
"has the different levels than",
|
|
&p[i]->name, p[i]->conf_file, p[i]->line);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (p[i]->level[n] == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
*slot = p[i];
|
|
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
p = ngx_array_push(&cf->cycle->paths);
|
|
if (p == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*p = path;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_create_paths(ngx_cycle_t *cycle, ngx_uid_t user)
|
|
{
|
|
ngx_err_t err;
|
|
ngx_uint_t i;
|
|
ngx_path_t **path;
|
|
|
|
path = cycle->paths.elts;
|
|
for (i = 0; i < cycle->paths.nelts; i++) {
|
|
|
|
if (ngx_create_dir(path[i]->name.data, 0700) == NGX_FILE_ERROR) {
|
|
err = ngx_errno;
|
|
if (err != NGX_EEXIST) {
|
|
ngx_log_error(NGX_LOG_EMERG, cycle->log, err,
|
|
ngx_create_dir_n " \"%s\" failed",
|
|
path[i]->name.data);
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
if (user == (ngx_uid_t) NGX_CONF_UNSET_UINT) {
|
|
continue;
|
|
}
|
|
|
|
#if !(NGX_WIN32)
|
|
{
|
|
ngx_file_info_t fi;
|
|
|
|
if (ngx_file_info(path[i]->name.data, &fi) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
|
ngx_file_info_n " \"%s\" failed", path[i]->name.data);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (fi.st_uid != user) {
|
|
if (chown((const char *) path[i]->name.data, user, -1) == -1) {
|
|
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
|
"chown(\"%s\", %d) failed",
|
|
path[i]->name.data, user);
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
if ((fi.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR))
|
|
!= (S_IRUSR|S_IWUSR|S_IXUSR))
|
|
{
|
|
fi.st_mode |= (S_IRUSR|S_IWUSR|S_IXUSR);
|
|
|
|
if (chmod((const char *) path[i]->name.data, fi.st_mode) == -1) {
|
|
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
|
|
"chmod() \"%s\" failed", path[i]->name.data);
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ext_rename_file(ngx_str_t *src, ngx_str_t *to, ngx_ext_rename_file_t *ext)
|
|
{
|
|
u_char *name;
|
|
ngx_err_t err;
|
|
ngx_copy_file_t cf;
|
|
|
|
#if !(NGX_WIN32)
|
|
|
|
if (ext->access) {
|
|
if (ngx_change_file_access(src->data, ext->access) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
|
|
ngx_change_file_access_n " \"%s\" failed", src->data);
|
|
err = 0;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
if (ext->time != -1) {
|
|
if (ngx_set_file_time(src->data, ext->fd, ext->time) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
|
|
ngx_set_file_time_n " \"%s\" failed", src->data);
|
|
err = 0;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
if (ngx_rename_file(src->data, to->data) != NGX_FILE_ERROR) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
err = ngx_errno;
|
|
|
|
if (err == NGX_ENOPATH) {
|
|
|
|
if (!ext->create_path) {
|
|
goto failed;
|
|
}
|
|
|
|
err = ngx_create_full_path(to->data, ngx_dir_access(ext->path_access));
|
|
|
|
if (err) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, err,
|
|
ngx_create_dir_n " \"%s\" failed", to->data);
|
|
err = 0;
|
|
goto failed;
|
|
}
|
|
|
|
if (ngx_rename_file(src->data, to->data) != NGX_FILE_ERROR) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
err = ngx_errno;
|
|
}
|
|
|
|
#if (NGX_WIN32)
|
|
|
|
if (err == NGX_EEXIST || err == NGX_EEXIST_FILE) {
|
|
err = ngx_win32_rename_file(src, to, ext->log);
|
|
|
|
if (err == 0) {
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
if (err == NGX_EXDEV) {
|
|
|
|
cf.size = -1;
|
|
cf.buf_size = 0;
|
|
cf.access = ext->access;
|
|
cf.time = ext->time;
|
|
cf.log = ext->log;
|
|
|
|
name = ngx_alloc(to->len + 1 + 10 + 1, ext->log);
|
|
if (name == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
(void) ngx_sprintf(name, "%*s.%010uD%Z", to->len, to->data,
|
|
(uint32_t) ngx_next_temp_number(0));
|
|
|
|
if (ngx_copy_file(src->data, name, &cf) == NGX_OK) {
|
|
|
|
if (ngx_rename_file(name, to->data) != NGX_FILE_ERROR) {
|
|
ngx_free(name);
|
|
|
|
if (ngx_delete_file(src->data) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
|
|
ngx_delete_file_n " \"%s\" failed",
|
|
src->data);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
|
|
ngx_rename_file_n " \"%s\" to \"%s\" failed",
|
|
name, to->data);
|
|
|
|
if (ngx_delete_file(name) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
|
|
ngx_delete_file_n " \"%s\" failed", name);
|
|
|
|
}
|
|
}
|
|
|
|
ngx_free(name);
|
|
|
|
err = 0;
|
|
}
|
|
|
|
failed:
|
|
|
|
if (ext->delete_file) {
|
|
if (ngx_delete_file(src->data) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, ngx_errno,
|
|
ngx_delete_file_n " \"%s\" failed", src->data);
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
ngx_log_error(NGX_LOG_CRIT, ext->log, err,
|
|
ngx_rename_file_n " \"%s\" to \"%s\" failed",
|
|
src->data, to->data);
|
|
}
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_copy_file(u_char *from, u_char *to, ngx_copy_file_t *cf)
|
|
{
|
|
char *buf;
|
|
off_t size;
|
|
time_t time;
|
|
size_t len;
|
|
ssize_t n;
|
|
ngx_fd_t fd, nfd;
|
|
ngx_int_t rc;
|
|
ngx_uint_t access;
|
|
ngx_file_info_t fi;
|
|
|
|
rc = NGX_ERROR;
|
|
buf = NULL;
|
|
nfd = NGX_INVALID_FILE;
|
|
|
|
fd = ngx_open_file(from, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
|
|
|
|
if (fd == NGX_INVALID_FILE) {
|
|
ngx_log_error(NGX_LOG_CRIT, cf->log, ngx_errno,
|
|
ngx_open_file_n " \"%s\" failed", from);
|
|
goto failed;
|
|
}
|
|
|
|
if (cf->size != -1 && cf->access != 0 && cf->time != -1) {
|
|
size = cf->size;
|
|
access = cf->access;
|
|
time = cf->time;
|
|
|
|
} else {
|
|
if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
|
ngx_fd_info_n " \"%s\" failed", from);
|
|
|
|
goto failed;
|
|
}
|
|
|
|
size = (cf->size != -1) ? cf->size : ngx_file_size(&fi);
|
|
access = cf->access ? cf->access : ngx_file_access(&fi);
|
|
time = (cf->time != -1) ? cf->time : ngx_file_mtime(&fi);
|
|
}
|
|
|
|
len = cf->buf_size ? cf->buf_size : 65536;
|
|
|
|
if ((off_t) len > size) {
|
|
len = (size_t) size;
|
|
}
|
|
|
|
buf = ngx_alloc(len, cf->log);
|
|
if (buf == NULL) {
|
|
goto failed;
|
|
}
|
|
|
|
nfd = ngx_open_file(to, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE, access);
|
|
|
|
if (nfd == NGX_INVALID_FILE) {
|
|
ngx_log_error(NGX_LOG_CRIT, cf->log, ngx_errno,
|
|
ngx_open_file_n " \"%s\" failed", to);
|
|
goto failed;
|
|
}
|
|
|
|
while (size > 0) {
|
|
|
|
if ((off_t) len > size) {
|
|
len = (size_t) size;
|
|
}
|
|
|
|
n = ngx_read_fd(fd, buf, len);
|
|
|
|
if (n == -1) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
|
ngx_read_fd_n " \"%s\" failed", from);
|
|
goto failed;
|
|
}
|
|
|
|
if ((size_t) n != len) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
|
|
ngx_read_fd_n " has read only %z of %O from %s",
|
|
n, size, from);
|
|
goto failed;
|
|
}
|
|
|
|
n = ngx_write_fd(nfd, buf, len);
|
|
|
|
if (n == -1) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
|
ngx_write_fd_n " \"%s\" failed", to);
|
|
goto failed;
|
|
}
|
|
|
|
if ((size_t) n != len) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
|
|
ngx_write_fd_n " has written only %z of %O to %s",
|
|
n, size, to);
|
|
goto failed;
|
|
}
|
|
|
|
size -= n;
|
|
}
|
|
|
|
if (ngx_set_file_time(to, nfd, time) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
|
ngx_set_file_time_n " \"%s\" failed", to);
|
|
goto failed;
|
|
}
|
|
|
|
rc = NGX_OK;
|
|
|
|
failed:
|
|
|
|
if (nfd != NGX_INVALID_FILE) {
|
|
if (ngx_close_file(nfd) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
|
ngx_close_file_n " \"%s\" failed", to);
|
|
}
|
|
}
|
|
|
|
if (fd != NGX_INVALID_FILE) {
|
|
if (ngx_close_file(fd) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
|
|
ngx_close_file_n " \"%s\" failed", from);
|
|
}
|
|
}
|
|
|
|
if (buf) {
|
|
ngx_free(buf);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* ctx->init_handler() - see ctx->alloc
|
|
* ctx->file_handler() - file handler
|
|
* ctx->pre_tree_handler() - handler is called before entering directory
|
|
* ctx->post_tree_handler() - handler is called after leaving directory
|
|
* ctx->spec_handler() - special (socket, FIFO, etc.) file handler
|
|
*
|
|
* ctx->data - some data structure, it may be the same on all levels, or
|
|
* reallocated if ctx->alloc is nonzero
|
|
*
|
|
* ctx->alloc - a size of data structure that is allocated at every level
|
|
* and is initialized by ctx->init_handler()
|
|
*
|
|
* ctx->log - a log
|
|
*
|
|
* on fatal (memory) error handler must return NGX_ABORT to stop walking tree
|
|
*/
|
|
|
|
ngx_int_t
|
|
ngx_walk_tree(ngx_tree_ctx_t *ctx, ngx_str_t *tree)
|
|
{
|
|
void *data, *prev;
|
|
u_char *p, *name;
|
|
size_t len;
|
|
ngx_int_t rc;
|
|
ngx_err_t err;
|
|
ngx_str_t file, buf;
|
|
ngx_dir_t dir;
|
|
|
|
ngx_str_null(&buf);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"walk tree \"%V\"", tree);
|
|
|
|
if (ngx_open_dir(tree, &dir) == NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
|
|
ngx_open_dir_n " \"%s\" failed", tree->data);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
prev = ctx->data;
|
|
|
|
if (ctx->alloc) {
|
|
data = ngx_alloc(ctx->alloc, ctx->log);
|
|
if (data == NULL) {
|
|
goto failed;
|
|
}
|
|
|
|
if (ctx->init_handler(data, prev) == NGX_ABORT) {
|
|
goto failed;
|
|
}
|
|
|
|
ctx->data = data;
|
|
|
|
} else {
|
|
data = NULL;
|
|
}
|
|
|
|
for ( ;; ) {
|
|
|
|
ngx_set_errno(0);
|
|
|
|
if (ngx_read_dir(&dir) == NGX_ERROR) {
|
|
err = ngx_errno;
|
|
|
|
if (err == NGX_ENOMOREFILES) {
|
|
rc = NGX_OK;
|
|
|
|
} else {
|
|
ngx_log_error(NGX_LOG_CRIT, ctx->log, err,
|
|
ngx_read_dir_n " \"%s\" failed", tree->data);
|
|
rc = NGX_ERROR;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
len = ngx_de_namelen(&dir);
|
|
name = ngx_de_name(&dir);
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"tree name %uz:\"%s\"", len, name);
|
|
|
|
if (len == 1 && name[0] == '.') {
|
|
continue;
|
|
}
|
|
|
|
if (len == 2 && name[0] == '.' && name[1] == '.') {
|
|
continue;
|
|
}
|
|
|
|
file.len = tree->len + 1 + len;
|
|
|
|
if (file.len + NGX_DIR_MASK_LEN > buf.len) {
|
|
|
|
if (buf.len) {
|
|
ngx_free(buf.data);
|
|
}
|
|
|
|
buf.len = tree->len + 1 + len + NGX_DIR_MASK_LEN;
|
|
|
|
buf.data = ngx_alloc(buf.len + 1, ctx->log);
|
|
if (buf.data == NULL) {
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
p = ngx_cpymem(buf.data, tree->data, tree->len);
|
|
*p++ = '/';
|
|
ngx_memcpy(p, name, len + 1);
|
|
|
|
file.data = buf.data;
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"tree path \"%s\"", file.data);
|
|
|
|
if (!dir.valid_info) {
|
|
if (ngx_de_info(file.data, &dir) == NGX_FILE_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
|
|
ngx_de_info_n " \"%s\" failed", file.data);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ngx_de_is_file(&dir)) {
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"tree file \"%s\"", file.data);
|
|
|
|
ctx->size = ngx_de_size(&dir);
|
|
ctx->fs_size = ngx_de_fs_size(&dir);
|
|
ctx->access = ngx_de_access(&dir);
|
|
ctx->mtime = ngx_de_mtime(&dir);
|
|
|
|
if (ctx->file_handler(ctx, &file) == NGX_ABORT) {
|
|
goto failed;
|
|
}
|
|
|
|
} else if (ngx_de_is_dir(&dir)) {
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"tree enter dir \"%s\"", file.data);
|
|
|
|
ctx->access = ngx_de_access(&dir);
|
|
ctx->mtime = ngx_de_mtime(&dir);
|
|
|
|
rc = ctx->pre_tree_handler(ctx, &file);
|
|
|
|
if (rc == NGX_ABORT) {
|
|
goto failed;
|
|
}
|
|
|
|
if (rc == NGX_DECLINED) {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"tree skip dir \"%s\"", file.data);
|
|
continue;
|
|
}
|
|
|
|
if (ngx_walk_tree(ctx, &file) == NGX_ABORT) {
|
|
goto failed;
|
|
}
|
|
|
|
ctx->access = ngx_de_access(&dir);
|
|
ctx->mtime = ngx_de_mtime(&dir);
|
|
|
|
if (ctx->post_tree_handler(ctx, &file) == NGX_ABORT) {
|
|
goto failed;
|
|
}
|
|
|
|
} else {
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
|
|
"tree special \"%s\"", file.data);
|
|
|
|
if (ctx->spec_handler(ctx, &file) == NGX_ABORT) {
|
|
goto failed;
|
|
}
|
|
}
|
|
}
|
|
|
|
failed:
|
|
|
|
rc = NGX_ABORT;
|
|
|
|
done:
|
|
|
|
if (buf.len) {
|
|
ngx_free(buf.data);
|
|
}
|
|
|
|
if (data) {
|
|
ngx_free(data);
|
|
ctx->data = prev;
|
|
}
|
|
|
|
if (ngx_close_dir(&dir) == NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
|
|
ngx_close_dir_n " \"%s\" failed", tree->data);
|
|
}
|
|
|
|
return rc;
|
|
}
|