diff --git a/include/libnfs-private.h b/include/libnfs-private.h index 54a0c67..7c3fbc3 100644 --- a/include/libnfs-private.h +++ b/include/libnfs-private.h @@ -534,6 +534,9 @@ int nfs4_truncate_async(struct nfs_context *nfs, const char *path, uint64_t length, nfs_cb cb, void *private_data); int nfs4_unlink_async(struct nfs_context *nfs, const char *path, nfs_cb cb, void *private_data); +int nfs4_utimes_async_internal(struct nfs_context *nfs, const char *path, + int no_follow, struct timeval *times, + nfs_cb cb, void *private_data); int nfs4_write_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t count, const void *buf, nfs_cb cb, void *private_data); diff --git a/lib/libnfs-sync.c b/lib/libnfs-sync.c index aa1e872..c43fa0d 100644 --- a/lib/libnfs-sync.c +++ b/lib/libnfs-sync.c @@ -1403,7 +1403,8 @@ nfs_utimes(struct nfs_context *nfs, const char *path, struct timeval *times) cb_data.is_finished = 0; if (nfs_utimes_async(nfs, path, times, utimes_cb, &cb_data) != 0) { - nfs_set_error(nfs, "nfs_utimes_async failed"); + nfs_set_error(nfs, "nfs_utimes_async failed. %s", + nfs_get_error(nfs)); return -1; } @@ -1420,7 +1421,8 @@ nfs_lutimes(struct nfs_context *nfs, const char *path, struct timeval *times) cb_data.is_finished = 0; if (nfs_lutimes_async(nfs, path, times, utimes_cb, &cb_data) != 0) { - nfs_set_error(nfs, "nfs_lutimes_async failed"); + nfs_set_error(nfs, "nfs_lutimes_async failed. %s", + nfs_get_error(nfs)); return -1; } diff --git a/lib/libnfs.c b/lib/libnfs.c index 3a65197..f452d0d 100755 --- a/lib/libnfs.c +++ b/lib/libnfs.c @@ -1504,9 +1504,12 @@ nfs_utimes_async(struct nfs_context *nfs, const char *path, case NFS_V3: return nfs3_utimes_async_internal(nfs, path, 0, times, cb, private_data); + case NFS_V4: + return nfs4_utimes_async_internal(nfs, path, 0, times, + cb, private_data); default: - nfs_set_error(nfs, "%s does not support NFSv4", - __FUNCTION__); + nfs_set_error(nfs, "%s does not support NFSv%d", + __FUNCTION__, nfs->version); return -1; } } @@ -1519,9 +1522,12 @@ nfs_lutimes_async(struct nfs_context *nfs, const char *path, case NFS_V3: return nfs3_utimes_async_internal(nfs, path, 1, times, cb, private_data); + case NFS_V4: + return nfs4_utimes_async_internal(nfs, path, 1, times, + cb, private_data); default: - nfs_set_error(nfs, "%s does not support NFSv4", - __FUNCTION__); + nfs_set_error(nfs, "%s does not support NFSv%d", + __FUNCTION__, nfs->version); return -1; } } diff --git a/lib/nfs_v4.c b/lib/nfs_v4.c index 3595167..311be4a 100644 --- a/lib/nfs_v4.c +++ b/lib/nfs_v4.c @@ -749,6 +749,29 @@ nfs4_op_chown(struct nfs_context *nfs, nfs_argop4 *op, struct nfsfh *fh, return 1; } +static int +nfs4_op_utimes(struct nfs_context *nfs, nfs_argop4 *op, struct nfsfh *fh, + void *sabuf, int len) +{ + SETATTR4args *saargs; + static uint32_t mask[2] = {0, + 1 << (FATTR4_TIME_ACCESS_SET - 32) | + 1 << (FATTR4_TIME_MODIFY_SET - 32)}; + + op[0].argop = OP_SETATTR; + saargs = &op[0].nfs_argop4_u.opsetattr; + saargs->stateid.seqid = fh->stateid.seqid; + memcpy(saargs->stateid.other, fh->stateid.other, 12); + + saargs->obj_attributes.attrmask.bitmap4_len = 2; + saargs->obj_attributes.attrmask.bitmap4_val = mask; + + saargs->obj_attributes.attr_vals.attrlist4_len = len; + saargs->obj_attributes.attr_vals.attrlist4_val = sabuf; + + return 1; +} + static int nfs4_op_readdir(struct nfs_context *nfs, nfs_argop4 *op, uint64_t cookie) { @@ -4321,3 +4344,91 @@ nfs4_access2_async(struct nfs_context *nfs, const char *path, nfs_cb cb, return nfs4_access_internal(nfs, path, R_OK|W_OK|X_OK, 1, cb, private_data); } + +static void +nfs4_utimes_open_cb(struct rpc_context *rpc, int status, void *command_data, + void *private_data) +{ + struct nfs4_cb_data *data = private_data; + struct nfs_context *nfs = data->nfs; + struct nfsfh *fh = data->filler.blob0.val; + COMPOUND4res *res = command_data; + COMPOUND4args args; + nfs_argop4 op[4]; + int i; + + if (check_nfs4_error(nfs, status, data, res, "OPEN")) { + return; + } + + i = nfs4_op_putfh(nfs, &op[0], fh); + i += nfs4_op_utimes(nfs, &op[i], fh, data->filler.blob3.val, + data->filler.blob3.len); + i += nfs4_op_close(nfs, &op[i], fh); + + memset(&args, 0, sizeof(args)); + args.argarray.argarray_len = i; + args.argarray.argarray_val = op; + + if (rpc_nfs4_compound_async(nfs->rpc, nfs4_close_cb, &args, + data) != 0) { + /* Not much we can do but leak one fd on the server :( */ + data->cb(-ENOMEM, nfs, nfs_get_error(nfs), data->private_data); + free_nfs4_cb_data(data); + return; + } +} + +int +nfs4_utimes_async_internal(struct nfs_context *nfs, const char *path, + int no_follow, struct timeval *times, + nfs_cb cb, void *private_data) +{ + struct nfs4_cb_data *data; + char *buf; + uint32_t u32; + uint64_t u64; + + data = init_cb_data_split_path(nfs, path); + if (data == NULL) { + return -1; + } + + data->cb = cb; + data->private_data = private_data; + data->open_cb = nfs4_utimes_open_cb; + + if (no_follow) { + data->flags |= LOOKUP_FLAG_NO_FOLLOW; + } + + data->filler.blob3.len = 2 * (4 + 8 + 4); + buf = data->filler.blob3.val = malloc(data->filler.blob3.len); + if (data->filler.blob3.val == NULL) { + nfs_set_error(nfs, "Out of memory"); + return -1; + } + data->filler.blob3.free = free; + + /* atime */ + u32 = htonl(SET_TO_CLIENT_TIME4); + memcpy(buf, &u32, sizeof(uint32_t)); + u64 = nfs_hton64(times[0].tv_sec); + memcpy(buf + 4, &u64, sizeof(uint64_t)); + u32 = htonl(times[0].tv_usec * 1000); + memcpy(buf + 12, &u32, sizeof(uint32_t)); + buf += 16; + /* mtime */ + u32 = htonl(SET_TO_CLIENT_TIME4); + memcpy(buf, &u32, sizeof(uint32_t)); + u64 = nfs_hton64(times[1].tv_sec); + memcpy(buf + 4, &u64, sizeof(uint64_t)); + u32 = htonl(times[1].tv_usec * 1000); + memcpy(buf + 12, &u32, sizeof(uint32_t)); + + if (nfs4_open_async_internal(nfs, data, O_WRONLY, 0) < 0) { + return -1; + } + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 9e7c223..774dc00 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,7 +9,7 @@ noinst_PROGRAMS = prog_access prog_access2 prog_chmod prog_chown prog_create \ prog_lchown prog_link prog_lseek prog_lstat prog_mkdir \ prog_mknod prog_mount prog_open_read prog_open_write \ prog_rename prog_rmdir prog_stat prog_statvfs prog_symlink \ - prog_timeout prog_truncate prog_unlink + prog_timeout prog_truncate prog_unlink prog_utimes EXTRA_PROGRAMS = ld_timeout CLEANFILES = ld_timeout.o ld_timeout.so diff --git a/tests/prog_utimes.c b/tests/prog_utimes.c new file mode 100644 index 0000000..ded9c7e --- /dev/null +++ b/tests/prog_utimes.c @@ -0,0 +1,97 @@ +/* -*- mode:c; tab-width:8; c-basic-offset:8; indent-tabs-mode:nil; -*- */ +/* + Copyright (C) by Ronnie Sahlberg 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#define _FILE_OFFSET_BITS 64 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnfs.h" + +void usage(void) +{ + fprintf(stderr, "Usage: prog_utimes \n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + struct nfs_context *nfs = NULL; + struct nfs_url *url = NULL; + int ret = 0; + int atime, mtime; + struct timeval times[2]; + + if (argc != 6) { + usage(); + } + + atime = strtol(argv[4], NULL, 10); + mtime = strtol(argv[5], NULL, 10); + + nfs = nfs_init_context(); + if (nfs == NULL) { + printf("failed to init context\n"); + exit(1); + } + + url = nfs_parse_url_full(nfs, argv[1]); + if (url == NULL) { + fprintf(stderr, "%s\n", nfs_get_error(nfs)); + exit(1); + } + + if (nfs_mount(nfs, url->server, url->path) != 0) { + fprintf(stderr, "Failed to mount nfs share : %s\n", + nfs_get_error(nfs)); + ret = 1; + goto finished; + } + + if (nfs_chdir(nfs, argv[2]) != 0) { + fprintf(stderr, "Failed to chdir to \"%s\" : %s\n", + argv[2], nfs_get_error(nfs)); + ret = 1; + goto finished; + } + + memset(×[0], 0, 2 * sizeof(struct timeval)); + times[0].tv_sec = atime; + times[1].tv_sec = mtime; + + if (nfs_utimes(nfs, argv[3], ×[0])) { + fprintf(stderr, "Failed to access(): %s\n", + nfs_get_error(nfs)); + ret = 1; + goto finished; + } + +finished: + nfs_destroy_url(url); + nfs_destroy_context(nfs); + + return ret; +} diff --git a/tests/test_0450_utimes.sh b/tests/test_0450_utimes.sh new file mode 100755 index 0000000..13e8bf6 --- /dev/null +++ b/tests/test_0450_utimes.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. ./functions.sh + +echo "NFSv${VERS} Basic nfs_utimes() tests." + +start_share + +dd if=/dev/zero of=testdata/testfile count=1 bs=32768 2>/dev/null +chmod 644 "${TESTDIR}/testfile" + +echo -n "test nfs_utimes() ... " +./prog_utimes "${TESTURL}/?version=${VERS}" "." /testfile 12345 23456 || failure +success + +echo -n "test nfs_stat64() ... " +./prog_stat "${TESTURL}/?version=${VERS}" "." /testfile > "${TESTDIR}/output" || failure +success + +echo -n "test nfs_atime ... " +grep "nfs_atime:12345" "${TESTDIR}/output" >/dev/null || failure +success + +echo -n "test nfs_mtime ... " +grep "nfs_mtime:23456" "${TESTDIR}/output" >/dev/null || failure +success + + +stop_share + +exit 0