/*- * Copyright (c) 2017 Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /* * This file contains 9P client functions which prepares message to be sent to * the server. Every fileop typically has a function defined here to interact * with the host. */ #include #include #include #include #include #include #include #include #include #include #include #define QEMU_HEADER 7 #define P9FS_MAX_FID_CNT (1024 * 1024 * 1024) #define P9FS_ROOT_FID_NO 2 #define P9FS_MIN_TAG 1 #define P9FS_MAX_TAG 65535 #define WSTAT_SIZE 47 #define WSTAT_EXTENSION_SIZE 14 static MALLOC_DEFINE(M_P9CLNT, "p9_client", "p9fs client structure"); static uma_zone_t p9fs_fid_zone; static uma_zone_t p9fs_req_zone; static uma_zone_t p9fs_buf_zone; SYSCTL_DECL(_vfs_p9fs); int p9_debug_level = 0; SYSCTL_INT(_vfs_p9fs, OID_AUTO, debug_level, CTLFLAG_RW, &p9_debug_level, 0, "p9fs debug logging"); static struct p9_req_t *p9_get_request(struct p9_client *c, int *error); static struct p9_req_t *p9_client_request( struct p9_client *c, int8_t type, int *error, const char *fmt, ...); inline int p9_is_proto_dotl(struct p9_client *clnt) { return (clnt->proto_version == p9_proto_2000L); } inline int p9_is_proto_dotu(struct p9_client *clnt) { return (clnt->proto_version == p9_proto_2000u); } /* Parse mount options into client structure */ static int p9_parse_opts(struct mount *mp, struct p9_client *clnt) { int error, len; char *trans; /* * Default to virtio since thats the only transport we have for now. */ error = vfs_getopt(mp->mnt_optnew, "trans", (void **)&trans, &len); if (error == ENOENT) trans = "virtio"; /* These are defaults for now */ clnt->proto_version = p9_proto_2000L; clnt->msize = 8192; /* Get the default trans callback */ clnt->ops = p9_get_trans_by_name(trans); return (0); } /* Allocate buffer for sending request and getting responses */ static struct p9_buffer * p9_buffer_alloc(int alloc_msize) { struct p9_buffer *fc; fc = uma_zalloc(p9fs_buf_zone, M_WAITOK | M_ZERO); fc->capacity = alloc_msize; fc->offset = 0; fc->size = 0; fc->sdata = (char *)fc + sizeof(struct p9_buffer); return (fc); } /* Free memory used by request and response buffers */ static void p9_buffer_free(struct p9_buffer **buf) { /* Free the sdata buffers first, then the whole structure*/ uma_zfree(p9fs_buf_zone, *buf); *buf = NULL; } /* Free the request */ static void p9_free_req(struct p9_client *clnt, struct p9_req_t *req) { if (req->tc != NULL) { if (req->tc->tag != P9_NOTAG) p9_tag_destroy(clnt, req->tc->tag); p9_buffer_free(&req->tc); } if (req->rc != NULL) p9_buffer_free(&req->rc); uma_zfree(p9fs_req_zone, req); } /* Allocate a request by tag */ static struct p9_req_t * p9_get_request(struct p9_client *clnt, int *error) { struct p9_req_t *req; int alloc_msize; uint16_t tag; alloc_msize = P9FS_MTU; req = uma_zalloc(p9fs_req_zone, M_WAITOK | M_ZERO); req->tc = p9_buffer_alloc(alloc_msize); req->rc = p9_buffer_alloc(alloc_msize); tag = p9_tag_create(clnt); if (tag == P9_NOTAG) { *error = EAGAIN; req->tc->tag = P9_NOTAG; p9_free_req(clnt, req); return (NULL); } req->tc->tag = tag; return (req); } /* Parse header arguments of the response buffer */ static int p9_parse_receive(struct p9_buffer *buf, struct p9_client *clnt) { int8_t type; int16_t tag; int32_t size; int error; buf->offset = 0; /* This value is set by QEMU for the header.*/ if (buf->size == 0) buf->size = QEMU_HEADER; /* This is the initial header. Parse size, type, and tag .*/ error = p9_buf_readf(buf, 0, "dbw", &size, &type, &tag); if (error != 0) goto out; buf->size = size; buf->id = type; buf->tag = tag; P9_DEBUG(TRANS, "%s: size=%d type: %d tag: %d\n", __func__, buf->size, buf->id, buf->tag); out: return (error); } /* Check 9P response for any errors returned and process it */ static int p9_client_check_return(struct p9_client *c, struct p9_req_t *req) { int error; int ecode; char *ename; /* Check what we have in the receive bufer .*/ error = p9_parse_receive(req->rc, c); if (error != 0) goto out; /* * No error, We are done with the preprocessing. Return to the caller * and process the actual data. */ if (req->rc->id != P9PROTO_RERROR && req->rc->id != P9PROTO_RLERROR) return (0); /* * Interpreting the error is done in different ways for Linux and * Unix version. Make sure you interpret it right. */ if (req->rc->id == P9PROTO_RERROR) { error = p9_buf_readf(req->rc, c->proto_version, "s?d", &ename, &ecode); } else if (req->rc->id == P9PROTO_RLERROR) { error = p9_buf_readf(req->rc, c->proto_version, "d", &ecode); } else { goto out; } if (error != 0) goto out; /* if there was an ecode error make this the err now */ error = ecode; /* * Note this is still not completely an error, as lookups for files * not present can hit this and return. Hence it is made a debug print. */ if (error != 0) { if (req->rc->id == P9PROTO_RERROR) { P9_DEBUG(PROTO, "RERROR error %d ename %s\n", error, ename); } else if (req->rc->id == P9PROTO_RLERROR) { P9_DEBUG(PROTO, "RLERROR error %d\n", error); } } if (req->rc->id == P9PROTO_RERROR) { free(ename, M_TEMP); } return (error); out: P9_DEBUG(ERROR, "couldn't parse receive buffer error%d\n", error); return (error); } /* State machine changing helpers */ void p9_client_disconnect(struct p9_client *clnt) { P9_DEBUG(TRANS, "%s: clnt %p\n", __func__, clnt); clnt->trans_status = P9FS_DISCONNECT; } void p9_client_begin_disconnect(struct p9_client *clnt) { P9_DEBUG(TRANS, "%s: clnt %p\n", __func__, clnt); clnt->trans_status = P9FS_BEGIN_DISCONNECT; } static struct p9_req_t * p9_client_prepare_req(struct p9_client *c, int8_t type, int req_size, int *error, const char *fmt, __va_list ap) { struct p9_req_t *req; P9_DEBUG(TRANS, "%s: client %p op %d\n", __func__, c, type); /* * Before we start with the request, check if its possible to finish * this request. We are allowed to submit the request only if there * are no close sessions happening or else there can be race. If the * status is Disconnected, we stop any requests coming in after that. */ if (c->trans_status == P9FS_DISCONNECT) { *error = EIO; return (NULL); } /* Allow only cleanup clunk messages once teardown has started. */ if ((c->trans_status == P9FS_BEGIN_DISCONNECT) && (type != P9PROTO_TCLUNK)) { *error = EIO; return (NULL); } /* Allocate buffer for transferring and receiving data from host */ req = p9_get_request(c, error); if (*error != 0) { P9_DEBUG(ERROR, "%s: request allocation failed.\n", __func__); return (NULL); } /* Marshall the data according to QEMU standards */ *error = p9_buf_prepare(req->tc, type); if (*error != 0) { P9_DEBUG(ERROR, "%s: p9_buf_prepare failed: %d\n", __func__, *error); goto out; } *error = p9_buf_vwritef(req->tc, c->proto_version, fmt, ap); if (*error != 0) { P9_DEBUG(ERROR, "%s: p9_buf_vwrite failed: %d\n", __func__, *error); goto out; } *error = p9_buf_finalize(c, req->tc); if (*error != 0) { P9_DEBUG(ERROR, "%s: p9_buf_finalize failed: %d \n", __func__, *error); goto out; } return (req); out: p9_free_req(c, req); return (NULL); } /* * Issue a request and wait for response. The routine takes care of preparing * the 9P request header to be sent, parsing and checking for error conditions * in the received buffer. It returns the request structure. */ static struct p9_req_t * p9_client_request(struct p9_client *c, int8_t type, int *error, const char *fmt, ...) { va_list ap; struct p9_req_t *req; va_start(ap, fmt); req = p9_client_prepare_req(c, type, c->msize, error, fmt, ap); va_end(ap); /* Issue with allocation of request buffer */ if (*error != 0) return (NULL); /* Call into the transport for submission. */ *error = c->ops->request(c->handle, req); if (*error != 0) { P9_DEBUG(ERROR, "%s: failed: %d\n", __func__, *error); goto out; } /* * Before we return, pre process the header and the rc buffer before * calling into the protocol infra to analyze the data in rc. */ *error = p9_client_check_return(c, req); if (*error != 0) goto out; return (req); out: p9_free_req(c, req); return (NULL); } /* Setup tag contents and structure */ uint16_t p9_tag_create(struct p9_client *clnt) { int tag; tag = alloc_unr(&clnt->tagpool); P9_DEBUG(LPROTO, "%s: clnt %p: tag %d\n", __func__, clnt, tag); /* Alloc_unr returning -1 is an error for no units left */ if (tag == -1) { return (P9_NOTAG); } return (tag); } /* Clean up tag structures */ void p9_tag_destroy(struct p9_client *clnt, uint16_t tag) { P9_DEBUG(LPROTO, "%s: clnt %p: tag %d\n", __func__, clnt, tag); /* Release to the pool */ free_unr(&clnt->tagpool, tag); } /* Allocate a new fid from the fidpool */ struct p9_fid * p9_fid_create(struct p9_client *clnt) { struct p9_fid *fid; fid = uma_zalloc(p9fs_fid_zone, M_WAITOK | M_ZERO); fid->fid = alloc_unr(&clnt->fidpool); P9_DEBUG(LPROTO, "%s: fid %d\n", __func__, fid->fid); /* Alloc_unr returning -1 is an error for no units left */ if (fid->fid == -1) { uma_zfree(p9fs_fid_zone, fid); return (NULL); } fid->mode = -1; fid->uid = -1; fid->clnt = clnt; return (fid); } /* Free the fid by releasing it to fidpool */ void p9_fid_destroy(struct p9_fid *fid) { struct p9_client *clnt; P9_DEBUG(LPROTO, "%s: fid %d\n", __func__, fid->fid); clnt = fid->clnt; /* Release to the pool */ free_unr(&clnt->fidpool, fid->fid); uma_zfree(p9fs_fid_zone, fid); } /* Request the version of 9P protocol */ int p9_client_version(struct p9_client *c) { int error; struct p9_req_t *req; char *version; int msize; error = 0; P9_DEBUG(PROTO, "TVERSION msize %d protocol %d\n", c->msize, c->proto_version); switch (c->proto_version) { case p9_proto_2000L: req = p9_client_request(c, P9PROTO_TVERSION, &error, "ds", c->msize, "9P2000.L"); break; case p9_proto_2000u: req = p9_client_request(c, P9PROTO_TVERSION, &error, "ds", c->msize, "9P2000.u"); break; case p9_proto_legacy: req = p9_client_request(c, P9PROTO_TVERSION, &error, "ds", c->msize, "9P2000"); break; default: return (EINVAL); } /* Always return the relevant error code */ if (error != 0) return (error); error = p9_buf_readf(req->rc, c->proto_version, "ds", &msize, &version); if (error != 0) { P9_DEBUG(ERROR, "%s: version error: %d\n", __func__, error); goto out; } P9_DEBUG(PROTO, "RVERSION msize %d %s\n", msize, version); if (!strncmp(version, "9P2000.L", 8)) c->proto_version = p9_proto_2000L; else if (!strncmp(version, "9P2000.u", 8)) c->proto_version = p9_proto_2000u; else if (!strncmp(version, "9P2000", 6)) c->proto_version = p9_proto_legacy; else { error = ENOMEM; goto out; } /* limit the msize .*/ if (msize < c->msize) c->msize = msize; out: p9_free_req(c, req); return (error); } /* * Initialize zones for different things. This is called from Init module * so that we just have them initalized once. */ void p9_init_zones(void) { /* Create the request and the fid zones */ p9fs_fid_zone = uma_zcreate("p9fs fid zone", sizeof(struct p9_fid), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); /* Create the request and the fid zones */ p9fs_req_zone = uma_zcreate("p9fs req zone", sizeof(struct p9_req_t), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); /* Create the buffer zone */ p9fs_buf_zone = uma_zcreate("p9fs buf zone", sizeof(struct p9_buffer) + P9FS_MTU, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); } void p9_destroy_zones(void) { uma_zdestroy(p9fs_fid_zone); uma_zdestroy(p9fs_req_zone); uma_zdestroy(p9fs_buf_zone); } /* Return the client to the session in the FS to hold it */ struct p9_client * p9_client_create(struct mount *mp, int *error, const char *mount_tag) { struct p9_client *clnt; clnt = malloc(sizeof(struct p9_client), M_P9CLNT, M_WAITOK | M_ZERO); mtx_init(&clnt->clnt_mtx, "p9clnt", NULL, MTX_DEF); /* Parse should have set trans_mod */ *error = p9_parse_opts(mp, clnt); if (*error != 0) goto out; if (clnt->ops == NULL) { *error = EINVAL; P9_DEBUG(ERROR, "%s: no transport\n", __func__); goto out; } /* All the structures from here are protected by the lock clnt_mtx */ init_unrhdr(&clnt->fidpool, P9FS_ROOT_FID_NO, P9FS_MAX_FID_CNT, &clnt->clnt_mtx); init_unrhdr(&clnt->tagpool, P9FS_MIN_TAG, P9FS_MAX_TAG, &clnt->clnt_mtx); P9_DEBUG(TRANS, "%s: clnt %p trans %p msize %d protocol %d\n", __func__, clnt, clnt->ops, clnt->msize, clnt->proto_version); *error = clnt->ops->create(mount_tag, &clnt->handle); if (*error != 0) { P9_DEBUG(ERROR, "%s: transport create failed .%d \n", __func__, *error); goto out; } clnt->trans_status = P9FS_CONNECT; *error = p9_client_version(clnt); if (*error != 0) goto out; P9_DEBUG(TRANS, "%s: client creation succeeded.\n", __func__); return (clnt); out: free(clnt, M_P9CLNT); return (NULL); } /* Destroy the client by destroying associated fidpool and tagpool */ void p9_client_destroy(struct p9_client *clnt) { P9_DEBUG(TRANS, "%s: client %p\n", __func__, clnt); clnt->ops->close(clnt->handle); P9_DEBUG(TRANS, "%s : Destroying fidpool\n", __func__); clear_unrhdr(&clnt->fidpool); P9_DEBUG(TRANS, "%s : Destroying tagpool\n", __func__); clear_unrhdr(&clnt->tagpool); free(clnt, M_P9CLNT); } /* * Attach a user to the filesystem. Create a fid for that user to access * the root of the filesystem. */ struct p9_fid * p9_client_attach(struct p9_client *clnt, struct p9_fid *afid, const char *uname, uid_t n_uname, const char *aname, int *error) { struct p9_req_t *req; struct p9_fid *fid; struct p9_qid qid; P9_DEBUG(PROTO, "TATTACH uname=%s aname=%s, n_uname=%d\n", uname, aname, n_uname); fid = p9_fid_create(clnt); if (fid == NULL) { *error = ENOMEM; return (NULL); } fid->uid = n_uname; req = p9_client_request(clnt, P9PROTO_TATTACH, error, "ddssd", fid->fid, P9PROTO_NOFID, uname, aname, n_uname); if (*error != 0) goto out; *error = p9_buf_readf(req->rc, clnt->proto_version, "Q", &qid); if (*error != 0) { P9_DEBUG(ERROR, "%s: p9_buf_readf failed: %d \n", __func__, *error); goto out; } P9_DEBUG(PROTO, "RATTACH qid %x.%llx.%x\n", qid.type, (unsigned long long)qid.path, qid.version); memmove(&fid->qid, &qid, sizeof(struct p9_qid)); p9_free_req(clnt, req); return (fid); out: if (req != NULL) p9_free_req(clnt, req); if (fid != NULL) p9_fid_destroy(fid); return (NULL); } /* Delete a file/directory. Corresponding fid will be cluncked too */ int p9_client_remove(struct p9_fid *fid) { int error; struct p9_client *clnt; struct p9_req_t *req; P9_DEBUG(PROTO, "TREMOVE fid %d\n", fid->fid); error = 0; clnt = fid->clnt; req = p9_client_request(clnt, P9PROTO_TREMOVE, &error, "d", fid->fid); if (error != 0) { P9_DEBUG(PROTO, "RREMOVE fid %d\n", fid->fid); return (error); } p9_free_req(clnt, req); return (error); } int p9_client_unlink(struct p9_fid *dfid, const char *name, int32_t flags) { int error; struct p9_client *clnt; struct p9_req_t *req; error = 0; clnt = dfid->clnt; req = p9_client_request(clnt, P9PROTO_TUNLINKAT, &error, "dsd", dfid->fid, name, flags); if (error != 0) { P9_DEBUG(PROTO, "RUNLINKAT fid %d\n", dfid->fid); return (error); } p9_free_req(clnt, req); return (error); } /* Inform the file server that the current file represented by fid is no longer * needed by the client. Any allocated fid on the server needs a clunk to be * destroyed. */ int p9_client_clunk(struct p9_fid *fid) { int error; struct p9_client *clnt; struct p9_req_t *req; error = 0; if (fid == NULL) { P9_DEBUG(ERROR, "%s: clunk with NULL fid is bad\n", __func__); return (0); } P9_DEBUG(PROTO, "TCLUNK fid %d \n", fid->fid); clnt = fid->clnt; req = p9_client_request(clnt, P9PROTO_TCLUNK, &error, "d", fid->fid); if (req != NULL) { P9_DEBUG(PROTO, "RCLUNK fid %d\n", fid->fid); p9_free_req(clnt, req); } p9_fid_destroy(fid); return (error); } /* * Client_walk is for searching any component name in a directory. * This is usually called on lookups. Also when we need a new open fid * as 9p needs to have an open fid for every file to fileops, we call this * validate the component of the file and return the newfid(openfid) created. */ struct p9_fid * p9_client_walk(struct p9_fid *oldfid, uint16_t nwnames, char **wnames, int clone, int *error) { struct p9_client *clnt; struct p9_fid *fid; struct p9_qid *wqids; struct p9_req_t *req; uint16_t nwqids, count; clnt = oldfid->clnt; wqids = NULL; nwqids = 0; /* * Before, we go and create fid, make sure we are not tearing * down. Only then we create. * Allow only cleanup clunk messages once we are starting to teardown. */ if (clnt->trans_status != P9FS_CONNECT) { *error = EIO; return (NULL); } if (clone) { fid = p9_fid_create(clnt); if (fid == NULL) { *error = ENOMEM; return (NULL); } fid->uid = oldfid->uid; } else fid = oldfid; P9_DEBUG(PROTO, "TWALK fids %d,%d nwnames %u wname %s\n", oldfid->fid, fid->fid, nwnames, wnames != NULL ? wnames[nwnames-1] : NULL); /* * The newfid is for the component in search. We are preallocating as * qemu on other side allocates or returns a fid if it sees a match */ req = p9_client_request(clnt, P9PROTO_TWALK, error, "ddT", oldfid->fid, fid->fid, wnames, nwnames); if (*error != 0) { if (fid != oldfid) p9_fid_destroy(fid); return (NULL); } *error = p9_buf_readf(req->rc, clnt->proto_version, "R", &nwqids, &wqids); if (*error != 0) goto out; P9_DEBUG(PROTO, "RWALK nwqid %d:\n", nwqids); if (nwqids != nwnames) { *error = ENOENT; goto out; } for (count = 0; count < nwqids; count++) P9_DEBUG(TRANS, "%s: [%d] %x.%llx.%x\n", __func__, count, wqids[count].type, (unsigned long long)wqids[count].path, wqids[count].version); if (nwnames) memmove(&fid->qid, &wqids[nwqids - 1], sizeof(struct p9_qid)); else fid->qid = oldfid->qid; p9_free_req(clnt, req); free(wqids, M_TEMP); return (fid); out: p9_free_req(clnt, req); if (wqids) free(wqids, M_TEMP); if (fid && fid != oldfid) p9_client_clunk(fid); return (NULL); } /* Open a file with given fid and mode */ int p9_client_open(struct p9_fid *fid, int mode) { int error, mtu; struct p9_client *clnt; struct p9_req_t *req; error = 0; clnt = fid->clnt; mtu = 0; P9_DEBUG(PROTO, "%s fid %d mode %d\n", p9_is_proto_dotl(clnt) ? "TLOPEN" : "TOPEN", fid->fid, mode); if (fid->mode != -1) return (EINVAL); if (p9_is_proto_dotl(clnt)) req = p9_client_request(clnt, P9PROTO_TLOPEN, &error, "dd", fid->fid, mode); else req = p9_client_request(clnt, P9PROTO_TOPEN, &error, "db", fid->fid, mode); if (error != 0) return (error); error = p9_buf_readf(req->rc, clnt->proto_version, "Qd", &fid->qid, &mtu); if (error != 0) goto out; P9_DEBUG(PROTO, "%s qid %x.%llx.%x mtu %x\n", p9_is_proto_dotl(clnt) ? "RLOPEN" : "ROPEN", (fid->qid).type, (unsigned long long)(fid->qid).path, (fid->qid).version, mtu); fid->mode = mode; fid->mtu = mtu; out: p9_free_req(clnt, req); return (error); } /* Request to get directory entries */ int p9_client_readdir(struct p9_fid *fid, char *data, uint64_t offset, uint32_t count) { int error; uint32_t rsize; struct p9_client *clnt; struct p9_req_t *req; char *dataptr; P9_DEBUG(PROTO, "TREADDIR fid %d offset %llu count %d\n", fid->fid, (unsigned long long) offset, count); error = 0; rsize = fid->mtu; clnt = fid->clnt; if (rsize == 0 || rsize > clnt->msize) rsize = clnt->msize; if (count < rsize) rsize = count; req = p9_client_request(clnt, P9PROTO_TREADDIR, &error, "dqd", fid->fid, offset, rsize); if (error != 0) { P9_DEBUG(ERROR, "%s: couldn't allocate req in client_readdir\n", __func__); return (-error); } error = p9_buf_readf(req->rc, clnt->proto_version, "D", &count, &dataptr); if (error != 0) { P9_DEBUG(ERROR, "%s: p0_buf_readf failed: %d\n", __func__, error); p9_free_req(clnt, req); return (-error); } P9_DEBUG(PROTO, "RREADDIR count %u\n", count); /* Copy back the data into the input buffer. */ memmove(data, dataptr, count); p9_free_req(clnt, req); return (count); } /* * Read count bytes from offset for the file fid into the character * buffer data. This buffer is handed over to p9fs to process into user * buffers. Note that this function typically returns the number of bytes read * so in case of an error we return -error so that we can distinguish between * error codes and bytes. */ int p9_client_read(struct p9_fid *fid, uint64_t offset, uint32_t count, char *data) { struct p9_client *clnt; struct p9_req_t *req; char *dataptr; int error, rsize; clnt = fid->clnt; rsize = fid->mtu; error = 0; P9_DEBUG(PROTO, "TREAD fid %d offset %llu %u\n", fid->fid, (unsigned long long) offset, count); if (!rsize || rsize > clnt->msize) rsize = clnt->msize; if (count < rsize) rsize = count; /* At this stage, we only have 8K buffers so only transfer */ req = p9_client_request(clnt, P9PROTO_TREAD, &error, "dqd", fid->fid, offset, rsize); if (error != 0) { P9_DEBUG(ERROR, "%s: failed allocate request\n", __func__); return (-error); } error = p9_buf_readf(req->rc, clnt->proto_version, "D", &count, &dataptr); if (error != 0) { P9_DEBUG(ERROR, "%s: p9_buf_readf failed: %d\n", __func__, error); goto out; } if (rsize < count) { P9_DEBUG(PROTO, "RREAD count (%d > %d)\n", count, rsize); count = rsize; } P9_DEBUG(PROTO, "RREAD count %d\n", count); if (count == 0) { error = -EIO; P9_DEBUG(ERROR, "%s: EIO error in client_read \n", __func__); goto out; } /* Copy back the data into the input buffer. */ memmove(data, dataptr, count); p9_free_req(clnt, req); return (count); out: p9_free_req(clnt, req); return (-error); } /* * Write count bytes from buffer to the offset for the file fid * Note that this function typically returns the number of bytes written * so in case of an error we return -error so that we can distinguish between * error codes and bytes. */ int p9_client_write(struct p9_fid *fid, uint64_t offset, uint32_t count, char *data) { struct p9_client *clnt; struct p9_req_t *req; int ret, error, rsize; clnt = fid->clnt; rsize = fid->mtu; ret = 0; error = 0; P9_DEBUG(PROTO, "TWRITE fid %d offset %llu %u\n", fid->fid, (unsigned long long) offset, count); if (!rsize || rsize > clnt->msize) rsize = clnt->msize; /* Limit set by Qemu ,8168 */ if (count > rsize) { count = rsize; } /* * Doing the Data blob instead. If at all we add the zerocopy, we can * change it to uio direct copy */ req = p9_client_request(clnt, P9PROTO_TWRITE, &error, "dqD", fid->fid, offset, count, data); if (error != 0) { P9_DEBUG(ERROR, "%s: failed allocate request: %d\n", __func__, error); return (-error); } error = p9_buf_readf(req->rc, clnt->proto_version, "d", &ret); if (error != 0) { P9_DEBUG(ERROR, "%s: p9_buf_readf error: %d\n", __func__, error); goto out; } if (count < ret) { P9_DEBUG(PROTO, "RWRITE count (%d > %d)\n", count, ret); ret = count; } P9_DEBUG(PROTO, "RWRITE count %d\n", ret); if (count == 0) { error = EIO; P9_DEBUG(ERROR, "%s: EIO error\n", __func__); goto out; } p9_free_req(clnt, req); return (ret); out: p9_free_req(clnt, req); return (-error); } /* Create file under directory fid, with name, permissions, mode. */ int p9_client_file_create(struct p9_fid *fid, char *name, uint32_t perm, int mode, char *extension) { int error; struct p9_client *clnt; struct p9_req_t *req; struct p9_qid qid; int mtu; P9_DEBUG(PROTO, "TCREATE fid %d name %s perm %d mode %d\n", fid->fid, name, perm, mode); clnt = fid->clnt; error = 0; if (fid->mode != -1) return (EINVAL); req = p9_client_request(clnt, P9PROTO_TCREATE, &error, "dsdb?s", fid->fid, name, perm, mode, extension); if (error != 0) return (error); error = p9_buf_readf(req->rc, clnt->proto_version, "Qd", &qid, &mtu); if (error != 0) goto out; P9_DEBUG(PROTO, "RCREATE qid %x.%jx.%x mtu %x\n", qid.type, (uintmax_t)qid.path, qid.version, mtu); fid->mode = mode; fid->mtu = mtu; out: p9_free_req(clnt, req); return (error); } /* Request file system information of the file system */ int p9_client_statfs(struct p9_fid *fid, struct p9_statfs *stat) { int error; struct p9_req_t *req; struct p9_client *clnt; error = 0; clnt = fid->clnt; P9_DEBUG(PROTO, "TSTATFS fid %d\n", fid->fid); req = p9_client_request(clnt, P9PROTO_TSTATFS, &error, "d", fid->fid); if (error != 0) { return (error); } error = p9_buf_readf(req->rc, clnt->proto_version, "ddqqqqqqd", &stat->type, &stat->bsize, &stat->blocks, &stat->bfree, &stat->bavail, &stat->files, &stat->ffree, &stat->fsid, &stat->namelen); if (error != 0) goto out; P9_DEBUG(PROTO, "RSTATFS fid %d type 0x%jx bsize %ju " "blocks %ju bfree %ju bavail %ju files %ju ffree %ju " "fsid %ju namelen %ju\n", fid->fid, (uintmax_t)stat->type, (uintmax_t)stat->bsize, (uintmax_t)stat->blocks, (uintmax_t)stat->bfree, (uintmax_t)stat->bavail, (uintmax_t)stat->files, (uintmax_t)stat->ffree, (uintmax_t)stat->fsid, (uintmax_t)stat->namelen); out: p9_free_req(clnt, req); return (error); } /* Rename file referenced by the fid */ int p9_client_renameat(struct p9_fid *oldfid, char *oldname, struct p9_fid *newfid, char *newname) { int error; struct p9_client *clnt; struct p9_req_t *req; P9_DEBUG(PROTO, "TRENAMEAT oldfid %d oldname %s newfid %d newfid %s", oldfid->fid, oldname, newfid->fid, newname); error = 0; clnt = oldfid->clnt; /* * we are calling the request with TRENAMEAT tag and not TRENAME with * the 9p protocol version 9p2000.u as the QEMU version supports this * version of renaming */ req = p9_client_request(clnt, P9PROTO_TRENAMEAT, &error, "dsds", oldfid->fid, oldname, newfid->fid, newname); if (error != 0) return (error); p9_free_req(clnt, req); return (error); } /* Request to create symbolic link */ int p9_create_symlink(struct p9_fid *fid, char *name, char *symtgt, gid_t gid) { int error; struct p9_req_t *req; struct p9_client *clnt; struct p9_qid qid; error = 0; clnt = fid->clnt; P9_DEBUG(PROTO, "TSYMLINK fid %d name %s\n", fid->fid, name); req = p9_client_request(clnt, P9PROTO_TSYMLINK, &error, "dssd", fid->fid, name, symtgt, gid); if (error != 0) return (error); error = p9_buf_readf(req->rc, clnt->proto_version, "Q", &qid); if (error != 0) { P9_DEBUG(ERROR, "%s: buf_readf failed %d\n", __func__, error); return (error); } P9_DEBUG(PROTO, "RSYMLINK qid %x.%jx.%x\n", qid.type, (uintmax_t)qid.path, qid.version); p9_free_req(clnt, req); return (0); } /* Request to create hard link */ int p9_create_hardlink(struct p9_fid *dfid, struct p9_fid *oldfid, char *name) { int error; struct p9_req_t *req; struct p9_client *clnt; error = 0; clnt = dfid->clnt; P9_DEBUG(PROTO, "TLINK dfid %d oldfid %d name %s\n", dfid->fid, oldfid->fid, name); req = p9_client_request(clnt, P9PROTO_TLINK, &error, "dds", dfid->fid, oldfid->fid, name); if (error != 0) return (error); p9_free_req(clnt, req); return (0); } /* Request to read contents of symbolic link */ int p9_readlink(struct p9_fid *fid, char **target) { int error; struct p9_client *clnt; struct p9_req_t *req; error = 0; clnt = fid->clnt; P9_DEBUG(PROTO, "TREADLINK fid %d\n", fid->fid); req = p9_client_request(clnt, P9PROTO_TREADLINK, &error, "d", fid->fid); if (error != 0) return (error); error = p9_buf_readf(req->rc, clnt->proto_version, "s", target); if (error != 0) { P9_DEBUG(ERROR, "%s: buf_readf failed %d\n", __func__, error); return (error); } P9_DEBUG(PROTO, "RREADLINK target %s \n", *target); p9_free_req(clnt, req); return (0); } /* Get file attributes of the file referenced by the fid */ int p9_client_getattr(struct p9_fid *fid, struct p9_stat_dotl *stat_dotl, uint64_t request_mask) { int err; struct p9_client *clnt; struct p9_req_t *req; err = 0; P9_DEBUG(PROTO, "TGETATTR fid %d mask %ju\n", fid->fid, (uintmax_t)request_mask); clnt = fid->clnt; req = p9_client_request(clnt, P9PROTO_TGETATTR, &err, "dq", fid->fid, request_mask); if (req == NULL) { P9_DEBUG(ERROR, "%s: allocation failed %d", __func__, err); goto error; } err = p9_buf_readf(req->rc, clnt->proto_version, "A", stat_dotl); if (err != 0) { P9_DEBUG(ERROR, "%s: buf_readf failed %d\n", __func__, err); goto error; } p9_free_req(clnt, req); P9_DEBUG(PROTO, "RGETATTR fid %d qid %x.%jx.%x st_mode %8.8x " "uid %d gid %d nlink %ju rdev %jx st_size %jx blksize %ju " "blocks %ju st_atime_sec %ju, st_atime_nsec %ju " "st_mtime_sec %ju, st_mtime_nsec %ju st_ctime_sec %ju " "st_ctime_nsec %ju st_btime_sec %ju, st_btime_nsec %ju " "st_stat %ju, st_data_version %ju \n", fid->fid, stat_dotl->qid.type, (uintmax_t)stat_dotl->qid.path, stat_dotl->qid.version, stat_dotl->st_mode, stat_dotl->st_uid, stat_dotl->st_gid, (uintmax_t)stat_dotl->st_nlink, (uintmax_t)stat_dotl->st_rdev, (uintmax_t)stat_dotl->st_size, (uintmax_t)stat_dotl->st_blksize, (uintmax_t)stat_dotl->st_blocks, (uintmax_t)stat_dotl->st_atime_sec, (uintmax_t)stat_dotl->st_atime_nsec, (uintmax_t)stat_dotl->st_mtime_sec, (uintmax_t)stat_dotl->st_mtime_nsec, (uintmax_t)stat_dotl->st_ctime_sec, (uintmax_t)stat_dotl->st_ctime_nsec, (uintmax_t)stat_dotl->st_btime_sec, (uintmax_t)stat_dotl->st_btime_nsec, (uintmax_t)stat_dotl->st_gen, (uintmax_t)stat_dotl->st_data_version); return (err); error: if (req != NULL) p9_free_req(clnt, req); return (err); } /* Set file attributes of the file referenced by the fid */ int p9_client_setattr(struct p9_fid *fid, struct p9_iattr_dotl *p9attr) { int err; struct p9_req_t *req; struct p9_client *clnt; err = 0; P9_DEBUG(PROTO, "TSETATTR fid %d" " valid %x mode %x uid %d gid %d size %ju" " atime_sec %ju atime_nsec %ju" " mtime_sec %ju mtime_nsec %ju\n", fid->fid, p9attr->valid, p9attr->mode, p9attr->uid, p9attr->gid, (uintmax_t)p9attr->size, (uintmax_t)p9attr->atime_sec, (uintmax_t)p9attr->atime_nsec, (uintmax_t)p9attr->mtime_sec, (uintmax_t)p9attr->mtime_nsec); clnt = fid->clnt; /* Any client_request error is converted to req == NULL error*/ req = p9_client_request(clnt, P9PROTO_TSETATTR, &err, "dA", fid->fid, p9attr); if (req == NULL) { P9_DEBUG(ERROR, "%s: allocation failed %d\n", __func__, err); goto error; } p9_free_req(clnt, req); error: return (err); }