9p.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. // -------------------------------------------------
  2. // --------------------- 9P ------------------------
  3. // -------------------------------------------------
  4. // Implementation of the 9p filesystem device following the
  5. // 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
  6. "use strict";
  7. // Feature bit (bit position) for mount tag.
  8. const VIRTIO_9P_F_MOUNT_TAG = 0;
  9. // Assumed max tag length in bytes.
  10. const VIRTIO_9P_MAX_TAGLEN = 254;
  11. const MAX_REPLYBUFFER_SIZE = 16 * 1024 * 1024;
  12. // TODO
  13. // flush
  14. var EPERM = 1; /* Operation not permitted */
  15. var ENOENT = 2; /* No such file or directory */
  16. var EEXIST = 17; /* File exists */
  17. var EINVAL = 22; /* Invalid argument */
  18. var EOPNOTSUPP = 95; /* Operation is not supported */
  19. var ENOTEMPTY = 39; /* Directory not empty */
  20. var EPROTO = 71; /* Protocol error */
  21. var P9_SETATTR_MODE = 0x00000001;
  22. var P9_SETATTR_UID = 0x00000002;
  23. var P9_SETATTR_GID = 0x00000004;
  24. var P9_SETATTR_SIZE = 0x00000008;
  25. var P9_SETATTR_ATIME = 0x00000010;
  26. var P9_SETATTR_MTIME = 0x00000020;
  27. var P9_SETATTR_CTIME = 0x00000040;
  28. var P9_SETATTR_ATIME_SET = 0x00000080;
  29. var P9_SETATTR_MTIME_SET = 0x00000100;
  30. var P9_STAT_MODE_DIR = 0x80000000;
  31. var P9_STAT_MODE_APPEND = 0x40000000;
  32. var P9_STAT_MODE_EXCL = 0x20000000;
  33. var P9_STAT_MODE_MOUNT = 0x10000000;
  34. var P9_STAT_MODE_AUTH = 0x08000000;
  35. var P9_STAT_MODE_TMP = 0x04000000;
  36. var P9_STAT_MODE_SYMLINK = 0x02000000;
  37. var P9_STAT_MODE_LINK = 0x01000000;
  38. var P9_STAT_MODE_DEVICE = 0x00800000;
  39. var P9_STAT_MODE_NAMED_PIPE = 0x00200000;
  40. var P9_STAT_MODE_SOCKET = 0x00100000;
  41. var P9_STAT_MODE_SETUID = 0x00080000;
  42. var P9_STAT_MODE_SETGID = 0x00040000;
  43. var P9_STAT_MODE_SETVTX = 0x00010000;
  44. const P9_LOCK_TYPE_RDLCK = 0;
  45. const P9_LOCK_TYPE_WRLCK = 1;
  46. const P9_LOCK_TYPE_UNLCK = 2;
  47. const P9_LOCK_TYPES = ["shared", "exclusive", "unlock"];
  48. const P9_LOCK_FLAGS_BLOCK = 1;
  49. const P9_LOCK_FLAGS_RECLAIM = 2;
  50. const P9_LOCK_SUCCESS = 0;
  51. const P9_LOCK_BLOCKED = 1;
  52. const P9_LOCK_ERROR = 2;
  53. const P9_LOCK_GRACE = 3;
  54. var FID_NONE = -1;
  55. var FID_INODE = 1;
  56. var FID_XATTR = 2;
  57. /**
  58. * @constructor
  59. *
  60. * @param {FS} filesystem
  61. * @param {CPU} cpu
  62. */
  63. function Virtio9p(filesystem, cpu, bus) {
  64. /** @type {FS} */
  65. this.fs = filesystem;
  66. /** @const @type {BusConnector} */
  67. this.bus = bus;
  68. //this.configspace = [0x0, 0x4, 0x68, 0x6F, 0x73, 0x74]; // length of string and "host" string
  69. //this.configspace = [0x0, 0x9, 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x72, 0x6F, 0x6F, 0x74 ]; // length of string and "/dev/root" string
  70. this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
  71. this.configspace_taglen = this.configspace_tagname.length; // num bytes
  72. this.VERSION = "9P2000.L";
  73. this.BLOCKSIZE = 8192; // Let's define one page.
  74. this.msize = 8192; // maximum message size
  75. this.replybuffer = new Uint8Array(this.msize*2); // Twice the msize to stay on the safe site
  76. this.replybuffersize = 0;
  77. this.fids = [];
  78. /** @type {VirtIO} */
  79. this.virtio = new VirtIO(cpu,
  80. {
  81. name: "virtio-9p",
  82. pci_id: 0x06 << 3,
  83. device_id: 0x1049,
  84. subsystem_device_id: 9,
  85. common:
  86. {
  87. initial_port: 0xA800,
  88. queues:
  89. [
  90. {
  91. size_supported: 32,
  92. notify_offset: 0,
  93. },
  94. ],
  95. features:
  96. [
  97. VIRTIO_9P_F_MOUNT_TAG,
  98. VIRTIO_F_VERSION_1,
  99. VIRTIO_F_RING_EVENT_IDX,
  100. VIRTIO_F_RING_INDIRECT_DESC,
  101. ],
  102. on_driver_ok: () => {},
  103. },
  104. notification:
  105. {
  106. initial_port: 0xA900,
  107. single_handler: false,
  108. handlers:
  109. [
  110. (queue_id) =>
  111. {
  112. if(queue_id !== 0)
  113. {
  114. dbg_assert(false, "Virtio9P Notified for non-existent queue: " + queue_id +
  115. " (expected queue_id of 0)");
  116. return;
  117. }
  118. while(this.virtqueue.has_request())
  119. {
  120. const bufchain = this.virtqueue.pop_request();
  121. this.ReceiveRequest(bufchain);
  122. }
  123. this.virtqueue.notify_me_after(0);
  124. // Don't flush replies here: async replies are not completed yet.
  125. },
  126. ],
  127. },
  128. isr_status:
  129. {
  130. initial_port: 0xA700,
  131. },
  132. device_specific:
  133. {
  134. initial_port: 0xA600,
  135. struct:
  136. [
  137. {
  138. bytes: 2,
  139. name: "mount tag length",
  140. read: () => this.configspace_taglen,
  141. write: data => { /* read only */ },
  142. },
  143. ].concat(v86util.range(VIRTIO_9P_MAX_TAGLEN).map(index =>
  144. ({
  145. bytes: 1,
  146. name: "mount tag name " + index,
  147. // Note: configspace_tagname may have changed after set_state
  148. read: () => this.configspace_tagname[index] || 0,
  149. write: data => { /* read only */ },
  150. })
  151. )),
  152. },
  153. });
  154. this.virtqueue = this.virtio.queues[0];
  155. }
  156. Virtio9p.prototype.get_state = function()
  157. {
  158. var state = [];
  159. state[0] = this.configspace_tagname;
  160. state[1] = this.configspace_taglen;
  161. state[2] = this.virtio;
  162. state[3] = this.VERSION;
  163. state[4] = this.BLOCKSIZE;
  164. state[5] = this.msize;
  165. state[6] = this.replybuffer;
  166. state[7] = this.replybuffersize;
  167. state[8] = this.fids.map(function(f) { return [f.inodeid, f.type, f.uid, f.dbg_name]; });
  168. state[9] = this.fs;
  169. return state;
  170. };
  171. Virtio9p.prototype.set_state = function(state)
  172. {
  173. this.configspace_tagname = state[0];
  174. this.configspace_taglen = state[1];
  175. this.virtio.set_state(state[2]);
  176. this.virtqueue = this.virtio.queues[0];
  177. this.VERSION = state[3];
  178. this.BLOCKSIZE = state[4];
  179. this.msize = state[5];
  180. this.replybuffer = state[6];
  181. this.replybuffersize = state[7];
  182. this.fids = state[8].map(function(f)
  183. {
  184. return { inodeid: f[0], type: f[1], uid: f[2], dbg_name: f[3] };
  185. });
  186. this.fs.set_state(state[9]);
  187. };
  188. // Note: dbg_name is only used for debugging messages and may not be the same as the filename,
  189. // since it is not synchronised with renames done outside of 9p. Hard-links, linking and unlinking
  190. // operations also mean that having a single filename no longer makes sense.
  191. // Set TRACK_FILENAMES = true (in config.js) to sync dbg_name during 9p renames.
  192. Virtio9p.prototype.Createfid = function(inodeid, type, uid, dbg_name) {
  193. return {inodeid, type, uid, dbg_name};
  194. };
  195. Virtio9p.prototype.update_dbg_name = function(idx, newname)
  196. {
  197. for(const fid of this.fids)
  198. {
  199. if(fid.inodeid === idx) fid.dbg_name = newname;
  200. }
  201. };
  202. Virtio9p.prototype.reset = function() {
  203. this.fids = [];
  204. this.virtio.reset();
  205. };
  206. Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
  207. dbg_assert(payloadsize >= 0, "9P: Negative payload size");
  208. marshall.Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0);
  209. if((payloadsize+7) >= this.replybuffer.length) {
  210. message.Debug("Error in 9p: payloadsize exceeds maximum length");
  211. }
  212. //for(var i=0; i<payload.length; i++)
  213. // this.replybuffer[7+i] = payload[i];
  214. this.replybuffersize = payloadsize+7;
  215. };
  216. Virtio9p.prototype.SendError = function (tag, errormsg, errorcode) {
  217. //var size = marshall.Marshall(["s", "w"], [errormsg, errorcode], this.replybuffer, 7);
  218. var size = marshall.Marshall(["w"], [errorcode], this.replybuffer, 7);
  219. this.BuildReply(6, tag, size);
  220. };
  221. Virtio9p.prototype.SendReply = function (bufchain) {
  222. dbg_assert(this.replybuffersize >= 0, "9P: Negative replybuffersize");
  223. bufchain.set_next_blob(this.replybuffer.subarray(0, this.replybuffersize));
  224. this.virtqueue.push_reply(bufchain);
  225. this.virtqueue.flush_replies();
  226. };
  227. Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
  228. // TODO: split into header + data blobs to avoid unnecessary copying.
  229. const buffer = new Uint8Array(bufchain.length_readable);
  230. bufchain.get_next_blob(buffer);
  231. const state = { offset : 0 };
  232. var header = marshall.Unmarshall(["w", "b", "h"], buffer, state);
  233. var size = header[0];
  234. var id = header[1];
  235. var tag = header[2];
  236. //message.Debug("size:" + size + " id:" + id + " tag:" + tag);
  237. switch(id)
  238. {
  239. case 8: // statfs
  240. size = this.fs.GetTotalSize(); // size used by all files
  241. var space = this.fs.GetSpace();
  242. var req = [];
  243. req[0] = 0x01021997;
  244. req[1] = this.BLOCKSIZE; // optimal transfer block size
  245. req[2] = Math.floor(space/req[1]); // free blocks
  246. req[3] = req[2] - Math.floor(size/req[1]); // free blocks in fs
  247. req[4] = req[2] - Math.floor(size/req[1]); // free blocks avail to non-superuser
  248. req[5] = this.fs.CountUsedInodes(); // total number of inodes
  249. req[6] = this.fs.CountFreeInodes();
  250. req[7] = 0; // file system id?
  251. req[8] = 256; // maximum length of filenames
  252. size = marshall.Marshall(["w", "w", "d", "d", "d", "d", "d", "d", "w"], req, this.replybuffer, 7);
  253. this.BuildReply(id, tag, size);
  254. this.SendReply(bufchain);
  255. break;
  256. case 112: // topen
  257. case 12: // tlopen
  258. var req = marshall.Unmarshall(["w", "w"], buffer, state);
  259. var fid = req[0];
  260. var mode = req[1];
  261. message.Debug("[open] fid=" + fid + ", mode=" + mode);
  262. var idx = this.fids[fid].inodeid;
  263. var inode = this.fs.GetInode(idx);
  264. message.Debug("file open " + this.fids[fid].dbg_name);
  265. //if (inode.status === STATUS_LOADING) return;
  266. var ret = this.fs.OpenInode(idx, mode);
  267. this.fs.AddEvent(this.fids[fid].inodeid,
  268. function() {
  269. message.Debug("file opened " + this.fids[fid].dbg_name + " tag:"+tag);
  270. var req = [];
  271. req[0] = inode.qid;
  272. req[1] = this.msize - 24;
  273. marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
  274. this.BuildReply(id, tag, 13+4);
  275. this.SendReply(bufchain);
  276. }.bind(this)
  277. );
  278. break;
  279. case 70: // link
  280. var req = marshall.Unmarshall(["w", "w", "s"], buffer, state);
  281. var dfid = req[0];
  282. var fid = req[1];
  283. var name = req[2];
  284. message.Debug("[link] dfid=" + dfid + ", name=" + name);
  285. var ret = this.fs.Link(this.fids[dfid].inodeid, this.fids[fid].inodeid, name);
  286. if(ret < 0)
  287. {
  288. let error_message = "";
  289. if(ret === -EPERM) error_message = "Operation not permitted";
  290. else
  291. {
  292. error_message = "Unknown error: " + (-ret);
  293. dbg_assert(false, "[link]: Unexpected error code: " + (-ret));
  294. }
  295. this.SendError(tag, error_message, -ret);
  296. this.SendReply(bufchain);
  297. break;
  298. }
  299. this.BuildReply(id, tag, 0);
  300. this.SendReply(bufchain);
  301. break;
  302. case 16: // symlink
  303. var req = marshall.Unmarshall(["w", "s", "s", "w"], buffer, state);
  304. var fid = req[0];
  305. var name = req[1];
  306. var symgt = req[2];
  307. var gid = req[3];
  308. message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
  309. var idx = this.fs.CreateSymlink(name, this.fids[fid].inodeid, symgt);
  310. var inode = this.fs.GetInode(idx);
  311. inode.uid = this.fids[fid].uid;
  312. inode.gid = gid;
  313. marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
  314. this.BuildReply(id, tag, 13);
  315. this.SendReply(bufchain);
  316. break;
  317. case 18: // mknod
  318. var req = marshall.Unmarshall(["w", "s", "w", "w", "w", "w"], buffer, state);
  319. var fid = req[0];
  320. var name = req[1];
  321. var mode = req[2];
  322. var major = req[3];
  323. var minor = req[4];
  324. var gid = req[5];
  325. message.Debug("[mknod] fid=" + fid + ", name=" + name + ", major=" + major + ", minor=" + minor+ "");
  326. var idx = this.fs.CreateNode(name, this.fids[fid].inodeid, major, minor);
  327. var inode = this.fs.GetInode(idx);
  328. inode.mode = mode;
  329. //inode.mode = mode | S_IFCHR; // XXX: fails "Mknod - fifo" test
  330. inode.uid = this.fids[fid].uid;
  331. inode.gid = gid;
  332. marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
  333. this.BuildReply(id, tag, 13);
  334. this.SendReply(bufchain);
  335. break;
  336. case 22: // TREADLINK
  337. var req = marshall.Unmarshall(["w"], buffer, state);
  338. var fid = req[0];
  339. var inode = this.fs.GetInode(this.fids[fid].inodeid);
  340. message.Debug("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink);
  341. size = marshall.Marshall(["s"], [inode.symlink], this.replybuffer, 7);
  342. this.BuildReply(id, tag, size);
  343. this.SendReply(bufchain);
  344. break;
  345. case 72: // tmkdir
  346. var req = marshall.Unmarshall(["w", "s", "w", "w"], buffer, state);
  347. var fid = req[0];
  348. var name = req[1];
  349. var mode = req[2];
  350. var gid = req[3];
  351. message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
  352. var idx = this.fs.CreateDirectory(name, this.fids[fid].inodeid);
  353. var inode = this.fs.GetInode(idx);
  354. inode.mode = mode | S_IFDIR;
  355. inode.uid = this.fids[fid].uid;
  356. inode.gid = gid;
  357. marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
  358. this.BuildReply(id, tag, 13);
  359. this.SendReply(bufchain);
  360. break;
  361. case 14: // tlcreate
  362. var req = marshall.Unmarshall(["w", "s", "w", "w", "w"], buffer, state);
  363. var fid = req[0];
  364. var name = req[1];
  365. var flags = req[2];
  366. var mode = req[3];
  367. var gid = req[4];
  368. this.bus.send("9p-create", [name, this.fids[fid].inodeid]);
  369. message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
  370. var idx = this.fs.CreateFile(name, this.fids[fid].inodeid);
  371. this.fids[fid].inodeid = idx;
  372. this.fids[fid].type = FID_INODE;
  373. this.fids[fid].dbg_name = name;
  374. var inode = this.fs.GetInode(idx);
  375. inode.uid = this.fids[fid].uid;
  376. inode.gid = gid;
  377. inode.mode = mode | S_IFREG;
  378. marshall.Marshall(["Q", "w"], [inode.qid, this.msize - 24], this.replybuffer, 7);
  379. this.BuildReply(id, tag, 13+4);
  380. this.SendReply(bufchain);
  381. break;
  382. case 52: // lock
  383. var req = marshall.Unmarshall(["w", "b", "w", "d", "d", "w", "s"], buffer, state);
  384. var fid = req[0];
  385. var flags = req[2];
  386. var lock_length = req[4] === 0 ? Infinity : req[4];
  387. var lock_request = this.fs.DescribeLock(req[1], req[3], lock_length, req[5], req[6]);
  388. message.Debug("[lock] fid=" + fid +
  389. ", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
  390. ", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
  391. var ret = this.fs.Lock(this.fids[fid].inodeid, lock_request, flags);
  392. marshall.Marshall(["b"], [ret], this.replybuffer, 7);
  393. this.BuildReply(id, tag, 1);
  394. this.SendReply(bufchain);
  395. break;
  396. case 54: // getlock
  397. var req = marshall.Unmarshall(["w", "b", "d", "d", "w", "s"], buffer, state);
  398. var fid = req[0];
  399. var lock_length = req[3] === 0 ? Infinity : req[3];
  400. var lock_request = this.fs.DescribeLock(req[1], req[2], lock_length, req[4], req[5]);
  401. message.Debug("[getlock] fid=" + fid +
  402. ", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
  403. ", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
  404. var ret = this.fs.GetLock(this.fids[fid].inodeid, lock_request);
  405. if(!ret)
  406. {
  407. ret = lock_request;
  408. ret.type = P9_LOCK_TYPE_UNLCK;
  409. }
  410. var ret_length = ret.length === Infinity ? 0 : ret.length;
  411. size = marshall.Marshall(["b", "d", "d", "w", "s"],
  412. [ret.type, ret.start, ret_length, ret.proc_id, ret.client_id],
  413. this.replybuffer, 7);
  414. this.BuildReply(id, tag, size);
  415. this.SendReply(bufchain);
  416. break;
  417. case 24: // getattr
  418. var req = marshall.Unmarshall(["w", "d"], buffer, state);
  419. var fid = req[0];
  420. var inode = this.fs.GetInode(this.fids[fid].inodeid);
  421. message.Debug("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1]);
  422. if(!inode || inode.status === STATUS_UNLINKED)
  423. {
  424. message.Debug("getattr: unlinked");
  425. this.SendError(tag, "No such file or directory", ENOENT);
  426. this.SendReply(bufchain);
  427. break;
  428. }
  429. req[0] = req[1]; // request mask
  430. req[1] = inode.qid;
  431. req[2] = inode.mode;
  432. req[3] = inode.uid; // user id
  433. req[4] = inode.gid; // group id
  434. req[5] = inode.nlinks; // number of hard links
  435. req[6] = (inode.major<<8) | (inode.minor); // device id low
  436. req[7] = inode.size; // size low
  437. req[8] = this.BLOCKSIZE;
  438. req[9] = Math.floor(inode.size/512+1); // blk size low
  439. req[10] = inode.atime; // atime
  440. req[11] = 0x0;
  441. req[12] = inode.mtime; // mtime
  442. req[13] = 0x0;
  443. req[14] = inode.ctime; // ctime
  444. req[15] = 0x0;
  445. req[16] = 0x0; // btime
  446. req[17] = 0x0;
  447. req[18] = 0x0; // st_gen
  448. req[19] = 0x0; // data_version
  449. marshall.Marshall([
  450. "d", "Q",
  451. "w",
  452. "w", "w",
  453. "d", "d",
  454. "d", "d", "d",
  455. "d", "d", // atime
  456. "d", "d", // mtime
  457. "d", "d", // ctime
  458. "d", "d", // btime
  459. "d", "d",
  460. ], req, this.replybuffer, 7);
  461. this.BuildReply(id, tag, 8 + 13 + 4 + 4+ 4 + 8*15);
  462. this.SendReply(bufchain);
  463. break;
  464. case 26: // setattr
  465. var req = marshall.Unmarshall(["w", "w",
  466. "w", // mode
  467. "w", "w", // uid, gid
  468. "d", // size
  469. "d", "d", // atime
  470. "d", "d", // mtime
  471. ], buffer, state);
  472. var fid = req[0];
  473. var inode = this.fs.GetInode(this.fids[fid].inodeid);
  474. message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name);
  475. if(req[1] & P9_SETATTR_MODE) {
  476. // XXX: check mode (S_IFREG or S_IFDIR or similar should be set)
  477. inode.mode = req[2];
  478. }
  479. if(req[1] & P9_SETATTR_UID) {
  480. inode.uid = req[3];
  481. }
  482. if(req[1] & P9_SETATTR_GID) {
  483. inode.gid = req[4];
  484. }
  485. if(req[1] & P9_SETATTR_ATIME) {
  486. inode.atime = Math.floor((new Date()).getTime()/1000);
  487. }
  488. if(req[1] & P9_SETATTR_MTIME) {
  489. inode.mtime = Math.floor((new Date()).getTime()/1000);
  490. }
  491. if(req[1] & P9_SETATTR_CTIME) {
  492. inode.ctime = Math.floor((new Date()).getTime()/1000);
  493. }
  494. if(req[1] & P9_SETATTR_ATIME_SET) {
  495. inode.atime = req[6];
  496. }
  497. if(req[1] & P9_SETATTR_MTIME_SET) {
  498. inode.mtime = req[8];
  499. }
  500. if(req[1] & P9_SETATTR_SIZE) {
  501. await this.fs.ChangeSize(this.fids[fid].inodeid, req[5]);
  502. }
  503. this.BuildReply(id, tag, 0);
  504. this.SendReply(bufchain);
  505. break;
  506. case 50: // fsync
  507. var req = marshall.Unmarshall(["w", "d"], buffer, state);
  508. var fid = req[0];
  509. this.BuildReply(id, tag, 0);
  510. this.SendReply(bufchain);
  511. break;
  512. case 40: // TREADDIR
  513. case 116: // read
  514. var req = marshall.Unmarshall(["w", "d", "w"], buffer, state);
  515. var fid = req[0];
  516. var offset = req[1];
  517. var count = req[2];
  518. var inode = this.fs.GetInode(this.fids[fid].inodeid);
  519. if(id === 40) message.Debug("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
  520. if(id === 116) message.Debug("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
  521. if(!inode || inode.status === STATUS_UNLINKED)
  522. {
  523. message.Debug("read/treaddir: unlinked");
  524. this.SendError(tag, "No such file or directory", ENOENT);
  525. this.SendReply(bufchain);
  526. break;
  527. }
  528. if(this.fids[fid].type === FID_XATTR) {
  529. if(inode.caps.length < offset+count) count = inode.caps.length - offset;
  530. for(var i=0; i<count; i++)
  531. this.replybuffer[7+4+i] = inode.caps[offset+i];
  532. marshall.Marshall(["w"], [count], this.replybuffer, 7);
  533. this.BuildReply(id, tag, 4 + count);
  534. this.SendReply(bufchain);
  535. } else {
  536. this.fs.OpenInode(this.fids[fid].inodeid, undefined);
  537. const inodeid = this.fids[fid].inodeid;
  538. count = Math.min(count, this.replybuffer.length - (7 + 4));
  539. if(inode.size < offset+count) count = inode.size - offset;
  540. else if(id === 40)
  541. {
  542. // for directories, return whole number of dir-entries.
  543. count = this.fs.RoundToDirentry(inodeid, offset + count) - offset;
  544. }
  545. if(offset > inode.size)
  546. {
  547. // offset can be greater than available - should return count of zero.
  548. // See http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor30
  549. count = 0;
  550. }
  551. this.bus.send("9p-read-start", [this.fids[fid].dbg_name]);
  552. const data = await this.fs.Read(inodeid, offset, count);
  553. this.bus.send("9p-read-end", [this.fids[fid].dbg_name, count]);
  554. if(data) {
  555. this.replybuffer.set(data, 7 + 4);
  556. }
  557. marshall.Marshall(["w"], [count], this.replybuffer, 7);
  558. this.BuildReply(id, tag, 4 + count);
  559. this.SendReply(bufchain);
  560. }
  561. break;
  562. case 118: // write
  563. var req = marshall.Unmarshall(["w", "d", "w"], buffer, state);
  564. var fid = req[0];
  565. var offset = req[1];
  566. var count = req[2];
  567. const filename = this.fids[fid].dbg_name;
  568. message.Debug("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
  569. if(this.fids[fid].type === FID_XATTR)
  570. {
  571. // XXX: xattr not supported yet. Ignore write.
  572. this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
  573. this.SendReply(bufchain);
  574. break;
  575. }
  576. else
  577. {
  578. // XXX: Size of the subarray is unchecked
  579. await this.fs.Write(this.fids[fid].inodeid, offset, count, buffer.subarray(state.offset));
  580. }
  581. this.bus.send("9p-write-end", [filename, count]);
  582. marshall.Marshall(["w"], [count], this.replybuffer, 7);
  583. this.BuildReply(id, tag, 4);
  584. this.SendReply(bufchain);
  585. break;
  586. case 74: // RENAMEAT
  587. var req = marshall.Unmarshall(["w", "s", "w", "s"], buffer, state);
  588. var olddirfid = req[0];
  589. var oldname = req[1];
  590. var newdirfid = req[2];
  591. var newname = req[3];
  592. message.Debug("[renameat]: oldname=" + oldname + " newname=" + newname);
  593. var ret = await this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
  594. if(ret < 0) {
  595. let error_message = "";
  596. if(ret === -ENOENT) error_message = "No such file or directory";
  597. else if(ret === -EPERM) error_message = "Operation not permitted";
  598. else if(ret === -ENOTEMPTY) error_message = "Directory not empty";
  599. else
  600. {
  601. error_message = "Unknown error: " + (-ret);
  602. dbg_assert(false, "[renameat]: Unexpected error code: " + (-ret));
  603. }
  604. this.SendError(tag, error_message, -ret);
  605. this.SendReply(bufchain);
  606. break;
  607. }
  608. if(TRACK_FILENAMES)
  609. {
  610. const newidx = this.fs.Search(this.fids[newdirfid].inodeid, newname);
  611. this.update_dbg_name(newidx, newname);
  612. }
  613. this.BuildReply(id, tag, 0);
  614. this.SendReply(bufchain);
  615. break;
  616. case 76: // TUNLINKAT
  617. var req = marshall.Unmarshall(["w", "s", "w"], buffer, state);
  618. var dirfd = req[0];
  619. var name = req[1];
  620. var flags = req[2];
  621. message.Debug("[unlink]: dirfd=" + dirfd + " name=" + name + " flags=" + flags);
  622. var fid = this.fs.Search(this.fids[dirfd].inodeid, name);
  623. if(fid === -1) {
  624. this.SendError(tag, "No such file or directory", ENOENT);
  625. this.SendReply(bufchain);
  626. break;
  627. }
  628. var ret = this.fs.Unlink(this.fids[dirfd].inodeid, name);
  629. if(ret < 0) {
  630. let error_message = "";
  631. if(ret === -ENOTEMPTY) error_message = "Directory not empty";
  632. else if(ret === -EPERM) error_message = "Operation not permitted";
  633. else
  634. {
  635. error_message = "Unknown error: " + (-ret);
  636. dbg_assert(false, "[unlink]: Unexpected error code: " + (-ret));
  637. }
  638. this.SendError(tag, error_message, -ret);
  639. this.SendReply(bufchain);
  640. break;
  641. }
  642. this.BuildReply(id, tag, 0);
  643. this.SendReply(bufchain);
  644. break;
  645. case 100: // version
  646. var version = marshall.Unmarshall(["w", "s"], buffer, state);
  647. message.Debug("[version]: msize=" + version[0] + " version=" + version[1]);
  648. if(this.msize !== version[0])
  649. {
  650. this.msize = version[0];
  651. this.replybuffer = new Uint8Array(Math.min(MAX_REPLYBUFFER_SIZE, this.msize*2));
  652. }
  653. size = marshall.Marshall(["w", "s"], [this.msize, this.VERSION], this.replybuffer, 7);
  654. this.BuildReply(id, tag, size);
  655. this.SendReply(bufchain);
  656. break;
  657. case 104: // attach
  658. // return root directorie's QID
  659. var req = marshall.Unmarshall(["w", "w", "s", "s", "w"], buffer, state);
  660. var fid = req[0];
  661. var uid = req[4];
  662. message.Debug("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
  663. this.fids[fid] = this.Createfid(0, FID_INODE, uid, "");
  664. var inode = this.fs.GetInode(this.fids[fid].inodeid);
  665. marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
  666. this.BuildReply(id, tag, 13);
  667. this.SendReply(bufchain);
  668. this.bus.send("9p-attach");
  669. break;
  670. case 108: // tflush
  671. var req = marshall.Unmarshall(["h"], buffer, state);
  672. var oldtag = req[0];
  673. message.Debug("[flush] " + tag);
  674. //marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
  675. this.BuildReply(id, tag, 0);
  676. this.SendReply(bufchain);
  677. break;
  678. case 110: // walk
  679. var req = marshall.Unmarshall(["w", "w", "h"], buffer, state);
  680. var fid = req[0];
  681. var nwfid = req[1];
  682. var nwname = req[2];
  683. message.Debug("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
  684. if(nwname === 0) {
  685. this.fids[nwfid] = this.Createfid(this.fids[fid].inodeid, FID_INODE, this.fids[fid].uid, this.fids[fid].dbg_name);
  686. //this.fids[nwfid].inodeid = this.fids[fid].inodeid;
  687. marshall.Marshall(["h"], [0], this.replybuffer, 7);
  688. this.BuildReply(id, tag, 2);
  689. this.SendReply(bufchain);
  690. break;
  691. }
  692. var wnames = [];
  693. for(var i=0; i<nwname; i++) {
  694. wnames.push("s");
  695. }
  696. var walk = marshall.Unmarshall(wnames, buffer, state);
  697. var idx = this.fids[fid].inodeid;
  698. var offset = 7+2;
  699. var nwidx = 0;
  700. //console.log(idx, this.fs.GetInode(idx));
  701. message.Debug("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString());
  702. for(var i=0; i<nwname; i++) {
  703. idx = this.fs.Search(idx, walk[i]);
  704. if(idx === -1) {
  705. message.Debug("Could not find: " + walk[i]);
  706. break;
  707. }
  708. offset += marshall.Marshall(["Q"], [this.fs.GetInode(idx).qid], this.replybuffer, offset);
  709. nwidx++;
  710. //message.Debug(this.fids[nwfid].inodeid);
  711. //this.fids[nwfid].inodeid = idx;
  712. //this.fids[nwfid].type = FID_INODE;
  713. this.fids[nwfid] = this.Createfid(idx, FID_INODE, this.fids[fid].uid, walk[i]);
  714. }
  715. marshall.Marshall(["h"], [nwidx], this.replybuffer, 7);
  716. this.BuildReply(id, tag, offset-7);
  717. this.SendReply(bufchain);
  718. break;
  719. case 120: // clunk
  720. var req = marshall.Unmarshall(["w"], buffer, state);
  721. message.Debug("[clunk]: fid=" + req[0]);
  722. if(this.fids[req[0]] && this.fids[req[0]].inodeid >= 0) {
  723. await this.fs.CloseInode(this.fids[req[0]].inodeid);
  724. this.fids[req[0]].inodeid = -1;
  725. this.fids[req[0]].type = FID_NONE;
  726. }
  727. this.BuildReply(id, tag, 0);
  728. this.SendReply(bufchain);
  729. break;
  730. case 32: // txattrcreate
  731. var req = marshall.Unmarshall(["w", "s", "d", "w"], buffer, state);
  732. var fid = req[0];
  733. var name = req[1];
  734. var attr_size = req[2];
  735. var flags = req[3];
  736. message.Debug("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags);
  737. // XXX: xattr not supported yet. E.g. checks corresponding to the flags needed.
  738. this.fids[fid].type = FID_XATTR;
  739. this.BuildReply(id, tag, 0);
  740. this.SendReply(bufchain);
  741. //this.SendError(tag, "Operation i not supported", EINVAL);
  742. //this.SendReply(bufchain);
  743. break;
  744. case 30: // xattrwalk
  745. var req = marshall.Unmarshall(["w", "w", "s"], buffer, state);
  746. var fid = req[0];
  747. var newfid = req[1];
  748. var name = req[2];
  749. message.Debug("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
  750. // Workaround for Linux restarts writes until full blocksize
  751. this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
  752. this.SendReply(bufchain);
  753. /*
  754. this.fids[newfid] = this.Createfid(this.fids[fid].inodeid, FID_NONE, this.fids[fid].uid, this.fids[fid].dbg_name);
  755. //this.fids[newfid].inodeid = this.fids[fid].inodeid;
  756. //this.fids[newfid].type = FID_NONE;
  757. var length = 0;
  758. if (name === "security.capability") {
  759. length = this.fs.PrepareCAPs(this.fids[fid].inodeid);
  760. this.fids[newfid].type = FID_XATTR;
  761. }
  762. marshall.Marshall(["d"], [length], this.replybuffer, 7);
  763. this.BuildReply(id, tag, 8);
  764. this.SendReply(bufchain);
  765. */
  766. break;
  767. default:
  768. message.Debug("Error in Virtio9p: Unknown id " + id + " received");
  769. message.Abort();
  770. //this.SendError(tag, "Operation i not supported", EOPNOTSUPP);
  771. //this.SendReply(bufchain);
  772. break;
  773. }
  774. //consistency checks if there are problems with the filesystem
  775. //this.fs.Check();
  776. };