nfs_open: add support for O_APPEND
[deb_libnfs.git] / lib / libnfs.c
index 9053ab1884992ff7b01287d8c5ac655c90e4fc6e..59b30fdfffd3abc6b86c2ac3b5a38e618e6843cc 100644 (file)
@@ -84,6 +84,7 @@ struct nfsdir {
 struct nfsfh {
        struct nfs_fh3 fh;
        int is_sync;
+       int is_append;
        uint64_t offset;
 };
 
@@ -133,7 +134,7 @@ struct nfs_cb_data {
        int cancel;
        int oom;
        int num_calls;
-       uint64_t start_offset, max_offset;
+       uint64_t start_offset, max_offset, count;
        char *buffer;
        size_t request_size;
        char *usrbuf;
@@ -143,6 +144,7 @@ struct nfs_mcb_data {
        struct nfs_cb_data *data;
        uint64_t offset;
        uint64_t count;
+       int update_pos;
 };
 
 static int nfs_lookup_path_async_internal(struct nfs_context *nfs, struct nfs_cb_data *data, struct nfs_fh3 *fh);
@@ -1306,6 +1308,9 @@ static void nfs_open_trunc_cb(struct rpc_context *rpc, int status, void *command
        if (data->continue_int & O_SYNC) {
                nfsfh->is_sync = 1;
        }
+       if (data->continue_int & O_APPEND) {
+               nfsfh->is_append = 1;
+       }
 
        /* steal the filehandle */
        nfsfh->fh = data->fh;
@@ -1402,6 +1407,9 @@ static void nfs_open_cb(struct rpc_context *rpc, int status, void *command_data,
        if (data->continue_int & O_SYNC) {
                nfsfh->is_sync = 1;
        }
+       if (data->continue_int & O_APPEND) {
+               nfsfh->is_append = 1;
+       }
 
        /* steal the filehandle */
        nfsfh->fh = data->fh;
@@ -1515,9 +1523,14 @@ static void nfs_pread_mcb(struct rpc_context *rpc, int status, void *command_dat
                if (res->status != NFS3_OK) {
                        rpc_set_error(nfs->rpc, "NFS: Read failed with %s(%d)", nfsstat3_to_str(res->status), nfsstat3_to_errno(res->status));
                        data->error = 1;
-               } else  {
+               } else {
+                       uint64_t count = res->READ3res_u.resok.count;
+
+                       if (mdata->update_pos)
+                               data->nfsfh->offset += count;
+
                        /* if we have more than one call or we have received a short read we need a reassembly buffer */
-                       if (data->num_calls || (res->READ3res_u.resok.count < mdata->count && !res->READ3res_u.resok.eof)) {
+                       if (data->num_calls || (count < mdata->count && !res->READ3res_u.resok.eof)) {
                                if (data->buffer == NULL) {
                                        data->buffer =  malloc(data->request_size);
                                        if (data->buffer == NULL) {
@@ -1526,14 +1539,14 @@ static void nfs_pread_mcb(struct rpc_context *rpc, int status, void *command_dat
                                        }
                                }
                        }
-                       if (res->READ3res_u.resok.count > 0) {
-                               if (res->READ3res_u.resok.count <= mdata->count) {
+                       if (count > 0) {
+                               if (count <= mdata->count) {
                                        /* copy data into reassembly buffer if we have one */
                                        if (data->buffer != NULL) {
-                                               memcpy(&data->buffer[mdata->offset - data->start_offset], res->READ3res_u.resok.data.data_val, res->READ3res_u.resok.count);
+                                               memcpy(&data->buffer[mdata->offset - data->start_offset], res->READ3res_u.resok.data.data_val, count);
                                        }
-                                       if (data->max_offset < mdata->offset + res->READ3res_u.resok.count) {
-                                               data->max_offset = mdata->offset + res->READ3res_u.resok.count;
+                                       if (data->max_offset < mdata->offset + count) {
+                                               data->max_offset = mdata->offset + count;
                                        }
                                } else {
                                        rpc_set_error(nfs->rpc, "NFS: Read overflow. Server has sent more data than requested!");
@@ -1541,15 +1554,15 @@ static void nfs_pread_mcb(struct rpc_context *rpc, int status, void *command_dat
                                }
                        }
                        /* check if we have received a short read */
-                       if (res->READ3res_u.resok.count < mdata->count && !res->READ3res_u.resok.eof) {
-                               if (res->READ3res_u.resok.count == 0) {
+                       if (count < mdata->count && !res->READ3res_u.resok.eof) {
+                               if (count == 0) {
                                        rpc_set_error(nfs->rpc, "NFS: Read failed. No bytes read and not at EOF!");
                                        data->error = 1;
                                } else {
                                        /* reissue reminder of this read request */
                                        READ3args args;
-                                       mdata->offset += res->READ3res_u.resok.count;
-                                       mdata->count -= res->READ3res_u.resok.count;
+                                       mdata->offset += count;
+                                       mdata->count -= count;
                                        nfs_fill_READ3args(&args, data->nfsfh, mdata->offset, mdata->count);
                                        if (rpc_nfs3_read_async(nfs->rpc, nfs_pread_mcb, &args, mdata) == 0) {
                                                data->num_calls++;
@@ -1585,7 +1598,6 @@ static void nfs_pread_mcb(struct rpc_context *rpc, int status, void *command_dat
                return;
        }
 
-       data->nfsfh->offset = data->max_offset;
        if (data->buffer) {
                data->cb(data->max_offset - data->start_offset, nfs, data->buffer, data->private_data);
        } else {
@@ -1595,7 +1607,7 @@ static void nfs_pread_mcb(struct rpc_context *rpc, int status, void *command_dat
        free_nfs_cb_data(data);
 }
 
-int nfs_pread_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, nfs_cb cb, void *private_data)
+static int nfs_pread_async_internal(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, nfs_cb cb, void *private_data, int update_pos)
 {
        struct nfs_cb_data *data;
 
@@ -1611,8 +1623,6 @@ int nfs_pread_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offse
        data->nfsfh        = nfsfh;
        data->request_size = count;
 
-       nfsfh->offset = offset;
-
        assert(data->num_calls == 0);
 
        /* chop requests into chunks of at most READMAX bytes if necessary.
@@ -1644,6 +1654,7 @@ int nfs_pread_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offse
                mdata->data   = data;
                mdata->offset = offset;
                mdata->count  = readcount;
+               mdata->update_pos = update_pos;
 
                nfs_fill_READ3args(&args, nfsfh, offset, readcount);
 
@@ -1666,12 +1677,17 @@ int nfs_pread_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offse
         return 0;
 }
 
+int nfs_pread_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, nfs_cb cb, void *private_data)
+{
+       return nfs_pread_async_internal(nfs, nfsfh, offset, count, cb, private_data, 0);
+}
+
 /*
  * Async read()
  */
 int nfs_read_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t count, nfs_cb cb, void *private_data)
 {
-       return nfs_pread_async(nfs, nfsfh, nfsfh->offset, count, cb, private_data);
+       return nfs_pread_async_internal(nfs, nfsfh, nfsfh->offset, count, cb, private_data, 1);
 }
 
 
@@ -1686,7 +1702,7 @@ static void nfs_fill_WRITE3args (WRITE3args *args, struct nfsfh *fh, uint64_t of
        args->file = fh->fh;
        args->offset = offset;
        args->count  = count;
-       args->stable = fh->is_sync?FILE_SYNC:UNSTABLE;
+       args->stable = fh->is_sync ? FILE_SYNC : UNSTABLE;
        args->data.data_len = count;
        args->data.data_val = buf;
 }
@@ -1717,15 +1733,21 @@ static void nfs_pwrite_mcb(struct rpc_context *rpc, int status, void *command_da
                        rpc_set_error(nfs->rpc, "NFS: Write failed with %s(%d)", nfsstat3_to_str(res->status), nfsstat3_to_errno(res->status));
                        data->error = 1;
                } else  {
-                       if (res->WRITE3res_u.resok.count < mdata->count) {
-                               if (res->WRITE3res_u.resok.count == 0) {
+                       uint64_t count = res->WRITE3res_u.resok.count;
+
+                       if (mdata->update_pos)
+                               data->nfsfh->offset += count;
+
+                       if (count < mdata->count) {
+                               if (count == 0) {
                                        rpc_set_error(nfs->rpc, "NFS: Write failed. No bytes written!");
                                        data->error = 1;
                                } else {
                                        /* reissue reminder of this write request */
                                        WRITE3args args;
-                                       mdata->offset += res->WRITE3res_u.resok.count;
-                                       mdata->count -= res->WRITE3res_u.resok.count;
+                                       mdata->offset += count;
+                                       mdata->count -= count;
+
                                        nfs_fill_WRITE3args(&args, data->nfsfh, mdata->offset, mdata->count,
                                                                                &data->usrbuf[mdata->offset - data->start_offset]);
                                        if (rpc_nfs3_write_async(nfs->rpc, nfs_pwrite_mcb, &args, mdata) == 0) {
@@ -1737,9 +1759,9 @@ static void nfs_pwrite_mcb(struct rpc_context *rpc, int status, void *command_da
                                        }
                                }
                        }
-                       if (res->WRITE3res_u.resok.count > 0) {
-                               if (data->max_offset < mdata->offset + res->WRITE3res_u.resok.count) {
-                                       data->max_offset = mdata->offset + res->WRITE3res_u.resok.count;
+                       if (count > 0) {
+                               if (data->max_offset < mdata->offset + count) {
+                                       data->max_offset = mdata->offset + count;
                                }
                        }
                }
@@ -1767,14 +1789,13 @@ static void nfs_pwrite_mcb(struct rpc_context *rpc, int status, void *command_da
                return;
        }
 
-       data->nfsfh->offset = data->max_offset;
        data->cb(data->max_offset - data->start_offset, nfs, NULL, data->private_data);
 
        free_nfs_cb_data(data);
 }
 
 
-int nfs_pwrite_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, char *buf, nfs_cb cb, void *private_data)
+static int nfs_pwrite_async_internal(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, char *buf, nfs_cb cb, void *private_data, int update_pos)
 {
        struct nfs_cb_data *data;
 
@@ -1790,8 +1811,6 @@ int nfs_pwrite_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offs
        data->nfsfh        = nfsfh;
        data->usrbuf       = buf;
 
-       nfsfh->offset = offset;
-
        /* hello, clang-analyzer */
        assert(data->num_calls == 0);
 
@@ -1824,6 +1843,7 @@ int nfs_pwrite_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offs
                mdata->data   = data;
                mdata->offset = offset;
                mdata->count  = writecount;
+               mdata->update_pos = update_pos;
 
                nfs_fill_WRITE3args(&args, nfsfh, offset, writecount, &buf[offset - data->start_offset]);
 
@@ -1846,12 +1866,79 @@ int nfs_pwrite_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offs
        return 0;
 }
 
+int nfs_pwrite_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, char *buf, nfs_cb cb, void *private_data)
+{
+       return nfs_pwrite_async_internal(nfs, nfsfh, offset, count, buf, cb, private_data, 0);
+}
+
 /*
  * Async write()
  */
+static void nfs_write_append_cb(struct rpc_context *rpc, int status, void *command_data, void *private_data)
+{
+       struct nfs_cb_data *data = private_data;
+       struct nfs_context *nfs = data->nfs;
+       GETATTR3res *res;
+
+       assert(rpc->magic == RPC_CONTEXT_MAGIC);
+
+       if (status == RPC_STATUS_ERROR) {
+               data->cb(-EFAULT, nfs, command_data, data->private_data);
+               free_nfs_cb_data(data);
+               return;
+       }
+       if (status == RPC_STATUS_CANCEL) {
+               data->cb(-EINTR, nfs, "Command was cancelled", data->private_data);
+               free_nfs_cb_data(data);
+               return;
+       }
+
+       res = command_data;
+       if (res->status != NFS3_OK) {
+               rpc_set_error(nfs->rpc, "NFS: GETATTR failed with %s(%d)", nfsstat3_to_str(res->status), nfsstat3_to_errno(res->status));
+               data->cb(nfsstat3_to_errno(res->status), nfs, rpc_get_error(nfs->rpc), data->private_data);
+               free_nfs_cb_data(data);
+               return;
+       }
+
+       if (nfs_pwrite_async_internal(nfs, data->nfsfh, res->GETATTR3res_u.resok.obj_attributes.size, data->count, data->usrbuf, data->cb, data->private_data, 1) != 0) {
+               data->cb(-ENOMEM, nfs, rpc_get_error(nfs->rpc), data->private_data);
+               free_nfs_cb_data(data);
+               return;
+       }
+       free_nfs_cb_data(data);
+}
+
 int nfs_write_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t count, char *buf, nfs_cb cb, void *private_data)
 {
-       return nfs_pwrite_async(nfs, nfsfh, nfsfh->offset, count, buf, cb, private_data);
+       if (nfsfh->is_append) {
+               struct GETATTR3args args;
+               struct nfs_cb_data *data;
+
+               data = malloc(sizeof(struct nfs_cb_data));
+               if (data == NULL) {
+                       rpc_set_error(nfs->rpc, "out of memory: failed to allocate nfs_cb_data structure");
+                       return -1;
+               }
+               memset(data, 0, sizeof(struct nfs_cb_data));
+               data->nfs           = nfs;
+               data->cb            = cb;
+               data->private_data  = private_data;
+               data->nfsfh         = nfsfh;
+               data->usrbuf        = buf;
+               data->count         = count;
+
+               memset(&args, 0, sizeof(GETATTR3args));
+               args.object = nfsfh->fh;
+
+               if (rpc_nfs3_getattr_async(nfs->rpc, nfs_write_append_cb, &args, data) != 0) {
+                       rpc_set_error(nfs->rpc, "out of memory: failed to send GETATTR");
+                       free_nfs_cb_data(data);
+                       return -1;
+               }
+               return 0;
+       }
+       return nfs_pwrite_async_internal(nfs, nfsfh, nfsfh->offset, count, buf, cb, private_data, 1);
 }
 
 
@@ -3034,7 +3121,7 @@ void nfs_getcwd(struct nfs_context *nfs, const char **cwd)
 struct lseek_cb_data {
        struct nfs_context *nfs;
        struct nfsfh *nfsfh;
-       uint64_t offset;
+       int64_t offset;
        nfs_cb cb;
        void *private_data;
 };
@@ -3044,6 +3131,7 @@ static void nfs_lseek_1_cb(struct rpc_context *rpc, int status, void *command_da
        GETATTR3res *res;
        struct lseek_cb_data *data = private_data;
        struct nfs_context *nfs = data->nfs;
+       uint64_t size = 0;
 
        assert(rpc->magic == RPC_CONTEXT_MAGIC);
 
@@ -3066,24 +3154,41 @@ static void nfs_lseek_1_cb(struct rpc_context *rpc, int status, void *command_da
                return;
        }
 
-       data->nfsfh->offset = data->offset + res->GETATTR3res_u.resok.obj_attributes.size;
-       data->cb(0, nfs, &data->nfsfh->offset, data->private_data);
+       size = res->GETATTR3res_u.resok.obj_attributes.size;
+
+       if (data->offset < 0 &&
+           (uint64_t)(-data->offset) > size) {
+               data->cb(-EINVAL, nfs, &data->nfsfh->offset, data->private_data);
+       } else {
+               data->nfsfh->offset = data->offset + size;
+               data->cb(0, nfs, &data->nfsfh->offset, data->private_data);
+       }
+
        free(data);
 }
 
-int nfs_lseek_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, int whence, nfs_cb cb, void *private_data)
+int nfs_lseek_async(struct nfs_context *nfs, struct nfsfh *nfsfh, int64_t offset, int whence, nfs_cb cb, void *private_data)
 {
        struct lseek_cb_data *data;
        struct GETATTR3args args;
 
        if (whence == SEEK_SET) {
-               nfsfh->offset = offset;
-               cb(0, nfs, &nfsfh->offset, private_data);
+               if (offset < 0) {
+                       cb(-EINVAL, nfs, &nfsfh->offset, private_data);
+               } else {
+                       nfsfh->offset = offset;
+                       cb(0, nfs, &nfsfh->offset, private_data);
+               }
                return 0;
        }
        if (whence == SEEK_CUR) {
-               nfsfh->offset += offset;
-               cb(0, nfs, &nfsfh->offset, private_data);
+               if (offset < 0 &&
+                   nfsfh->offset < (uint64_t)(-offset)) {
+                       cb(-EINVAL, nfs, &nfsfh->offset, private_data);
+               } else {
+                       nfsfh->offset += offset;
+                       cb(0, nfs, &nfsfh->offset, private_data);
+               }
                return 0;
        }