Client implementation of the 9P protocol for constrained devices
git clone https://git.8pit.net/ninenano.git
1#include <assert.h> 2#include <errno.h> 3#include <string.h> 4#include <fcntl.h> 5#include <unistd.h> 6#include <inttypes.h> 7 8#include <sys/stat.h> 9#include <sys/types.h> 10 11#include "9p.h" 12#include "9util.h" 13 14#define ENABLE_DEBUG (0) 15#include "debug.h" 16 17/** 18 * @defgroup _9putil Static utility functions. 19 * 20 * @{ 21 */ 22 23/** 24 * Initializes the members of a packet buffer. The length field is set 25 * to the amount of bytes still available in the buffer and should be 26 * decremented when writing to the buffer. 27 * 28 * @param ctx 9P connection context. 29 * @param pkt Pointer to a packet for which the members should be 30 * initialized. 31 * @param type Type which should be used for this packet. 32 */ 33static void 34newpkt(_9pctx *ctx, _9ppkt *pkt, _9ptype type) 35{ 36 pkt->buf = ctx->buffer + _9P_HEADSIZ; 37 pkt->len = ctx->msize - _9P_HEADSIZ; 38 pkt->type = type; 39} 40 41/** 42 * Parses the header (the first 7 bytes) of a 9P message contained in 43 * the given packet buffer. The result is written to the len, type and 44 * tag members of the given packet buffer. Besides the position in the 45 * packet buffer is advanced. 46 * 47 * @param pkt Packet buffer from which data should be read. Besides this 48 * packet buffer is also used to store the result. 49 * @return `0` on success. 50 * @return `-EBADMSG` if the buffer content isn't a valid 9P message. 51 */ 52static int 53_9pheader(_9ppkt *pkt) 54{ 55 uint8_t type; 56 uint32_t len; 57 58 /* From intro(5): 59 * Each 9P message begins with a four-byte size field 60 * specifying the length in bytes of the complete message 61 * including the four bytes of the size field itself. 62 */ 63 if (pkt->len < BIT32SZ) 64 return -EBADMSG; 65 ptoh32(&len, pkt); 66 67 DEBUG("Length of the 9P message: %"PRIu32"\n", len); 68 if (len > pkt->len + BIT32SZ || len < _9P_HEADSIZ) 69 return -EBADMSG; 70 pkt->len = len - BIT32SZ; 71 72 /* From intro(5): 73 * The next byte is the message type, one of the constants in 74 * the enumeration in the include file <fcall.h>. 75 */ 76 ptoh8(&type, pkt); 77 78 DEBUG("Type of 9P message: %"PRIu8"\n", type); 79 if (type < Tversion || type >= Tmax) 80 return -EBADMSG; 81 pkt->type = (_9ptype)type; 82 83 /* From intro(5): 84 * The next two bytes are an identifying tag, described below. 85 */ 86 ptoh16(&pkt->tag, pkt); 87 DEBUG("Tag of 9P message: %"PRIu16"\n", pkt->tag); 88 89 return 0; 90} 91 92/** 93 * Performs a 9P request, meaning this function sends a T-message to the 94 * server, reads the R-message from the client and stores it in a memory 95 * location provided by the caller. 96 * 97 * Keep in mind that the same ::_9ppkt is being used for the T- and 98 * R-message, thus after calling this method you shouldn't can't access 99 * the R-message values anymore.100 *101 * The length field of the given packet should be equal to the amount of102 * space (in bytes) still available in the packet buffer.103 *104 * @param ctx 9P connection context.105 * @param p Pointer to the memory location containing the T-message. The106 * caller doesn't need to initialize the `tag` field of this message.107 * Besides this pointer is also used to store the R-message send by108 * the server as a reply.109 * @return `0` on success, on error a negative errno is returned.110 */111static int112do9p(_9pctx *ctx, _9ppkt *p)113{114 ssize_t ret;115 _9ptype ttype;116 uint32_t reallen;117 uint16_t ttag;118119 DEBUG("Sending message of type %d to server\n", p->type);120121 /* From version(5):122 * The tag should be NOTAG (value (ushort)~0) for a version message.123 */124 p->tag = (p->type == Tversion) ?125 _9P_NOTAG : (uint16_t)randu32();126127 assert(ctx->msize > p->len);128 reallen = ctx->msize - p->len;129130 p->buf = ctx->buffer; /* Reset buffer position. */131 p->len = _9P_HEADSIZ;132133 /* Build the "header", meaning: size[4] type[1] tag[2]134 * Every 9P message needs those first 7 bytes. */135 htop32(reallen, p);136 htop8(p->type, p);137 htop16(p->tag, p);138139 DEBUG("Sending %"PRIu32" bytes to server...\n", reallen);140 if ((ret = ctx->write(ctx->buffer, reallen)) < 0)141 return (int)ret;142143 /* Values will be overwritten by _9pheader. */144 ttype = p->type;145 ttag = p->tag;146147 DEBUG("Reading from server...\n");148 if ((ret = ctx->read(ctx->buffer, ctx->msize)) < 0)149 return (int)ret;150151 /* Maximum length of a 9P message is 2^32. */152 if ((size_t)ret > UINT32_MAX)153 return -EMSGSIZE;154155 p->len = (uint32_t)ret;156 p->buf = ctx->buffer;157158 DEBUG("Read %zu bytes from server, parsing them...\n", ret);159 if ((ret = _9pheader(p)))160 return (int)ret;161162 if (p->tag != ttag) {163 DEBUG("Tag mismatch (%"PRIu8" vs. %"PRIu8")\n", p->tag, ttag);164 return -EBADMSG;165 }166167 if (p->type != ttype + 1) {168 DEBUG("Unexpected value in type field: %d\n", p->type);169 return -EBADMSG;170 }171172 return 0;173}174175/**176 * Frees all resources allocated for a given fid on both the client and177 * the server. Optionally the file associated with the fid can also be178 * removed from the server.179 *180 * @pre t == Tclunk || t == Tremove181 *182 * @param ctx 9P connection context.183 * @param f Pointer to fid on which the operation should be performed.184 * @param t Type of the operation which should be performed.185 *186 * @return `0` on success, on error a negative errno is returned.187 */188static int189fidrem(_9pctx *ctx, _9pfid *f, _9ptype t)190{191 int r;192 _9ppkt pkt;193194 assert(t == Tclunk || t == Tremove);195196 /* From intro(5):197 * size[4] Tclunk|Tremove tag[2] fid[4]198 */199 newpkt(ctx, &pkt, t);200 htop32(f->fid, &pkt);201202 if ((r = do9p(ctx, &pkt)))203 return r;204205 /* From intro(5):206 * size[4] Rclunk|Rremove tag[2]207 *208 * These first seven bytes are already parsed by do9p.209 * Therefore we don't need to parse anything here.210 */211212 /* fid wasn't allocated by us so don't use `fid->fid = 0` here */213 if (!fidtbl(ctx->fids, f->fid, DEL))214 return -EBADF;215216 return 0;217}218219/**220 * Parses the body of a 9P Ropen or Rcreate message. The body of those221 * two messages consists of a qid and an iounit. The values of both222 * fields are stored in the given fid.223 *224 * @param ctx 9P connection context.225 * @param f Pointer to the fid associated with the new file.226 * @param pkt Pointer to the packet from which the body should be read.227 * @return `0` on success, on error a negative errno is returned.228 */229static int230newfile(_9pctx *ctx, _9pfid *f, _9ppkt *pkt)231{232 if (hqid(&f->qid, pkt) || pkt->len < BIT32SZ)233 return -EBADMSG;234 ptoh32(&f->iounit, pkt);235236 /* From open(5):237 * The iounit field returned by open and create may be zero.238 * If it is not, it is the maximum number of bytes that are239 * guaranteed to be read from or written to the file without240 * breaking the I/O transfer into multiple 9P messages241 */242 if (!f->iounit)243 f->iounit = ctx->msize - _9P_IOHDRSIZ;244245 f->off = 0;246 return 0;247}248249/**250 * Only a maximum of bytes can be transfered atomically. If the amonut251 * of bytes we want to write to a file (or read from it) exceed this252 * limit we need to send multiple R-messages to the server. This253 * function takes care of doing this.254 *255 * @pre t == Twrite || t == Tread256 *257 * @param ctx 9P connection context.258 * @param f Pointer to fid on which a read or write operation should be259 * performed.260 * @param buf Pointer to a buffer from which data should be written to261 * or written from.262 * @param count Amount of bytes that should be written or read from the263 * file.264 * @param t Type of the operation which should be performed.265 */266static ssize_t267ioloop(_9pctx *ctx, _9pfid *f, char *buf, size_t count, _9ptype t)268{269 int r;270 size_t n;271 uint32_t pcnt, ocnt;272 _9ppkt pkt;273274 assert(t == Twrite || t == Tread);275276 n = 0;277 while (n < count) {278 /* From intro(5):279 * size[4] Tread tag[2] fid[4] offset[8] count[4]280 * size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]281 */282 newpkt(ctx, &pkt, t);283 htop32(f->fid, &pkt);284 htop64(f->off, &pkt);285286 assert(n <= count);287 if (count - n <= UINT32_MAX)288 pcnt = (uint32_t)(count - n);289 else290 pcnt = UINT32_MAX;291292 if (pcnt > f->iounit)293 pcnt = f->iounit;294 htop32(pcnt, &pkt);295296 if (t == Twrite) {297 if (pcnt > pkt.len - BIT32SZ)298 pcnt = pkt.len - BIT32SZ;299 if (!pcnt) return -EOVERFLOW;300 bufcpy(&pkt, &buf[n], pcnt);301 }302303 DEBUG("Sending %s with offset %"PRIu64" and count %"PRIu32"\n",304 (t == Tread) ? "Tread" : "Twrite", f->off, pcnt);305306 if ((r = do9p(ctx, &pkt)))307 return r;308309 /* From intro(5):310 * size[4] Rread tag[2] count[4] data[count]311 * size[4] Rwrite tag[2] count[4]312 */313 ocnt = pcnt;314 if (pkt.len < BIT32SZ)315 return -EBADMSG;316 ptoh32(&pcnt, &pkt);317318 /* From open(5):319 * If the offset field is greater than or equal to the320 * number of bytes in the file, a count of zero will321 * be returned.322 */323 if (!pcnt)324 return 0; /* EOF */325326 if (pcnt > count)327 return -EBADMSG;328329 if (t == Tread) {330 if (pkt.len < pcnt)331 return -EBADMSG;332 memcpy(&buf[n], pkt.buf, pcnt);333 }334335 n += pcnt;336 f->off += pcnt;337338 if (pcnt < ocnt)339 break;340 }341342 return (ssize_t)n;343}344345/**@}*/346347/**348 * Initializes a 9P connection context.349 *350 * @param read Function used for receiving data from the server.351 * @param write Function used for sending data to the server.352 * @param ctx 9P connection context which should be initialized.353 */354void355_9pinit(_9pctx *ctx, iofunc read, iofunc write)356{357 initrand();358 memset(ctx->fids, 0, _9P_MAXFIDS * sizeof(_9pfid));359360 ctx->msize = _9P_MSIZE;361 ctx->write = write;362 ctx->read = read;363}364365/**366 * From version(5):367 * The version request negotiates the protocol version and message368 * size to be used on the connection and initializes the connection369 * for I/O. Tversion must be the first message sent on the 9P370 * connection, and the client cannot issue any further requests until371 * it has received the Rversion reply.372 *373 * The version parameter is always set to the value of `_9P_VERSION`,374 * the msize parameter on the other hand is always set to the value of375 * `_9P_MSIZE`. The msize choosen by the server is stored in the msize376 * member of the given connection context.377 *378 * @param ctx 9P connection context.379 *380 * @return `0` on success.381 * @return `-EBADMSG` if the 9P message was invalid.382 * @return `-EMSGSIZE` if the server msize was greater than `_9P_MSIZE`.383 * @return `-ENOPROTOOPT` server implements a different version of the384 * 9P network protocol.385 */386int387_9pversion(_9pctx *ctx)388{389 int r;390 char ver[_9P_VERLEN];391 _9ppkt pkt;392393 /* From intro(5):394 * size[4] Tversion tag[2] msize[4] version[s]395 */396 newpkt(ctx, &pkt, Tversion);397 htop32(_9P_MSIZE, &pkt);398 if (pstring(_9P_VERSION, &pkt))399 return -EOVERFLOW;400401 if ((r = do9p(ctx, &pkt)))402 return r;403404 /* From intro(5):405 * size[4] Rversion tag[2] msize[4] version[s]406 *407 * Also according to version(5) the version field in the408 * R-message must be a string of the form `9Pnnnn` thus it has409 * to be at least 4 bytes long plus 2 bytes for the size tag and410 * 4 bytes for the msize field.411 */412 if (pkt.len <= 10)413 return -EBADMSG;414 ptoh32(&ctx->msize, &pkt);415416 DEBUG("Msize of Rversion message: %"PRIu32"\n", ctx->msize);417418 /* From version(5):419 * The server responds with its own maximum, msize, which must420 * be less than or equal to the client's value.421 */422 if (ctx->msize > _9P_MSIZE) {423 DEBUG("Servers msize is too large (%"PRIu32")\n", ctx->msize);424 return -EMSGSIZE;425 } else if (ctx->msize < _9P_MINSIZE) {426 DEBUG("Servers msize is too small (%"PRIu32")\n", ctx->msize);427 return -EOVERFLOW;428 }429430 if (hstring(ver, _9P_VERLEN, &pkt))431 return -EBADMSG;432 DEBUG("Version string reported by server: %s\n", ver);433434 /* From version(5):435 * If the server does not understand the client's version436 * string, it should respond with an Rversion message (not437 * Rerror) with the version string the 7 characters `unknown`.438 */439 if (!strcmp(ver, "unknown"))440 return -ENOPROTOOPT;441442 return 0;443}444445/**446 * From attach(5):447 * The attach message serves as a fresh introduction from a user on448 * the client machine to the server. As a result of the attach449 * transaction, the client will have a connection to the root450 * directory of the desired file tree, represented by fid.451 *452 * The afid parameter is always set to the value of `_9P_NOFID` since453 * authentication is not supported currently.454 *455 * @param ctx 9P connection context.456 * @param dest Pointer to a pointer which should be set to the address457 * of the corresponding entry in the fid table.458 * @param uname User identification.459 * @param aname File tree to access.460 * @return `0` on success, on error a negative errno is returned.461 */462int463_9pattach(_9pctx *ctx, _9pfid **dest, char *uname, char *aname)464{465 int r;466 _9pfid *fid;467 _9ppkt pkt;468469 /* From intro(5):470 * size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]471 */472 newpkt(ctx, &pkt, Tattach);473 htop32(_9P_ROOTFID, &pkt);474 htop32(_9P_NOFID, &pkt);475 if (pstring(uname, &pkt) || pstring(aname, &pkt))476 return -EOVERFLOW;477478 if ((r = do9p(ctx, &pkt)))479 return r;480481 /* From intro(5):482 * size[4] Rattach tag[2] qid[13]483 */484 if (!(fid = fidtbl(ctx->fids, _9P_ROOTFID, ADD)))485 return -ENFILE;486 fid->fid = _9P_ROOTFID;487488 if (hqid(&fid->qid, &pkt)) {489 fid->fid = 0; /* mark fid as free. */490 return -EBADMSG;491 }492493 *dest = fid;494 return 0;495}496497/**498 * From clunk(5):499 * The clunk request informs the file server that the current file500 * represented by fid is no longer needed by the client. The actual501 * file is not removed on the server unless the fid had been opened502 * with ORCLOSE.503 *504 * @param ctx 9P connection context.505 * @param f Pointer to a fid which should be closed.506 * @return `0` on success, on error a negative errno is returned.507 */508int509_9pclunk(_9pctx *ctx, _9pfid *f)510{511 return fidrem(ctx, f, Tclunk);512}513514/**515 * From intro(5):516 * The stat transaction retrieves information about the file. The stat517 * field in the reply includes the file's name, access permissions518 * (read, write and execute for owner, group and public), access and519 * modification times, and owner and group identifications (see520 * stat(2)).521 *522 * Retrieves information about the file associated with the given fid.523 *524 * @param ctx 9P connection context.525 * @param fid Fid of the file to retrieve information for.526 * @param buf Pointer to stat struct to fill.527 * @return `0` on success, on error a negative errno is returned.528 */529int530_9pstat(_9pctx *ctx, _9pfid *fid, struct stat *buf)531{532 int r;533 uint32_t mode;534 _9ppkt pkt;535536 /* From intro(5):537 * size[4] Tstat tag[2] fid[4]538 */539 newpkt(ctx, &pkt, Tstat);540 htop32(fid->fid, &pkt);541542 if ((r = do9p(ctx, &pkt)))543 return r;544545 /* From intro(5):546 * size[4] Rstat tag[2] stat[n]547 *548 * See stat(5) for the definition of stat[n].549 */550 if (pkt.len < _9P_MINSTSIZ)551 return -EBADMSG;552553 /* Skip: n[2], size[2], type[2] and dev[4]. */554 advbuf(&pkt, 3 * BIT16SZ + BIT32SZ);555556 /* store qid informations in given fid. */557 if (hqid(&fid->qid, &pkt))558 return -EBADMSG;559560 /* store the other information in the stat struct. */561 ptoh32(&mode, &pkt);562 buf->st_mode = (mode & DMDIR) ? S_IFDIR : S_IFREG;563 ptoh32((uint32_t*)&buf->st_atime, &pkt);564 ptoh32((uint32_t*)&buf->st_mtime, &pkt);565 buf->st_ctime = buf->st_mtime;566 ptoh64((uint64_t*)&buf->st_size, &pkt);567568 /* information for stat struct we cannot extract from the reply. */569 buf->st_dev = buf->st_ino = buf->st_rdev = 0;570 buf->st_nlink = 1;571 buf->st_uid = buf->st_gid = 0;572 buf->st_blksize = ctx->msize - _9P_IOHDRSIZ;573 buf->st_blocks = buf->st_size / buf->st_blksize + 1;574575 /* name, uid, gid and muid are ignored. */576577 return 0;578}579580/* TODO insert _9pwstat implementation here. */581582/**583 * From intro(5):584 * A walk message causes the server to change the current file585 * associated with a fid to be a file in the directory that is the old586 * current file, or one of its subdirectories. Walk returns a new fid587 * that refers to the resulting file. Usually, a client maintains a588 * fid for the root, and navigates by walks from the root fid.589 *590 * This function alsways walks frmom the root fid and returns a fid for591 * the last element of the given path.592 *593 * @attention The 9P protocol specifies that only a a maximum of sixteen594 * name elements or qids may be packed in a single message. For name595 * elements or qids that exceeds this limit multiple Twalk/Rwalk message596 * need to be send. Since `VFS_NAME_MAX` defaults to `31` it is very597 * unlikely that this limit will be reached. Therefore this function598 * doesn't support sending walk messages that exceed this limit.599 *600 * @param ctx 9P connection context.601 * @param dest Pointer to a pointer which should be set to the address602 * of the corresponding entry in the fid table.603 * @param path Path which should be walked.604 * @return `0` on success, on error a negative errno is returned.605 */606int607_9pwalk(_9pctx *ctx, _9pfid **dest, char *path)608{609 int r;610 char *cur, *sep;611 size_t n, i, len, elen;612 uint8_t *nwname;613 uint16_t nwqid;614 _9pfid *fid;615 _9ppkt pkt;616617 if (*path == '\0' || !strcmp(path, "/"))618 len = 0;619 else620 len = strlen(path);621622 if (!(fid = newfid(ctx->fids)))623 return -ENFILE;624625 /* From intro(5):626 * size[4] Twalk tag[2] fid[4] newfid[4] nwname[2]627 * nwname*(wname[s])628 */629 newpkt(ctx, &pkt, Twalk);630 htop32(_9P_ROOTFID, &pkt);631 htop32(fid->fid, &pkt);632633 /* leave space for nwname[2]. */634 nwname = pkt.buf;635 advbuf(&pkt, BIT16SZ);636637 /* generate nwname*(wname[s]) */638 for (n = i = 0; i < len; n++, i += elen + 1) {639 if (n >= _9P_MAXWEL) {640 r = -ENAMETOOLONG;641 goto err;642 }643644 cur = &path[i];645 if (!(sep = strchr(cur, _9P_PATHSEP)))646 sep = &path[len];647648 assert(sep - cur >= 0);649 elen = (size_t)(sep - cur);650651 if (pnstring(cur, elen, &pkt)) {652 r = -EOVERFLOW;653 goto err;654 }655 }656657 DEBUG("Constructed Twalk with %zu elements\n", n);658659 pkt.buf = nwname;660 pkt.len += BIT16SZ;661 assert(n <= UINT16_MAX);662 htop16((uint16_t)n, &pkt);663664 if ((r = do9p(ctx, &pkt)))665 goto err;666667 /* From intro(5):668 * size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13])669 */670 if (pkt.len < BIT16SZ) {671 r = -EBADMSG;672 goto err;673 }674 ptoh16(&nwqid, &pkt);675676 /**677 * From walk(5):678 * nwqid is therefore either nwname or the index of the first679 * elementwise walk that failed.680 */681 DEBUG("nwqid: %"PRIu16"\n", nwqid);682 if (nwqid != n || nwqid > pkt.len || nwqid > _9P_MAXWEL) { /* XXX */683 r = -EBADMSG;684 goto err;685 }686687 /* Skip to the last qid. */688 if (nwqid) {689 advbuf(&pkt, (nwqid - 1) * _9P_QIDSIZ);690 if (hqid(&fid->qid, &pkt)) {691 r = -EBADMSG;692 goto err;693 }694 }695696 *dest = fid;697 return 0;698699err:700 fid->fid = 0; /* mark fid as free. */701 return r;702}703704/**705 * From open(5):706 * The open request asks the file server to check permissions and707 * prepare a fid for I/O with subsequent read and write messages.708 *709 * @param ctx 9P connection context.710 * @param f Fid which should be opened for I/O.711 * @param flags Flags used for opening the fid. Supported flags are:712 * OREAD, OWRITE, ORDWR and OTRUNC.713 * @return `0` on success, on error a negative errno is returned.714 */715int716_9popen(_9pctx *ctx, _9pfid *f, uint8_t flags)717{718 int r;719 _9ppkt pkt;720721 /* From intro(5):722 * size[4] Topen tag[2] fid[4] mode[1]723 */724 newpkt(ctx, &pkt, Topen);725 htop32(f->fid, &pkt);726 htop8(flags, &pkt);727728 if ((r = do9p(ctx, &pkt)))729 return r;730731 /* From intro(5):732 * size[4] Ropen tag[2] qid[13] iounit[4]733 */734 if ((r = newfile(ctx, f, &pkt)))735 return r;736737 return 0;738}739740/**741 * From open(5):742 * The create request asks the file server to create a new file with the name743 * supplied, in the directory (dir) represented by fid, and requires write744 * permission in the directory745 *746 * After creation the file will be opened automatically. And the given747 * fid will no longer represent the directory, instead it now represents748 * the newly created file.749 *750 * @param ctx 9P connection context.751 * @param f Pointer to the fid associated with the directory in which a752 * new file should be created.753 * @param name Name which should be used for the newly created file.754 * @param perm Permissions with which the new file should be created.755 * @param flags Flags which should be used for opening the file756 * afterwards. See ::_9popen.757 */758int759_9pcreate(_9pctx *ctx, _9pfid *f, char *name, uint32_t perm, uint8_t flags)760{761 int r;762 _9ppkt pkt;763764 /* From intro(5):765 * size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]766 */767 newpkt(ctx, &pkt, Tcreate);768 htop32(f->fid, &pkt);769 if (pstring(name, &pkt) || BIT32SZ + BIT8SZ > pkt.len)770 return -EOVERFLOW;771 htop32(perm, &pkt);772 htop8(flags, &pkt);773774 if ((r = do9p(ctx, &pkt)))775 return r;776777 /* From intro(5):778 * size[4] Rcreate tag[2] qid[13] iounit[4]779 */780 if ((r = newfile(ctx, f, &pkt)))781 return r;782783 return 0;784}785786/**787 * From read(5):788 * The read request asks for count bytes of data from the file789 * identified by fid, which must be opened for reading, start-790 * ing offset bytes after the beginning of the file.791 *792 * @param ctx 9P connection context.793 * @param f Fid from which data should be read.794 * @param dest Pointer to a buffer to which the received data should be795 * written.796 * @param count Amount of data that should be read.797 * @return The number of bytes read on success or a negative errno on798 * error.799 */800ssize_t801_9pread(_9pctx *ctx, _9pfid *f, char *dest, size_t count)802{803 return ioloop(ctx, f, dest, count, Tread);804}805806/**807 * From intro(5):808 * The write request asks that count bytes of data be recorded in the809 * file identified by fid, which must be opened for writing, starting810 * offset bytes after the beginning of the file.811 *812 * @param ctx 9P connection context.813 * @param f Pointer to the fid to which data should be written.814 * @param src Pointer to a buffer containing the data which should be815 * written to the file.816 * @param count Amount of bytes which should be written to the file817 * @return The number of bytes read on success or a negative errno on818 * error.819 */820ssize_t821_9pwrite(_9pctx *ctx, _9pfid *f, char *src, size_t count)822{823 return ioloop(ctx, f, src, count, Twrite);824}825826/**827 * From remove(5):828 * The remove request asks the file server both to remove the file829 * represented by fid and to clunk the fid, even if the remove fails.830 *831 * @param ctx 9P connection context.832 * @param f Pointer to the fid which should be removed.833 * @return `0` on success, on error a negative errno is returned.834 */835int836_9premove(_9pctx *ctx, _9pfid *f)837{838 return fidrem(ctx, f, Tremove);839}