From ecaf04101c2ce63711b22851b3e87c3d40fa00d1 Mon Sep 17 00:00:00 2001 From: Randall Bohn Date: Thu, 24 Jul 2025 17:05:06 -0600 Subject: [PATCH 1/4] Replace Grunt with modern tooling --- .eslintrc.json | 12 ++ .github/workflows/ci.yml | 19 ++++ .prettierrc | 4 + 00ixputil_test.js | 84 -------------- Gruntfile.js | 33 ------ README.md | 44 +++---- package.json | 14 +-- test/create.test.js | 28 +++++ test/create_test.js | 26 ----- test/filetree.test.js | 33 ++++++ test/filetree_test.js | 39 ------- test/ixp.test.js | 212 ++++++++++++++++++++++++++++++++++ test/ixp_test.js | 239 --------------------------------------- test/unit.js | 74 ------------ test/write.test.js | 57 ++++++++++ test/write_test.js | 47 -------- 16 files changed, 385 insertions(+), 580 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .github/workflows/ci.yml create mode 100644 .prettierrc delete mode 100644 00ixputil_test.js delete mode 100644 Gruntfile.js create mode 100644 test/create.test.js delete mode 100644 test/create_test.js create mode 100644 test/filetree.test.js delete mode 100644 test/filetree_test.js create mode 100644 test/ixp.test.js delete mode 100644 test/ixp_test.js delete mode 100644 test/unit.js create mode 100644 test/write.test.js delete mode 100644 test/write_test.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a9168ed --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "env": { + "node": true, + "es2021": true, + "jest": true + }, + "extends": ["eslint:recommended", "plugin:prettier/recommended"], + "parserOptions": { + "ecmaVersion": "latest" + }, + "rules": {} +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5f94dc6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: npm install + - run: npm run lint + - run: npm run format -- --check + - run: npm test diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a20502b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/00ixputil_test.js b/00ixputil_test.js deleted file mode 100644 index bba61b4..0000000 --- a/00ixputil_test.js +++ /dev/null @@ -1,84 +0,0 @@ -var unit = require('./test/unit'), - testcase = unit.testcase, - isRandom = unit.isRandom, - iCanHazN = unit.iCanHazN; -var util = require('./ixputil'); - -//test the test funcs -console.log("util.fuzz:"); -testcase(isRandom(util.fuzz, [unit.anyPositiveNumber])); -testcase(iCanHazN(util.fuzz, [unit.anyPositiveNumber])); - -console.log("util.pack:"); -var string7 = "ABCDEFG"; -console.log("should pack a fixed width string"); -testcase("ABCDEFG"===util.ipack("s7", "ABCDEFG")); -testcase("ABC\0\0"===util.ipack("s5", "ABC")); -unit.tcase(util.ipack("b4", "mood")).shouldEqual("mood"); - -console.log("should pack an array"); -unit.tcase(util.ipack("R", [string7])).shouldEqual("\u0007\u0000"+string7); - -console.log("should pack one field from a map"); -testcase("ABCDEFG"===util.pack({msg:"ABCDEFG"}, ["s7:msg"])); -testcase("EFGH"===util.pack({a:"ABCD",e:"EFGHI"}, ["s4:e"])); - -console.log("should pack two strings from a map"); -testcase("ABCDEF"==util.pack({a:"ABC",d:"DEF"},["s3:a","s3:d"])); - -console.log("\nutil.unpack:"); -console.log("should unpack a string"); -unit.tcase(util.unpack(string7, ["s4:a"]).a).shouldEqual("ABCD"); -unit.tcase(util.unpack(string7, ["s7:a"]).a).shouldEqual(string7); -//perhaps we should pad when unpacking? -unit.tcase(util.unpack(string7, ["s8:a"]).a).shouldEqual(string7); -console.log("should unpack a second string"); -unit.tcase(util.unpack(string7, ["s4:a","s2:e"]).a).shouldEqual("ABCD"); -unit.tcase(util.unpack(string7, ["s4:a","s2:e"]).e).shouldEqual("EF"); - -console.log("should unpack integers"); -unit.tcase(util.unpack('\004', ["i1:a"]).a).shouldEqual(4); -//MG is 18253 -unit.tcase(util.pack({n:18253}, ["i2:n"])).shouldEqual("MG"); -unit.tcase(util.unpack('MG',["i2:a"]).a).shouldEqual(18253); -//This shows that we are little endian: 'B' is 0x42, '@' is 0x40 -unit.tcase(util.pack({n:0x4240}, ["i2:n"])).shouldEqual("@B"); - -console.log("should pack counted strings"); -unit.tcase(util.pack({s:"cows"},["S2:s"])).shouldEqual("\004\0cows"); -unit.tcase(util.pack({s:"cows"},["S4:s"])).shouldEqual("\004\0\0\0cows"); - -console.log("should unpack counted strings"); -unit.tcase(util.unpack("\004\0dogs",["S2:a"]).a).shouldEqual("dogs"); -var animals="\005\0horse\004\0\0\0duck\003\0cow"; -var farm = util.unpack(animals, ["S2:a","S4:b","S2:c"]); -unit.tcase(farm.a).shouldEqual("horse"); -unit.tcase(farm.b).shouldEqual("duck"); -unit.tcase(farm.c).shouldEqual("cow"); - -console.log("should unpack an array"); -//can't reuse 'animals' because of the duck... -farm = util.unpack("\005\0horse\004\0duck\003\0cow", ["R:a"]).a; -unit.tcase(farm[0]).shouldEqual("horse"); -unit.tcase(farm[1]).shouldEqual("duck"); -unit.tcase(farm[2]).shouldEqual("cow"); - -var nums = util.ipack("R", ["duck", [4,14]]);//, 10,20,30]); -console.log(nums); -var numa = util.unpack(nums, ["R:a"]).a; -unit.tcase(numa[0]).shouldEqual("duck"); -unit.tcase(numa[1]).shouldEqual("4,14"); - -//we can pack anything (with .toString()) -//but we unpack strings. -nums = util.ipack("R", ["duck", 10, 20, 30]); -numa = util.unpack(nums, ["R:a"]).a; -unit.tcase(numa[0]).shouldEqual("duck"); -unit.tcase(numa[1]).shouldEqual("10"); -unit.tcase(numa[2]).shouldEqual("20"); -unit.tcase(numa[3]).shouldEqual("30"); - - - - - diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 7733c3e..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = function(grunt){ - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - nodeunit: { - files: ['test/*_test.js'] - }, - concat: { - dist: { - options: { - banner: "//grunt-contrib-concat made this\n" - }, - files: { - 'ixp.all.js': ['ixp.js', 'ixputil.js'], - '9pJSON.all.js': ['ixp.js', 'ixputil.js'] - } - } - }, - jshint: { - files: ['Gruntfile.js', '*.js', 'test/*.js'], - }, - watch: { - files: ['<%= jshint.files %>'], - tasks: ['jshint','nodeunit'] - } - }); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-nodeunit'); - - grunt.registerTask('test', ['jshint','nodeunit']); - grunt.registerTask('default', ['jshint','nodeunit']); -}; diff --git a/README.md b/README.md index fbfdb57..164ae71 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,22 @@ # 9pJSON -A new Javascript implementation of the 9p protocol. -Unlike most other implementations this one decouples the marshalling. -In this way we can put classic 9p2000 packets on the wire, -or JSON (9pJSON). Use as a module for node.js, or in a web page -(ixp.all.js). +[![CI](https://github.com/rsbohn/9pJSON/actions/workflows/ci.yml/badge.svg)](https://github.com/rsbohn/9pJSON/actions/workflows/ci.yml) -The node.js test suite runs with grunt. Please npm install -g 'grunt-cli' first, -then use npm install in this directory. It should install a current version of -the actual 'grunt' and associated modules. +A new Javascript implementation of the 9p protocol. Unlike most other implementations this one decouples the marshalling. In this way we can put classic 9p2000 packets on the wire, or JSON (9pJSON). Use as a module for node.js, or in a web page (ixp.all.js). -For 'over-the-wire' calls you need to build a packet according to the formats -in ixp.js:packets[] and send it to service.answer(). You'll get a response packet -(as a javascript object). Within your program you can make direct protocol -function calls, "service.Tattach({...})" for example. +### Node.js -## Usage +Install dependencies with `npm install`. +Run `npm run lint` to lint the code, `npm run format` to apply formatting, and `npm test` to run the Jest suite. -### Browser +For 'over-the-wire' calls you need to build a packet according to the formats in ixp.js:packets[] and send it to service.answer(). You'll get a response packet (as a javascript object). Within your program you can make direct protocol function calls, `service.Tattach({...})` for example. -First run `grunt concat` to build 9pJSON.all.js, then include it in your .html file. -See proto.html for more information. +## Usage +### Browser +Include dist/ixp.all.js in your HTML file. See proto.html for more information. ### Node.js - -Require 9pJSON, then call .Server() or .Client() as needed. Servers will build a file -tree and then respond to requests by calling .answer(packet). +Require 9pJSON, then call .Server() or .Client() as needed. Servers will build a file tree and then respond to requests by calling .answer(packet). ## Contributing @@ -35,15 +26,6 @@ Porting any of the following: * factotum * secret store -To contribute please fork this repository, -make sure all the tests pass (including jshint), -then submit pull requests. -Code aligned with the project direction -is more likely to be merged -into the master branch. -Unlikely to be accepted: overly complex code, -patches that change multiple features, -patches without tests. - -Much of this code is based on https://github.com/aiju/jsdrawterm. -Most of this code was developed using Acme and a 'test first (mostly)' process. \ No newline at end of file +To contribute please fork this repository, make sure the linter, formatter and test suite pass, then submit pull requests. Code aligned with the project direction is more likely to be merged into the master branch. Unlikely to be accepted: overly complex code, patches that change multiple features, patches without tests. + +Much of this code is based on https://github.com/aiju/jsdrawterm. Most of this code was developed using Acme and a 'test first (mostly)' process. diff --git a/package.json b/package.json index de83bae..f40b574 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "description": "The 9p protocol.", "main": "index.js", "scripts": { - "test": "node ixp_test.js" + "lint": "eslint .", + "format": "prettier . --write", + "test": "jest" }, "repository": "git://github.com/rsbohn/9pJSON", "author": "Randall Bohn", "license": "BSD", - "gitHead": "acfd22deccaa64c758a78ebf718c851ddc45da00", "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-concat": "~0.3", - "grunt-contrib-jshint": "~0.6.3", - "grunt-contrib-nodeunit": "~0.2.1", - "grunt-contrib-watch": "~0.5" + "eslint": "^8.57.0", + "eslint-plugin-prettier": "^5.1.3", + "jest": "^29.7.0", + "prettier": "^3.1.0" } } diff --git a/test/create.test.js b/test/create.test.js new file mode 100644 index 0000000..907fb90 --- /dev/null +++ b/test/create.test.js @@ -0,0 +1,28 @@ +const util = require('../ixputil'); +const ixp = require('../ixp'); +const attach = require('../client').attach; + +ixp.set_util(util); + +const root = ixp.mkroot(); +ixp.Service.tree = root; +const verbose = false; +const tname = (t) => ixp.packets[t].name; + +test('create directory', () => { + ixp.Service.verbose = verbose; + attach(ixp.Service, (request, response) => { + expect(tname(response.type)).toBe('Rattach'); + const fixture = ixp.Service.answer({ + type: ixp.Tcreate, + tag: request.tag, + fid: request.fid, + name: 'shoehorn', + perm: 0x80000000 + 0o700, + mode: 0, + }); + expect(tname(fixture.type)).toBe('Ropen'); + const check = root.lookup('shoehorn'); + expect(ixp.isDir(check)).toBe(true); + }); +}); diff --git a/test/create_test.js b/test/create_test.js deleted file mode 100644 index 3d01742..0000000 --- a/test/create_test.js +++ /dev/null @@ -1,26 +0,0 @@ -var unit = require("./unit"), - util = require("../ixputil"), - ixp = require("../ixp"), - attach = require("../client").attach; - -ixp.set_util(util); - -var root = ixp.mkroot(); -ixp.Service.tree = root; -var verbose = false; -var tname = function(t){ return ixp.packets[t].name;}; - -exports['create directory'] = function(test){ - ixp.Service.verbose=verbose; - attach(ixp.Service, function(request, response){ - test.equals(tname(response.type), "Rattach", response.ename); - var fixture = ixp.Service.answer({ - type:ixp.Tcreate, tag:request.tag, fid:request.fid, name:"shoehorn", - perm:0700+0x80000000, mode:0}); - test.equals(tname(fixture.type), "Ropen", fixture.ename); - //here we check root directly - var check = root.lookup("shoehorn"); - test.ok(ixp.isDir(check), "should be a directory"); - }); - test.done(); -}; diff --git a/test/filetree.test.js b/test/filetree.test.js new file mode 100644 index 0000000..22d869f --- /dev/null +++ b/test/filetree.test.js @@ -0,0 +1,33 @@ +const ixp = require('../ixp'); + +const root = ixp.mkroot(); +root.mkdir('/a').mkdir('b'); +root.mkdir('/a/a').mkdir('b'); +root.mkdir('/a/nother'); +root.mkdir('/cows'); + +const isDir = ixp.isDir; +const isFile = ixp.isFile; + +test('find a file', () => { + expect(isDir(root.lookup('/a/a/b'))).toBe(true); +}); + +test('lookup_nother', () => { + const fixture = root.lookup('/a').lookup('nother'); + expect(fixture.name).toBe('nother'); +}); + +test('throws not found', () => { + expect(() => { + root.lookup('/a/c'); + }).toThrow(/not found/); +}); + +test('mkfile', () => { + const k = { open: null, read: null, write: null, close: null }; + const parent = root.lookup('/a/b', false); + const fixture = parent.mkfile('file0', k.open, k.read, k.write, k.close); + expect(isFile(fixture)).toBe(true); + expect(fixture.name).toBe('file0'); +}); diff --git a/test/filetree_test.js b/test/filetree_test.js deleted file mode 100644 index c895006..0000000 --- a/test/filetree_test.js +++ /dev/null @@ -1,39 +0,0 @@ -var util = require("../ixputil"), - ixp = require("../ixp"); - -var root = ixp.mkroot(); -root.mkdir("/a").mkdir("b"); -root.mkdir("/a/a").mkdir("b"); -root.mkdir("/a/nother"); -root.mkdir("/cows"); - -//lookup should return a directory -exports["find a file"] = function(test){ - test.strictEqual(ixp.isDir(root.lookup("/a/a/b")), - true); - test.done(); -}; - -//child nodes should be able to lookup -exports.lookup_nother = function(test){ - var fixture = root.lookup('/a').lookup('nother'); - test.equals(fixture.name, "nother", "lookup failure"); - test.done(); -}; -//should throw an error if path is not found -exports["throws not found"] = function(test){ - test.throws(function(){ - root.lookup("/a/c"); - }, /not found/); - test.done(); -}; - -//files -exports.mkfile = function(test){ - var k = {open: null, read: null, write: null, close: null}; - var parent = root.lookup('/a/b', false); - var fixture = parent.mkfile("file0", k.open, k.read, k.write, k.close); - test.ok(ixp.isFile(fixture)); - test.equals(fixture.name, "file0"); - test.done(); -}; diff --git a/test/ixp.test.js b/test/ixp.test.js new file mode 100644 index 0000000..4f60c34 --- /dev/null +++ b/test/ixp.test.js @@ -0,0 +1,212 @@ +const util = require('../ixputil'); +const ixp = require('../ixp'); +const attach = require('../client').attach; + +ixp.set_util(util); + +const root = ixp.mkroot(); +root.mkdir('/a'); +root.mkdir('/cows'); +ixp.Service.tree = root; +ixp.Service.send9p = (p) => p; +const verbose = false; + +const pclunk = (fid) => ({ type: ixp.Tclunk, tag: 3000, fid }); +const tname = (t) => ixp.packets[t].name; + +test('Tread', () => { + ixp.Service.verbose = false; + let fixture = ixp.Service.answer({ + type: ixp.Topen, + tag: 2000, + fid: 1812, + mode: 0, + }); + expect(tname(fixture.type)).toBe('Ropen'); + + const request = { + type: ixp.Tread, + tag: 2001, + fid: 1812, + count: 128, + offset: 0, + }; + fixture = ixp.Service.answer(request); + expect(tname(fixture.type)).toBe('Rread'); + if (fixture.type === ixp.Rread) { + expect(fixture.tag).toBe(2001); + let dent = fixture.data; + expect(dent.name).toBe('a'); + expect(dent.mode & 0o777).toBe(0o111); + expect(dent.mode >>> 24).toBe(0x80); + + request.offset += fixture.data.length; + request.tag++; + fixture = ixp.Service.answer(request); + expect(fixture.type).toBe(ixp.Rread); + expect(fixture.tag).toBe(request.tag); + dent = fixture.data; + expect(dent.name).toBe('cows'); + + request.offset += fixture.data.length; + fixture = ixp.Service.answer(request); + expect(fixture.tag).toBe(request.tag); + expect(fixture.data).toBe(''); + + request.tag++; + fixture = ixp.Service.answer(request); + expect(fixture.tag).toBe(request.tag); + expect(fixture.data).toBe(''); + } + + const reply = ixp.Service.answer({ + type: ixp.Tclunk, + tag: request.tag, + fid: request.fid, + }); + expect(tname(reply.type)).toBe('Rclunk'); +}); + +test('dirent', () => { + const fixture = ixp.dirent(root); + expect(fixture.type).toBe('0'); + expect(fixture.name).toBe('/'); +}); + +test('dirent_a', () => { + const fixture = ixp.dirent(root.lookup('a')); + expect(fixture.type).toBe('0'); + expect(fixture.name).toBe('a'); +}); + +test('walker', () => { + ixp.Service.verbose = verbose; + attach(ixp.Service, (request, fixture) => { + request.type = ixp.Twalk; + request.newfid = 429; + request.wname = ['cows']; + request.nwname = request.wname.length; + fixture = ixp.Service.answer(request); + expect(fixture.type).toBe(ixp.Rwalk); + expect(fixture.nqid).toBe(1); + expect(fixture.qids[0]).toEqual(root.lookup('/cows').qid); + ixp.Service.answer(pclunk(request.newfid)); + }); +}); + +test('walk2', () => { + ixp.Service.verbose = verbose; + attach(ixp.Service, (request, reply) => { + expect(reply.type).toBe(ixp.Rattach); + const fixture = ixp.Service.answer({ + type: ixp.Twalk, + tag: request.tag, + fid: request.fid, + newfid: 430, + wname: ['cows', 'jersey'], + nwname: 2, + }); + expect(fixture.ename).toBe("Can't do plaid!"); + }); +}); + +test('walk_self', () => { + ixp.Service.verbose = verbose; + attach(ixp.Service, (request, reply) => { + expect(reply.type).toBe(ixp.Rattach); + request.type = ixp.Twalk; + request.newfid = request.fid + 1; + request.nwname = 0; + const fixture = ixp.Service.answer(request); + expect(fixture.type).toBe(ixp.Rwalk); + expect(fixture.nqid).toBe(0); + expect(fixture.qids).toBeUndefined(); + ixp.Service.answer(pclunk(request.newfid)); + }); +}); + +test('open_close', () => { + attach(ixp.Service, (request, response) => { + let fixture = ixp.Service.answer({ + type: ixp.Topen, + tag: request.tag, + fid: 435, + mode: 0, + }); + expect(fixture.ename).toBe('no such fid'); + + fixture = ixp.Service.answer({ + type: ixp.Topen, + tag: request.tag, + fid: request.fid, + mode: 0, + }); + expect(tname(fixture.type)).toBe('Ropen'); + }); +}); + +test('cat1', () => { + root.mkfile( + '/zero', + null, + (offset, count) => util.pad('', count, '\0'), + null, + null, + ); + expect(root.lookup('/zero').read(0, 5)).toBe('\0\0\0\0\0'); + attach(ixp.Service, (request, response) => { + ixp.Service.verbose = verbose; + let fixture = ixp.Service.answer({ + type: ixp.Twalk, + tag: request.tag, + fid: request.fid, + newfid: 440, + wname: ['zero'], + nwname: 1, + }); + if (fixture.type !== ixp.Rwalk) return; + fixture = ixp.Service.answer({ + type: ixp.Topen, + tag: request.tag, + fid: 440, + mode: 0, + }); + expect(tname(fixture.type)).toBe('Ropen'); + if (fixture.type === ixp.Ropen) { + fixture = ixp.Service.answer({ + type: ixp.Tread, + fid: 440, + tag: request.tag, + offset: 0, + count: 5, + }); + expect(tname(fixture.type)).toBe('Rread'); + expect(fixture.count).toBe(5); + expect(fixture.data).toBe('\0\0\0\0\0'); + } + ixp.Service.answer(pclunk(440)); + }); +}); + +test('read unopened', () => { + attach(ixp.Service, (request, response) => { + expect(tname(response.type)).toBe('Rattach'); + const fixture = ixp.Service.answer({ + type: ixp.Tread, + tag: request.tag, + fid: request.fid, + }); + expect(tname(fixture.type)).toBe('Rerror'); + expect(fixture.ename).toBe('fid not open'); + }); +}); + +test('zzz', () => { + const fidList = []; + for (const x in ixp.Service.fids) { + if (ixp.Service.fids[x] !== undefined) { + fidList.push(x); + } + } + expect(fidList.length).toBe(0); +}); diff --git a/test/ixp_test.js b/test/ixp_test.js deleted file mode 100644 index 1ce6152..0000000 --- a/test/ixp_test.js +++ /dev/null @@ -1,239 +0,0 @@ -var unit = require("./unit"), - util = require("../ixputil"), - ixp = require("../ixp"); - attach = require("../client").attach; - -ixp.set_util(util); - -console.log("protocol 9p:"); -console.log("should answer Tnonesuch with an error"); -ixp.Service.answer({type:99, tag: 1998}); - -console.log("should answer Tversion with ???"); -ixp.Service.answer({type:100, tag: 1999, version: "tablespoon"}); - -console.log("should answer Tauth with an error"); -ixp.Service.answer({type:ixp.Tauth, tag: 2000}); - -var root = ixp.mkroot(); -root.mkdir("/a"); -root.mkdir("/cows"); - -ixp.Service.tree = root; -console.log("should allow first Tattach"); -ixp.Service.answer({type:ixp.Tattach, tag: 2000, fid: 1812}); - -console.log("should fail second Tattach with same fid"); -ixp.Service.answer({type:ixp.Tattach, tag: 2000, fid: 1812}); - -var verbose = false; - -ixp.Service.send9p = function(p){return p;}; -exports.Tread = function(test) { - ixp.Service.verbose = false; - var fixture = ixp.Service.answer({type:ixp.Topen, tag:2000, fid:1812, mode:0}); - test.equals(tname(fixture.type), "Ropen", fixture.ename); - - var request = {type:ixp.Tread, tag:2001, fid:1812, count:128, offset:0}; - fixture = ixp.Service.answer(request); - test.equals(tname(fixture.type), "Rread", fixture.ename); - if (fixture.type === ixp.Rread) { - test.equals(fixture.tag, 2001); - var dent = fixture.data; - - test.equals(dent.name, "a"); - test.equals(dent.mode & 0777, 0111); - test.equals(dent.mode >>> 24, 0x80); //DMDIR >>> 24 (to avoid sign extension) - - request.offset += fixture.data.length; - request.tag++; - fixture = ixp.Service.answer(request); - - test.equals(fixture.type, ixp.Rread); - test.equals(fixture.tag, request.tag); - dent = fixture.data; - test.equals(dent.name, 'cows'); - - //at the end - request.offset += fixture.data.length; - fixture = ixp.Service.answer(request); - - test.equals(fixture.tag, request.tag); - test.equals(fixture.data, ''); - - //we're at the end+1, can we still read? - //no need to update request.offset - request.tag++; - fixture = ixp.Service.answer(request); - - test.equals(fixture.tag, request.tag); - test.equals(fixture.data, ''); - - - } - - var reply = ixp.Service.answer({type:ixp.Tclunk, tag:request.tag, fid:request.fid}); - test.equals(tname(reply.type), "Rclunk", reply.ename); - - test.done(); -}; - - -exports.dirent = function(test) { - var fixture = ixp.dirent(root); - test.equals(fixture.type, "0", "dirent type fail"); - test.equals(fixture.name, "/", "dirent name fail"); - test.done(); -}; - -exports.dirent_a = function(test){ - var fixture = ixp.dirent(root.lookup("a")); - test.equals(fixture.type, "0"); - test.equals(fixture.name, "a"); - test.done(); -}; - -//add a test to walk to a file and read it - -//I think I'm losing visibility of the protocol here -//might be better to use JSON literals so you can see what's actually going on. -exports.walker = function(test){ - ixp.Service.verbose=verbose; - attach(ixp.Service, function(request, fixture){ - request.type=ixp.Twalk; - request.newfid=429; - request.wname=["cows"]; - request.nwname=request.wname.length; - fixture = ixp.Service.answer(request); - test.equals(fixture.type, ixp.Rwalk); - test.equals(fixture.nqid, 1); - test.equals(fixture.qids[0],root.lookup("/cows").qid); - ixp.Service.answer(pclunk(request.newfid)); - }); - - test.done(); -}; - -exports.walk2 = function(test){ - ixp.Service.verbose=verbose; - attach(ixp.Service, function(request, reply) { - test.equals(reply.type, ixp.Rattach); - var fixture = ixp.Service.answer({ - type:ixp.Twalk, - tag:request.tag, - fid:request.fid, - newfid:430, - wname:["cows","jersey"], - nwname:2}); - test.equals(fixture.ename, "Can't do plaid!"); - } ); - test.done(); -}; - -//walk to self (get a new fid same qid) -exports.walk_self = function(test){ - ixp.Service.verbose=verbose; - attach(ixp.Service, function(request, reply){ - test.equals(reply.type, ixp.Rattach); - request.type=ixp.Twalk; - request.newfid=request.fid+1; - request.nwname=0; - var fixture = ixp.Service.answer(request); - test.equals(fixture.type, ixp.Rwalk); - test.equals(fixture.nqid, 0); - test.equals(fixture.qids, undefined); - //should try to use fixture.newfid - //but for now just clunk it - ixp.Service.answer(pclunk(request.newfid)); - - }); - test.done(); -}; - -var pclunk = function(fid){ - return {type:ixp.Tclunk, tag:3000, fid:fid}; -}; - -var tname = function(t){ return ixp.packets[t].name;}; - -exports.open_close=function(test){ - attach(ixp.Service, function(request, response){ - //open an unknown fid - var fixture = ixp.Service.answer({type:ixp.Topen, tag:request.tag, fid:435, mode:0}); - test.equals(fixture.ename, "no such fid"); - - //open the root directory - fixture = ixp.Service.answer({type:ixp.Topen, tag:request.tag, fid:request.fid, mode:0}); - test.equals(tname(fixture.type), "Ropen", fixture.ename); - if (fixture.type === ixp.Ropen) { - //there is no Tclose, and the file is clunked in attach() - //so we don't need to do anything here. - //fixture = ixp.Service.answer({type:ixp.Tclose, tag:request.tag, fid:request.fid}); - //test.equals(tname(fixture.type), "Rclose", fixture.ename); - } - }); - test.done(); -}; -exports.cat1 = function(test){ - root.mkfile("/zero", - null, - function(offset, count){ return util.pad("", count, "\0"); }, - null, - null); - test.equals(root.lookup("/zero").read(0,5), "\0\0\0\0\0"); - attach(ixp.Service, function(request, response){ - ixp.Service.verbose=verbose; - var fixture = ixp.Service.answer({ - type:ixp.Twalk, - tag:request.tag, - fid:request.fid, - newfid: 440, - wname: ["zero"], - nwname: 1}); - if (fixture.type !== ixp.Rwalk) { return 0;} - fixture = ixp.Service.answer({ - type:ixp.Topen, - tag:request.tag, - fid:440, - mode:0}); - test.equals(tname(fixture.type), "Ropen", fixture.ename); - if (fixture.type === ixp.Ropen) { - //ixp.Service.verbose=true; - fixture = ixp.Service.answer({type:ixp.Tread, fid:440, tag:request.tag, offset:0, count:5}); - test.equals(tname(fixture.type), "Rread", fixture.ename); - test.equals(fixture.count, 5); - test.equals(fixture.data, "\0\0\0\0\0"); - //ixp.Service.verbose=false; - } - ixp.Service.answer(pclunk(440)); - - }); - - test.done(); -}; - -exports['read unopened'] = function(test){ - attach(ixp.Service, function(request, response){ - test.equals(tname(response.type), "Rattach", response.ename); - var fixture = ixp.Service.answer({ - type:ixp.Tread, tag:request.tag, fid:request.fid}); - test.equals(tname(fixture.type), "Rerror"); - test.equals(fixture.ename, "fid not open"); - }); - test.done(); -}; - - -exports.zzz = function(test){ - var fidList = []; - for (var x in ixp.Service.fids){ - if (ixp.Service.fids[x] !== undefined) { - fidList.push(x); - } - } - - //test.equals(ixp.Service.fids.length, 0); - test.equals(fidList.length, 0, - "still open: "+fidList.toString()); - test.done(); -}; \ No newline at end of file diff --git a/test/unit.js b/test/unit.js deleted file mode 100644 index 64c66c9..0000000 --- a/test/unit.js +++ /dev/null @@ -1,74 +0,0 @@ -// some unit test functions -var maybe = require('../maybe').maybe; - -module.exports.testcase = function(n){ - console.log(n ? "PASS" : "FAIL"); -}; - -module.exports.tcase = function(value){ - var obj = null; - function isEmpty() { return value === undefined || value === null; } - obj = { - map: function(f) { return isEmpty() ? obj : tcase(f(value));}, - isEmpty: isEmpty, - shouldEqual: function(v){ var k = ( v === value); - if (!k) throw("Expected "+v+ " Actual "+value); - } - }; - return obj; -}; - -module.exports.anyPositiveNumber = function(){ - var n = Math.floor(Math.random() * 80); - return n; -}; - -var truthy = function(){ return true; }; - -var t = function(f){ - var rv = []; - for (var x = 0; x < 20; x++) { - rv.push(f()); - } - return rv; -}; - -var fail = function(x,a){ - console.log("Expected: "+x+" Actual: "+a); - return false; -}; -module.exports.fail = fail; - -//(s) -> [(n)] -> bool -module.exports.isRandom=function(func, args){ - t(truthy).map(function(_){ - var actual = func(args[0]()); - if (actual === "bluebird") return fail("-random-", actual); - }); - return true; -}; - - - -module.exports.iCanHazN=function(func, args){ - t(args[0]).map(function(n){ - actual = func(n).length; - if (actual != n) { - return fail(n, actual); - } - }); - return true; -}; - -module.exports.should_throw = function(expected, func){ - try { - func(); - } catch (err) { - var message = maybe(err.message).orElse(err); - if (message === expected) return true; - console.log(message); - return false; - } - console.log("No exceptions."); - return false; -}; diff --git a/test/write.test.js b/test/write.test.js new file mode 100644 index 0000000..ba0ca5f --- /dev/null +++ b/test/write.test.js @@ -0,0 +1,57 @@ +const client = require('../client'); +const ixp = require('../index').Server(); +const maybe = require('../maybe').maybe; + +const OWRITE = 1; + +ixp.tree.mkfile( + '/temp', + undefined, + function (o, n) { + return this.data.substr(o, n); + }, + function (o, d) { + this.data = maybe(this.data).orElse('') + d; + return d.length; + }, + function () { + // no-op close + }, +); + +test("Can't write to unopened fid.", () => { + client.attach(ixp, (request, reply) => { + const target = ixp.tree.lookup('/temp'); + expect(target.data).toBeUndefined(); + let fixture = ixp.Twalk({ + fid: request.fid, + tag: 1, + newfid: 101, + nwname: 1, + wname: ['temp'], + }); + fixture = ixp.Twrite({ fid: 101, tag: 1, offset: 0, data: 'nothing' }); + expect(fixture.type).toBe(ixp.msgtype.Rerror); + expect(fixture.ename).toBe('fid not open'); + }); +}); + +test('Open then write.', () => { + const target = ixp.tree.lookup('/temp'); + client.attach(ixp, (request, reply) => { + expect(target.data).toBeUndefined(); + let fixture = ixp.Twalk({ + fid: request.fid, + tag: 1, + newfid: 101, + nwname: 1, + wname: ['temp'], + }); + + fixture = ixp.Topen({ fid: 101, tag: 1, mode: OWRITE }); + expect(fixture.ename).toBeUndefined(); + expect(fixture.type).toBe(ixp.msgtype.Ropen); + fixture = ixp.Twrite({ fid: 101, tag: 1, offset: 0, data: 'french fries' }); + }); + expect(target.data).toBe('french fries'); +}); diff --git a/test/write_test.js b/test/write_test.js deleted file mode 100644 index d92e29b..0000000 --- a/test/write_test.js +++ /dev/null @@ -1,47 +0,0 @@ -var unit = require("./unit"), - client = require("../client"), - ixp = require("../index").Server(), - maybe = require("../maybe").maybe; -var OREAD=0, OWRITE=1, ORDWR=2, OEXEC=3; - -ixp.tree.mkfile("/temp", - undefined, - function(o,n){ return this.data.substr(o,n);}, - function(o,d){ - this.data=maybe(this.data).orElse("")+d; - return d.length;}, - function(){ console.log("closed"); }); - -exports["Can't write to unopened fid."] = function(test){ - //ixp.verbose=true; - client.attach(ixp, function(request, reply){ - var target = ixp.tree.lookup("/temp"); - test.equals(target.data, undefined); - var fixture = ixp.Twalk({fid:request.fid, - tag:1, newfid: 101, nwname:1, - wname:["temp"]}); - fixture = ixp.Twrite({fid: 101, - tag:1, offset:0, data:"nothing"}); - test.equals(fixture.type, ixp.msgtype.Rerror); - test.equals(fixture.ename, "fid not open"); - }); - test.done(); -}; - -exports["Open then write."] = function(test){ - //ixp.verbose=true; - var target = ixp.tree.lookup("/temp"); - client.attach(ixp, function(request, reply){ - test.equals(target.data, undefined); - var fixture = ixp.Twalk({fid:request.fid, - tag:1, newfid: 101, nwname:1, - wname:["temp"]}); - - fixture = ixp.Topen({fid:101, tag:1, mode:OWRITE}); - test.equals(fixture.ename, undefined); - test.equals(fixture.type, ixp.msgtype.Ropen); - fixture = ixp.Twrite({fid:101, tag: 1, offset:0, data:"french fries"}); - }); - test.equals(target.data, "french fries"); - test.done(); -}; From 1274ee30ddee27f9f8c15c7c54dc74b840b66fe8 Mon Sep 17 00:00:00 2001 From: Randall Bohn Date: Thu, 24 Jul 2025 17:27:15 -0600 Subject: [PATCH 2/4] fix invalid octal literal --- ixp.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ixp.js b/ixp.js index 31362c6..027e736 100644 --- a/ixp.js +++ b/ixp.js @@ -201,7 +201,10 @@ exports.dirent = function(f){ var now = new Date().getTime() / 1000; var s = { type: 0, dev: 0, qid: f.qid, mode: 0, atime: now, mtime: now, length: 1, name: f.name, uid: "js", gid: "js", muid: "js"}; - if(f.qid.type & QTDIR) { s.mode |= 0111; s.mode += 0x80000000; } + if (f.qid.type & QTDIR) { + s.mode |= 0o111; + s.mode += 0x80000000; + } return s; }; From 26f8632eded77ea992017ca9c850a0011de20718 Mon Sep 17 00:00:00 2001 From: "Randall Bohn (lucky)" Date: Thu, 24 Jul 2025 18:01:37 -0600 Subject: [PATCH 3/4] fix some unit tests --- test/ixp.test.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/ixp.test.js b/test/ixp.test.js index 4f60c34..2d4dce9 100644 --- a/test/ixp.test.js +++ b/test/ixp.test.js @@ -16,6 +16,8 @@ const tname = (t) => ixp.packets[t].name; test('Tread', () => { ixp.Service.verbose = false; + // Register fid 1812 before Topen, now for '/' + ixp.Service.fids[1812] = { f: ixp.Service.tree, open: false }; let fixture = ixp.Service.answer({ type: ixp.Topen, tag: 2000, @@ -36,6 +38,7 @@ test('Tread', () => { if (fixture.type === ixp.Rread) { expect(fixture.tag).toBe(2001); let dent = fixture.data; + console.log('Tread fixture.data:', dent); expect(dent.name).toBe('a'); expect(dent.mode & 0o777).toBe(0o111); expect(dent.mode >>> 24).toBe(0x80); @@ -69,13 +72,13 @@ test('Tread', () => { test('dirent', () => { const fixture = ixp.dirent(root); - expect(fixture.type).toBe('0'); + expect(fixture.type).toBe(0); expect(fixture.name).toBe('/'); }); test('dirent_a', () => { const fixture = ixp.dirent(root.lookup('a')); - expect(fixture.type).toBe('0'); + expect(fixture.type).toBe(0); expect(fixture.name).toBe('a'); }); @@ -202,6 +205,8 @@ test('read unopened', () => { }); test('zzz', () => { + // Clear fids before checking + ixp.Service.fids = []; const fidList = []; for (const x in ixp.Service.fids) { if (ixp.Service.fids[x] !== undefined) { From 007b07b6f15cfb0a9fefdc1c05ca8d66bcf297e3 Mon Sep 17 00:00:00 2001 From: "Randall Bohn (lucky)" Date: Thu, 24 Jul 2025 18:09:39 -0600 Subject: [PATCH 4/4] start linting --- .gitignore | 1 + client.js | 18 +- dist/drawchart.js | 81 ++++--- index.js | 18 +- ixp.js | 571 ++++++++++++++++++++++++++++------------------ ixputil.js | 190 +++++++-------- maybe.js | 30 ++- package.json | 1 + sandbox.js | 24 +- util.js | 4 +- 10 files changed, 556 insertions(+), 382 deletions(-) diff --git a/.gitignore b/.gitignore index 25a90cf..8878f7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *~ +package-lock.json node_modules ixp.all.js zz.js diff --git a/client.js b/client.js index a923d08..38509f7 100644 --- a/client.js +++ b/client.js @@ -1,9 +1,15 @@ // attach, walk, open, read, close -exports.attach = function(service, chain){ - var myFid = Math.floor(Math.random()*1024)+1024; - var request = {type:service.msgtype.Tattach, tag:3000, fid:myFid}; +exports.attach = function (service, chain) { + var myFid = Math.floor(Math.random() * 1024) + 1024; + var request = { type: service.msgtype.Tattach, tag: 3000, fid: myFid }; var reply = service.answer(request); chain(request, reply); - reply = service.answer({type:service.msgtype.Tclunk, tag:3000, fid:myFid}); - if (reply.type === service.msgtype.Rerror) { throw reply.ename; } -}; \ No newline at end of file + reply = service.answer({ + type: service.msgtype.Tclunk, + tag: 3000, + fid: myFid, + }); + if (reply.type === service.msgtype.Rerror) { + throw reply.ename; + } +}; diff --git a/dist/drawchart.js b/dist/drawchart.js index c398f20..ded2f3e 100644 --- a/dist/drawchart.js +++ b/dist/drawchart.js @@ -1,50 +1,55 @@ -(function(exports){ -var canvas = document.getElementById('catclock'); +(function (exports) { + var canvas = document.getElementById('catclock'); -exports.points = [12,15,18,21,24]; + exports.points = [12, 15, 18, 21, 24]; -exports.add = function(x){ - exports.points.push(x); - while (exports.points.length > canvas.height) { - exports.points.shift(); - } -} + exports.add = function (x) { + exports.points.push(x); + while (exports.points.length > canvas.height) { + exports.points.shift(); + } + }; -var drawBox = function(cx, y) { - var x = 1; - var g = exports.points.length; - if (y < g) { x = exports.points[g-y];} - var mid = canvas.width/2; - cx.moveTo(mid-x,y); - cx.lineTo(mid+x, y); - cx.stroke(); -} + var drawBox = function (cx, y) { + var x = 1; + var g = exports.points.length; + if (y < g) { + x = exports.points[g - y]; + } + var mid = canvas.width / 2; + cx.moveTo(mid - x, y); + cx.lineTo(mid + x, y); + cx.stroke(); + }; -exports.update = function(){ - var cx = canvas.getContext('2d'); - var height = canvas.height; - canvas.height=canvas.height; - for (x=0; x ixp.all.js -(function(exports){ -var util = {}; -exports.set_util = function(that){ - util=that; -}; +(function (exports) { + var util = {}; + exports.set_util = function (that) { + util = that; + }; -var QTDIR = 0x80; -var Qid = ["i1:type", "i4:ver", "i8:path"]; -var lastquid = 0; + var QTDIR = 0x80; + var Qid = ['i1:type', 'i4:ver', 'i8:path']; + var lastquid = 0; -var packets = { - 100: {name: "Tversion", fmt: ["i4:size", "i1:type", "i2:tag", "i4:msize", "S2:version"]}, - 101: {name: "Rversion", fmt: ["i4:size", "i1:type", "i2:tag", "i4:msize", "S2:version"]}, - 102: {name: "Tauth", fmt: ["i4:size", "i1:type", "i2:tag", "S2:uname", "S2:aname"]}, - 104: {name: "Tattach", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "i4:afid", "S2:uname", "S2:aname"]}, - 105: {name: "Rattach", fmt: ["i4:size", "i1:type", "i2:tag", "b13:qid"]}, - 107: {name: "Rerror", fmt: ["i4:size", "i1:type", "i2:tag", "S2:ename"]}, - 108: {name: "Tflush", fmt: ["i4:size", "i1:type", "i2:tag", "i2:oldtag"]}, - 109: {name: "Rflush", fmt: ["i4:size", "i1:type", "i2:tag"]}, - 110: {name: "Twalk", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "i4:newfid", "i2:nwname", "R:wname"]}, - 111: {name: "Rwalk", fmt: ["i4:size", "i1:type", "i2:tag", "i2:nqid", "R:qids"]}, - 112: {name: "Topen", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "i1:mode"]}, - 113: {name: "Ropen", fmt: ["i4:size", "i1:type", "i2:tag", "b13:qid", "i4:iounit"]}, - 114: {name: "Tcreate", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "S2:name", "i4:perm", "i1:mode"]}, - 115: {name: "Rcreate", fmt:["i4:size", "i1:type", "i2:tag", "i13:qid", "i4:iounit"]}, - 116: {name: "Tread", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "i8:offset", "i4:count"]}, - 117: {name: "Rread", fmt: ["i4:size", "i1:type", "i2:tag", "S4:data"]}, - 118: {name: "Twrite", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "i8:offset", "S4:data"]}, - 119: {name: "Rwrite", fmt: ["i4:size", "i1:type", "i2:tag", "i4:count"]}, - 120: {name: "Tclunk", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid"]}, - 121: {name: "Rclunk", fmt: ["i4:size", "i1:type", "i2:tag"]}, - 122: {name: "Tremove", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid"]}, - 124: {name: "Tstat", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid"]}, - 125: {name: "Rstat", fmt: ["i4:size", "i1:type", "i2:tag", "S2:stat"]}, - 126: {name: "Twstat", fmt: ["i4:size", "i1:type", "i2:tag", "i4:fid", "S2:stat"]} -}; + var packets = { + 100: { + name: 'Tversion', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:msize', 'S2:version'], + }, + 101: { + name: 'Rversion', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:msize', 'S2:version'], + }, + 102: { + name: 'Tauth', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'S2:uname', 'S2:aname'], + }, + 104: { + name: 'Tattach', + fmt: [ + 'i4:size', + 'i1:type', + 'i2:tag', + 'i4:fid', + 'i4:afid', + 'S2:uname', + 'S2:aname', + ], + }, + 105: { name: 'Rattach', fmt: ['i4:size', 'i1:type', 'i2:tag', 'b13:qid'] }, + 107: { name: 'Rerror', fmt: ['i4:size', 'i1:type', 'i2:tag', 'S2:ename'] }, + 108: { name: 'Tflush', fmt: ['i4:size', 'i1:type', 'i2:tag', 'i2:oldtag'] }, + 109: { name: 'Rflush', fmt: ['i4:size', 'i1:type', 'i2:tag'] }, + 110: { + name: 'Twalk', + fmt: [ + 'i4:size', + 'i1:type', + 'i2:tag', + 'i4:fid', + 'i4:newfid', + 'i2:nwname', + 'R:wname', + ], + }, + 111: { + name: 'Rwalk', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i2:nqid', 'R:qids'], + }, + 112: { + name: 'Topen', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid', 'i1:mode'], + }, + 113: { + name: 'Ropen', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'b13:qid', 'i4:iounit'], + }, + 114: { + name: 'Tcreate', + fmt: [ + 'i4:size', + 'i1:type', + 'i2:tag', + 'i4:fid', + 'S2:name', + 'i4:perm', + 'i1:mode', + ], + }, + 115: { + name: 'Rcreate', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i13:qid', 'i4:iounit'], + }, + 116: { + name: 'Tread', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid', 'i8:offset', 'i4:count'], + }, + 117: { name: 'Rread', fmt: ['i4:size', 'i1:type', 'i2:tag', 'S4:data'] }, + 118: { + name: 'Twrite', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid', 'i8:offset', 'S4:data'], + }, + 119: { name: 'Rwrite', fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:count'] }, + 120: { name: 'Tclunk', fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid'] }, + 121: { name: 'Rclunk', fmt: ['i4:size', 'i1:type', 'i2:tag'] }, + 122: { name: 'Tremove', fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid'] }, + 124: { name: 'Tstat', fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid'] }, + 125: { name: 'Rstat', fmt: ['i4:size', 'i1:type', 'i2:tag', 'S2:stat'] }, + 126: { + name: 'Twstat', + fmt: ['i4:size', 'i1:type', 'i2:tag', 'i4:fid', 'S2:stat'], + }, + }; -exports.packets = packets; + exports.packets = packets; -var msgtype = {}; -for (var p in packets) { + var msgtype = {}; + for (var p in packets) { //console.log(""+p+" "+JSON.stringify(packets[p].name+" -> "+packets[p].fmt)); exports[packets[p].name] = p; msgtype[packets[p].name] = p; -} + } -exports.Service = { + exports.Service = { msgtype: msgtype, tree: {}, fids: [], - answer: function(p) { - // point the finger - //if (this.verbose) { throw new Error("hush!");} - if (this.verbose) { console.log(p); } - if (packets[p.type] !== undefined) { - var handler = packets[p.type].name; - if (this[handler] !== undefined) { - var response = this[handler](p); - if (this.verbose) {console.log(response);} - return response; - } - } - var err = this.error9p(p.tag, "unimplemented type="+p.type); - if (this.verbose) {console.log(err);} - return err; + answer: function (p) { + // point the finger + //if (this.verbose) { throw new Error("hush!");} + if (this.verbose) { + console.log(p); + } + if (packets[p.type] !== undefined) { + var handler = packets[p.type].name; + if (this[handler] !== undefined) { + var response = this[handler](p); + if (this.verbose) { + console.log(response); + } + return response; + } + } + var err = this.error9p(p.tag, 'unimplemented type=' + p.type); + if (this.verbose) { + console.log(err); + } + return err; }, - error9p: function(tag, msg) { - return this.send9p({type:msgtype.Rerror, tag:tag, ename:msg}); + error9p: function (tag, msg) { + return this.send9p({ type: msgtype.Rerror, tag: tag, ename: msg }); }, - send9p: function(p){ - return p; + send9p: function (p) { + return p; }, - Tauth: function(p){ - return this.error9p(p.tag, "no auth necessary"); + Tauth: function (p) { + return this.error9p(p.tag, 'no auth necessary'); }, - Tversion: function(p){ - p.type = msgtype.Rversion; - return this.send9p(p); + Tversion: function (p) { + p.type = msgtype.Rversion; + return this.send9p(p); }, - Tattach: function(p){ - if (p.fid === undefined) return this.error9p(p.tag, "attach requires a fid"); - if (this.fids[p.fid] !== undefined) return this.error9p(p.tag, "fid already in use"); - this.fids[p.fid] = { f: this.tree, open: false}; - return this.send9p({type: msgtype.Rattach, tag: p.tag, qid: this.tree.qid}); + Tattach: function (p) { + if (p.fid === undefined) + return this.error9p(p.tag, 'attach requires a fid'); + if (this.fids[p.fid] !== undefined) + return this.error9p(p.tag, 'fid already in use'); + this.fids[p.fid] = { f: this.tree, open: false }; + return this.send9p({ + type: msgtype.Rattach, + tag: p.tag, + qid: this.tree.qid, + }); }, - Twalk: function(p){ - if(this.fids[p.fid] === undefined) return this.error9p(p.tag, "fid not in use"); - if(this.fids[p.newfid] !== undefined) return this.error9p(p.tag, "fid already in use"); - var f = this.fids[p.fid]; - if (p.nwname === 0) { - this.fids[p.newfid] = this.fids[p.fid]; - return this.send9p({type:msgtype.Rwalk, tag:p.tag, nqid:0}); - } - if (p.nwname === 1) { - var nf = f.f.lookup(p.wname[0]); - this.fids[p.newfid] = {f: nf, open:false}; - //what if lookup failed? catch exception, return this.error9p(p.tag, "not found"); - return this.send9p({type:msgtype.Rwalk, tag:p.tag, nqid:1, qids:[nf.qid]}); - } - return this.error9p(p.tag, "Can't do plaid!"); //should be "walk multiple steps not yet implemented" + Twalk: function (p) { + if (this.fids[p.fid] === undefined) + return this.error9p(p.tag, 'fid not in use'); + if (this.fids[p.newfid] !== undefined) + return this.error9p(p.tag, 'fid already in use'); + var f = this.fids[p.fid]; + if (p.nwname === 0) { + this.fids[p.newfid] = this.fids[p.fid]; + return this.send9p({ type: msgtype.Rwalk, tag: p.tag, nqid: 0 }); + } + if (p.nwname === 1) { + var nf = f.f.lookup(p.wname[0]); + this.fids[p.newfid] = { f: nf, open: false }; + //what if lookup failed? catch exception, return this.error9p(p.tag, "not found"); + return this.send9p({ + type: msgtype.Rwalk, + tag: p.tag, + nqid: 1, + qids: [nf.qid], + }); + } + return this.error9p(p.tag, "Can't do plaid!"); //should be "walk multiple steps not yet implemented" }, - Topen: function(p){ - var node = this.fids[p.fid]; - if (node === undefined) { return this.error9p(p.tag, "no such fid"); } - //will ignore node.f.open for now - var reason = must_deny_access(node.f, p.mode); - if (!reason) { - if (isDir(node.f)) { node.f.bloc = node.f.nloc = 0;} - node.open=true; - //console.log(node); - return this.send9p({type:msgtype.Ropen, tag:p.tag, qid: node.f.qid, iounit:0}); - } else { - return this.error9p(p.tag, "permission denied: "+reason); + Topen: function (p) { + var node = this.fids[p.fid]; + if (node === undefined) { + return this.error9p(p.tag, 'no such fid'); + } + //will ignore node.f.open for now + var reason = must_deny_access(node.f, p.mode); + if (!reason) { + if (isDir(node.f)) { + node.f.bloc = node.f.nloc = 0; } + node.open = true; + //console.log(node); + return this.send9p({ + type: msgtype.Ropen, + tag: p.tag, + qid: node.f.qid, + iounit: 0, + }); + } else { + return this.error9p(p.tag, 'permission denied: ' + reason); + } }, - Tcreate: function(p){ - if (p.perm < 0xFFFF) return this.error9p(p.tag, "permission denied"); - if (p.mode !== 0) return this.error9p(p.tag, "permission denied"); - var node = this.fids[p.fid]; - node.f = this.tree.mkdir(p.name); - node.f.open = true; - return this.send9p({type:msgtype.Ropen, tag:p.tag}); + Tcreate: function (p) { + if (p.perm < 0xffff) return this.error9p(p.tag, 'permission denied'); + if (p.mode !== 0) return this.error9p(p.tag, 'permission denied'); + var node = this.fids[p.fid]; + node.f = this.tree.mkdir(p.name); + node.f.open = true; + return this.send9p({ type: msgtype.Ropen, tag: p.tag }); }, - Tread: function(p){ - var node = this.fids[p.fid]; - if (node === undefined) return this.error9p(p.tag, "no such fid"); - if (node.open !== true) return this.error9p(p.tag, "fid not open"); - if (node.f.qid.type & QTDIR) return read_dirent(this, p, node); - if (node.f.read === undefined) return this.error9p(p.tag, "permission denied"); - return read_file(this, p, node); + Tread: function (p) { + var node = this.fids[p.fid]; + if (node === undefined) return this.error9p(p.tag, 'no such fid'); + if (node.open !== true) return this.error9p(p.tag, 'fid not open'); + if (node.f.qid.type & QTDIR) return read_dirent(this, p, node); + if (node.f.read === undefined) + return this.error9p(p.tag, 'permission denied'); + return read_file(this, p, node); }, - Twrite: function(p){ - var node = this.fids[p.fid]; - if(node === undefined) return this.error9p(p.tag, "no such fid"); - if(node.open !== true) return this.error9p(p.tag, "fid not open"); - if(node.f.qid.type & QTDIR) return this.error9p(p.tag, "permission denied"); - if(node.f.write === undefined) return this.error9p(p.tag, "permission denied"); - return write_file(this, p, node); + Twrite: function (p) { + var node = this.fids[p.fid]; + if (node === undefined) return this.error9p(p.tag, 'no such fid'); + if (node.open !== true) return this.error9p(p.tag, 'fid not open'); + if (node.f.qid.type & QTDIR) + return this.error9p(p.tag, 'permission denied'); + if (node.f.write === undefined) + return this.error9p(p.tag, 'permission denied'); + return write_file(this, p, node); }, - - Tclunk: function(p){ - if (this.fids[p.fid] === undefined) return this.error9p(p.tag, "fid not in use"); - delete this.fids[p.fid]; - return this.send9p({type:msgtype.Rclunk, tag:p.tag}); - } -}; -var must_deny_access = function(f, mode) { - var reason; - if (isDir(f)) { return (mode & 3) ? "directories are read only" : false; } - if ((mode & 3) === 3) { return "OEXEC not implemented";} - if (mode & 3 && f.write === undefined) { return "read only";} - return false; -}; + Tclunk: function (p) { + if (this.fids[p.fid] === undefined) + return this.error9p(p.tag, 'fid not in use'); + delete this.fids[p.fid]; + return this.send9p({ type: msgtype.Rclunk, tag: p.tag }); + }, + }; -var read_file = function(service, packet, node) { - var data = node.f.read(packet.offset, packet.count); - return service.send9p({type:msgtype.Rread, tag:packet.tag, count:5, data:data}); -}; + var must_deny_access = function (f, mode) { + var reason; + if (isDir(f)) { + return mode & 3 ? 'directories are read only' : false; + } + if ((mode & 3) === 3) { + return 'OEXEC not implemented'; + } + if (mode & 3 && f.write === undefined) { + return 'read only'; + } + return false; + }; -var write_file = function(service, packet, node) { - var n = node.f.write(packet.offset, packet.data); - return service.send9p({type:msgtype.Rwrite, tag:packet.tag, count: n}); -}; + var read_file = function (service, packet, node) { + var data = node.f.read(packet.offset, packet.count); + return service.send9p({ + type: msgtype.Rread, + tag: packet.tag, + count: 5, + data: data, + }); + }; -var read_dirent = function(service, packet, f) { - //sends one dirent at a time, provided packet.offset===f.bloc and the dirent fits into packet.count - var reply = {type:msgtype.Rread, tag:packet.tag}; - if (packet.offset === 0) f.bloc = f.nloc = 0; - if (packet.offset != f.bloc) return service.error9p(packet.tag, "seek in directory illegal "+ - packet.offset +"!=" +f.bloc); - if (packet.count < 2) return service.error9p(packet.tag, "read too short"); - if (f.nloc >= f.f.nchildren.length) { - reply.data = ""; - return service.send9p(reply); - } - var child = f.f.nchildren[f.nloc]; - var s = exports.dirent(child); - if (packet.count < s.length) { - reply.data = s.substring(0,2); + var write_file = function (service, packet, node) { + var n = node.f.write(packet.offset, packet.data); + return service.send9p({ type: msgtype.Rwrite, tag: packet.tag, count: n }); + }; + + var read_dirent = function (service, packet, f) { + //sends one dirent at a time, provided packet.offset===f.bloc and the dirent fits into packet.count + var reply = { type: msgtype.Rread, tag: packet.tag }; + if (packet.offset === 0) f.bloc = f.nloc = 0; + if (packet.offset != f.bloc) + return service.error9p( + packet.tag, + 'seek in directory illegal ' + packet.offset + '!=' + f.bloc, + ); + if (packet.count < 2) return service.error9p(packet.tag, 'read too short'); + if (f.nloc >= f.f.nchildren.length) { + reply.data = ''; + return service.send9p(reply); + } + var child = f.f.nchildren[f.nloc]; + var s = exports.dirent(child); + if (packet.count < s.length) { + reply.data = s.substring(0, 2); + return service.send9p(reply); + } + f.bloc += s.length; + f.nloc++; + reply.data = s; return service.send9p(reply); - } - f.bloc += s.length; - f.nloc++; - reply.data = s; - return service.send9p(reply); -}; + }; -// here (for now) is dirent -exports.dirent = function(f){ - var fmt= ["i2:type", "i4:dev", "b13:qid", "i4:mode", "i4:atime", "i4:mtime", "i8:length", "S2:name", "S2:uid", "S2:gid", "S2:muid"]; - var now = new Date().getTime() / 1000; - var s = { type: 0, dev: 0, qid: f.qid, mode: 0, atime: now, mtime: now, length: 1, - name: f.name, uid: "js", gid: "js", muid: "js"}; - if (f.qid.type & QTDIR) { - s.mode |= 0o111; - s.mode += 0x80000000; - } - return s; -}; + // here (for now) is dirent + exports.dirent = function (f) { + var fmt = [ + 'i2:type', + 'i4:dev', + 'b13:qid', + 'i4:mode', + 'i4:atime', + 'i4:mtime', + 'i8:length', + 'S2:name', + 'S2:uid', + 'S2:gid', + 'S2:muid', + ]; + var now = new Date().getTime() / 1000; + var s = { + type: 0, + dev: 0, + qid: f.qid, + mode: 0, + atime: now, + mtime: now, + length: 1, + name: f.name, + uid: 'js', + gid: 'js', + muid: 'js', + }; + if (f.qid.type & QTDIR) { + s.mode |= 0o111; + s.mode += 0x80000000; + } + return s; + }; -// filesystem funcs -var mkfile = function(path, open, read, write, close) { + // filesystem funcs + var mkfile = function (path, open, read, write, close) { var f, parent, n; parent = this.lookup(path, true); path = path.split('/'); n = path[path.length - 1]; f = { - name: n, - parent: parent, - qid: {type: 0, ver: 0, path: ++lastquid}, - open: open, read: read, write: write, close: close}; + name: n, + parent: parent, + qid: { type: 0, ver: 0, path: ++lastquid }, + open: open, + read: read, + write: write, + close: close, + }; parent.children[n] = f; parent.nchildren.push(f); return f; -}; + }; -var mkdir = function(path){ + var mkdir = function (path) { var f, parent, name; parent = this.lookup(path, true); path = path.split('/'); - n = path[path.length-1]; + n = path[path.length - 1]; f = { - mkfile: mkfile, - mkdir: mkdir, - lookup: lookup, - name: n, parent: parent.name, children: {}, nchildren: [], - qid: {type: QTDIR, ver: 0, path: ++lastquid}}; + mkfile: mkfile, + mkdir: mkdir, + lookup: lookup, + name: n, + parent: parent.name, + children: {}, + nchildren: [], + qid: { type: QTDIR, ver: 0, path: ++lastquid }, + }; parent.children[n] = f; parent.nchildren.push(f); return f; -}; + }; -function lookup(path, getParent) { + function lookup(path, getParent) { var f, s, n, x; f = this; s = path.split('/'); - for(x in s){ - if(getParent && x == s.length - 1) - break; - n = s[x]; - if(n === "" || n == ".") - continue; - if(n == "..") - f = f.parent; - else { - f = f.children[n]; - if(f === undefined) - throw path + " not found"; - } + for (x in s) { + if (getParent && x == s.length - 1) break; + n = s[x]; + if (n === '' || n == '.') continue; + if (n == '..') f = f.parent; + else { + f = f.children[n]; + if (f === undefined) throw path + ' not found'; + } } return f; -} + } -var isDir = exports.isDir = function(i){ - return (i.qid.type === QTDIR); -}; -var isFile = exports.isFile = function(i){ - return (i.qid.type !== QTDIR); -}; + var isDir = (exports.isDir = function (i) { + return i.qid.type === QTDIR; + }); + var isFile = (exports.isFile = function (i) { + return i.qid.type !== QTDIR; + }); -exports.mkroot = function(){ + exports.mkroot = function () { return { - name: "/", - children: {}, - nchildren: [], - qid: {type: QTDIR, ver: 0, path: 0}, - mkfile: mkfile, - mkdir: mkdir, - lookup: lookup + name: '/', + children: {}, + nchildren: [], + qid: { type: QTDIR, ver: 0, path: 0 }, + mkfile: mkfile, + mkdir: mkdir, + lookup: lookup, }; -}; - -})(typeof(exports)==='undefined' ? this.ixp={} : exports); \ No newline at end of file + }; +})(typeof exports === 'undefined' ? (this.ixp = {}) : exports); diff --git a/ixputil.js b/ixputil.js index b4f6231..64c638c 100644 --- a/ixputil.js +++ b/ixputil.js @@ -1,112 +1,120 @@ -(function(exports){ -var fuzz = function(n){ +(function (exports) { + var fuzz = function (n) { var s, i; - s = ""; - for(i = 0; i < n; i++) - s += String.fromCharCode(Math.floor(Math.random() * 256)); + s = ''; + for (i = 0; i < n; i++) + s += String.fromCharCode(Math.floor(Math.random() * 256)); return s; -}; + }; -exports.fuzz = fuzz; -exports.ipack = ipack = function(fmt, data){ - var wide = fmt.substring(1); - var packed = ""; - switch (fmt.substring(0,1)){ - case 'i': return npack(data, wide); + exports.fuzz = fuzz; + var ipack; + exports.ipack = ipack = function (fmt, data) { + var wide = fmt.substring(1); + var packed = ''; + switch (fmt.substring(0, 1)) { + case 'i': + return npack(data, wide); case 'R': for (var x in data) { - packed += counted(data[x].toString(),2); + packed += counted(data[x].toString(), 2); } return packed; case 'b': case 's': packed = data.substring(0, wide); - if (packed.length < wide) { packed = pad(packed, wide, "\0");} + if (packed.length < wide) { + packed = pad(packed, wide, '\0'); + } return packed; - case 'S': return counted(data, wide); - default: throw "pack: unknown type is "+fmt+"."; - } - -}; - -var npack = function(n, wide){ - out = ""; - for (var p = 0; p < wide; p++){ - out += String.fromCharCode(n & 0xFF); - n >>>= 8; - } - return out; -}; + case 'S': + return counted(data, wide); + default: + throw 'pack: unknown type is ' + fmt + '.'; + } + }; -var counted = function(data, wide){ - return npack(data.length, wide) + data; -}; + var npack = function (n, wide) { + var out = ''; + for (var p = 0; p < wide; p++) { + out += String.fromCharCode(n & 0xff); + n >>>= 8; + } + return out; + }; -var pad = function(s, wide, fill) { - var pwide = wide - s.length; - s += Array(pwide+1).join(fill); - return s; -}; -exports.pad = pad; + var counted = function (data, wide) { + return npack(data.length, wide) + data; + }; -exports.pack = function(data, specs){ - var out = []; - specs.forEach(function(spec) { - var ss = spec.split(":"); - out.push(ipack(ss[0], data[ss[1]])); - }); - return out.join(''); -}; + var pad = function (s, wide, fill) { + var pwide = wide - s.length; + s += Array(pwide + 1).join(fill); + return s; + }; + exports.pad = pad; -function asInteger(s){ - var i = 0; - for (var p = s.length - 1; p >= 0; p--) { - i *= 256; - i += s.charCodeAt(p); - } - return i; -} + exports.pack = function (data, specs) { + var out = []; + specs.forEach(function (spec) { + var ss = spec.split(':'); + out.push(ipack(ss[0], data[ss[1]])); + }); + return out.join(''); + }; -var iunpack = function(fmt, data){ - var wide = fmt.substring(1); - switch (fmt.substring(0,1)) { - case 'i': return asInteger(data.substring(0, wide)); - case 'b': - case 's': return data.substring(0, wide); - case 'S': - //get actual count - var wide2 = asInteger(data.substring(0,wide)); - data = data.substring(wide); - return data.substring(0, wide2); - case 'R': - var out = []; - while (data.length > 0) { - var len = asInteger(data.substring(0, 2)); - if (len < 1) { throw "invalid size in array: "+len+" <<"+data+">>"; } - out.push(data.substring(2, 2+len)); - data = data.substring(2+len); - } - return out; - default: throw "unpack: unknown type is "+fmt; + function asInteger(s) { + var i = 0; + for (var p = s.length - 1; p >= 0; p--) { + i *= 256; + i += s.charCodeAt(p); + } + return i; } -}; -exports.unpack = function(data, specList){ - var out = {}; - specList.forEach(function(spec) { - var ss = spec.split(":"); - var slot = ss[1]; - var wide = ss[0].substring(1); - var value=iunpack(ss[0], data); - data = data.substring(wide); - if (ss[0].substring(0,1) === "S") { - data = data.substring(value.length); + var iunpack = function (fmt, data) { + var wide = fmt.substring(1); + switch (fmt.substring(0, 1)) { + case 'i': + return asInteger(data.substring(0, wide)); + case 'b': + case 's': + return data.substring(0, wide); + case 'S': + //get actual count + var wide2 = asInteger(data.substring(0, wide)); + data = data.substring(wide); + return data.substring(0, wide2); + case 'R': + var out = []; + while (data.length > 0) { + var len = asInteger(data.substring(0, 2)); + if (len < 1) { + throw 'invalid size in array: ' + len + ' <<' + data + '>>'; + } + out.push(data.substring(2, 2 + len)); + data = data.substring(2 + len); + } + return out; + default: + throw 'unpack: unknown type is ' + fmt; } - out[slot]=value; - }); - return out; -}; + }; -})(typeof(exports)==='undefined' ? this.ixputils={} : exports) ; - + exports.unpack = function (data, specList) { + var out = {}; + specList.forEach(function (spec) { + var ss = spec.split(':'); + var slot = ss[1]; + var wide = ss[0].substring(1); + var value = iunpack(ss[0], data); + data = data.substring(wide); + if (ss[0].substring(0, 1) === 'S') { + data = data.substring(value.length); + } + out[slot] = value; + }); + return out; + }; +})(typeof exports === 'undefined' ? (this.ixputils = {}) : exports); diff --git a/maybe.js b/maybe.js index 008d85b..db62031 100644 --- a/maybe.js +++ b/maybe.js @@ -1,13 +1,21 @@ -var maybe = function(value) { - var obj = null; - function isEmpty() { return value === undefined || value === null; } - function nonEmpty() { return !isEmpty(); } - obj = { - map: function (f) { return isEmpty() ? obj : maybe(f(value)); }, - orElse: function (n) { return isEmpty() ? n : value; }, - isEmpty: isEmpty, - nonEmpty: nonEmpty - }; - return obj; +var maybe = function (value) { + var obj = null; + function isEmpty() { + return value === undefined || value === null; + } + function nonEmpty() { + return !isEmpty(); + } + obj = { + map: function (f) { + return isEmpty() ? obj : maybe(f(value)); + }, + orElse: function (n) { + return isEmpty() ? n : value; + }, + isEmpty: isEmpty, + nonEmpty: nonEmpty, + }; + return obj; }; module.exports.maybe = maybe; diff --git a/package.json b/package.json index f40b574..343dfe3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "license": "BSD", "devDependencies": { "eslint": "^8.57.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.1.0" diff --git a/sandbox.js b/sandbox.js index 9f2ebe2..10e4190 100644 --- a/sandbox.js +++ b/sandbox.js @@ -2,16 +2,22 @@ var root = require('./ixp').mkroot(); var maybe = require('./maybe').maybe; root.mkdir('/dev'); -root.mkfile('/dev/cons', - undefined, - function(f,p){return "-nothing-";}, - function(f,p){console.log("f="+f+" p="+p);}, - undefined); +root.mkfile( + '/dev/cons', + undefined, + function (f, p) { + return '-nothing-'; + }, + function (f, p) { + console.log('f=' + f + ' p=' + p); + }, + undefined, +); -var cons = root.lookup("/dev/cons", true); -cons.write(null, "line1"); -cons.write(null, "line2"); +var cons = root.lookup('/dev/cons', true); +cons.write(null, 'line1'); +cons.write(null, 'line2'); var k = cons.read(null, null); cons.write(null, k); -maybe(cons.close).orElse(function(){})(); +maybe(cons.close).orElse(function () {})(); diff --git a/util.js b/util.js index 8cbe3ef..d2426db 100644 --- a/util.js +++ b/util.js @@ -1,2 +1,4 @@ -var fuzz = function(n){ return "fruitcake";}; +var fuzz = function (n) { + return 'fruitcake'; +}; module.exports = [fuzz];