1implement Shellbuiltin; 2 3include "sys.m"; 4 sys: Sys; 5include "draw.m"; 6include "lock.m"; 7 lock: Lock; 8 Semaphore: import lock; 9include "sh.m"; 10 sh: Sh; 11 Listnode, Context: import sh; 12 myself: Shellbuiltin; 13 14Tag: adt { 15 tagid, blocked: int; 16 offset, fid: int; 17 pick { 18 Read => 19 count: int; 20 rc: chan of (array of byte, string); 21 Write => 22 data: array of byte; 23 wc: chan of (int, string); 24 } 25}; 26 27taglock: ref Lock->Semaphore; 28maxtagid := 1; 29tags := array[16] of list of ref Tag; 30 31initbuiltin(ctxt: ref Context, shmod: Sh): string 32{ 33 sys = load Sys Sys->PATH; 34 sh = shmod; 35 36 myself = load Shellbuiltin "$self"; 37 if (myself == nil) 38 ctxt.fail("bad module", sys->sprint("file2chan: cannot load self: %r")); 39 40 lock = load Lock Lock->PATH; 41 if (lock == nil) ctxt.fail("bad module", sys->sprint("file2chan: cannot load %s: %r", Lock->PATH)); 42 lock->init(); 43 44 taglock = Semaphore.new(); 45 if (taglock == nil) 46 ctxt.fail("no lock", "file2chan: cannot make lock"); 47 48 49 ctxt.addbuiltin("file2chan", myself); 50 ctxt.addbuiltin("rblock", myself); 51 ctxt.addbuiltin("rread", myself); 52 ctxt.addbuiltin("rreadone", myself); 53 ctxt.addbuiltin("rwrite", myself); 54 ctxt.addbuiltin("rerror", myself); 55 ctxt.addbuiltin("fetchwdata", myself); 56 ctxt.addbuiltin("putrdata", myself); 57 ctxt.addsbuiltin("rget", myself); 58 59 return nil; 60} 61 62whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string 63{ 64 return nil; 65} 66 67getself(): Shellbuiltin 68{ 69 return myself; 70} 71 72runbuiltin(ctxt: ref Context, nil: Sh, 73 cmd: list of ref Listnode, nil: int): string 74{ 75 case (hd cmd).word { 76 "file2chan" => return builtin_file2chan(ctxt, cmd); 77 "rblock" => return builtin_rblock(ctxt, cmd); 78 "rread" => return builtin_rread(ctxt, cmd, 0); 79 "rreadone" => return builtin_rread(ctxt, cmd, 1); 80 "rwrite" => return builtin_rwrite(ctxt, cmd); 81 "rerror" => return builtin_rerror(ctxt, cmd); 82 "fetchwdata" => return builtin_fetchwdata(ctxt, cmd); 83 "putrdata" => return builtin_putrdata(ctxt, cmd); 84 } 85 return nil; 86} 87 88runsbuiltin(ctxt: ref Context, nil: Sh, 89 argv: list of ref Listnode): list of ref Listnode 90{ 91 # could add ${rtags} to retrieve list of currently outstanding tags 92 case (hd argv).word { 93 "rget" => return sbuiltin_rget(ctxt, argv); 94 } 95 return nil; 96} 97 98builtin_file2chan(ctxt: ref Context, argv: list of ref Listnode): string 99{ 100 rcmd, wcmd, ccmd: ref Listnode; 101 path: string; 102 103 n := len argv; 104 if (n < 4 || n > 5) 105 ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]"); 106 107 (path, argv) = ((hd tl argv).word, tl tl argv); 108 (rcmd, argv) = (hd argv, tl argv); 109 (wcmd, argv) = (hd argv, tl argv); 110 if (argv != nil) 111 ccmd = hd argv; 112 if (path == nil || !iscmd(rcmd) || !iscmd(wcmd) || (ccmd != nil && !iscmd(ccmd))) 113 ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]"); 114 115 (dir, f) := pathsplit(path); 116 if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) { 117 reporterror(ctxt, sys->sprint("file2chan: cannot bind #s: %r")); 118 return "no #s"; 119 } 120 fio := sys->file2chan(dir, f); 121 if (fio == nil) { 122 reporterror(ctxt, sys->sprint("file2chan: cannot make %s: %r", path)); 123 return "cannot make chan"; 124 } 125 sync := chan of int; 126 spawn srv(sync, ctxt, fio, rcmd, wcmd, ccmd); 127 apid := <-sync; 128 ctxt.set("apid", ref Listnode(nil, string apid) :: nil); 129 if (ctxt.options() & ctxt.INTERACTIVE) 130 sys->fprint(sys->fildes(2), "%d\n", apid); 131 return nil; 132} 133 134srv(sync: chan of int, ctxt: ref Context, 135 fio: ref Sys->FileIO, rcmd, wcmd, ccmd: ref Listnode) 136{ 137 ctxt = ctxt.copy(1); 138 sync <-= sys->pctl(0, nil); 139 for (;;) { 140 fid, offset, count: int; 141 rc: Sys->Rread; 142 wc: Sys->Rwrite; 143 d: array of byte; 144 t: ref Tag = nil; 145 cmd: ref Listnode = nil; 146 alt { 147 (offset, count, fid, rc) = <-fio.read => 148 if (rc != nil) { 149 t = ref Tag.Read(0, 0, offset, fid, count, rc); 150 cmd = rcmd; 151 } else 152 continue; # we get a close on both read and write... 153 (offset, d, fid, wc) = <-fio.write => 154 if (wc != nil) { 155 t = ref Tag.Write(0, 0, offset, fid, d, wc); 156 cmd = wcmd; 157 } 158 } 159 if (t != nil) { 160 addtag(t); 161 ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil); 162 ctxt.run(cmd :: nil, 0); 163 taglock.obtain(); 164 # make a default reply if it hasn't been deliberately blocked. 165 del := 0; 166 if (t.tagid >= 0 && !t.blocked) { 167 pick mt := t { 168 Read => 169 rreply(mt.rc, nil, "invalid read"); 170 Write => 171 wreply(mt.wc, len mt.data, nil); 172 } 173 del = 1; 174 } 175 taglock.release(); 176 if (del) 177 deltag(t.tagid); 178 ctxt.setlocal("tag", nil); 179 } else if (ccmd != nil) { 180 t = ref Tag.Read(0, 0, -1, fid, -1, nil); 181 addtag(t); 182 ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil); 183 ctxt.run(ccmd :: nil, 0); 184 deltag(t.tagid); 185 ctxt.setlocal("tag", nil); 186 } 187 } 188} 189 190builtin_rread(ctxt: ref Context, argv: list of ref Listnode, one: int): string 191{ 192 n := len argv; 193 if (n < 2 || n > 3) 194 ctxt.fail("usage", "usage: "+(hd argv).word+" [tag] data"); 195 argv = tl argv; 196 197 t := envgettag(ctxt, argv, n == 3); 198 if (t == nil) 199 ctxt.fail("bad tag", "rread: cannot find tag"); 200 if (n == 3) 201 argv = tl argv; 202 mt := etr(ctxt, "rread", t); 203 arg := word(hd argv); 204 d := array of byte arg; 205 if (one) { 206 if (mt.offset >= len d) 207 d = nil; 208 else 209 d = d[mt.offset:]; 210 } 211 if (len d > mt.count) 212 d = d[0:mt.count]; 213 rreply(mt.rc, d, nil); 214 deltag(t.tagid); 215 return nil; 216} 217 218builtin_rwrite(ctxt: ref Context, argv: list of ref Listnode): string 219{ 220 n := len argv; 221 if (n > 3) 222 ctxt.fail("usage", "usage: rwrite [tag [count]]"); 223 t := envgettag(ctxt, tl argv, n > 1); 224 if (t == nil) 225 ctxt.fail("bad tag", "rwrite: cannot find tag"); 226 227 mt := etw(ctxt, "rwrite", t); 228 count := len mt.data; 229 if (n == 3) { 230 arg := word(hd tl argv); 231 if (!isnum(arg)) 232 ctxt.fail("usage", "usage: freply [tag [count]]"); 233 count = int arg; 234 } 235 wreply(mt.wc, count, nil); 236 deltag(t.tagid); 237 return nil; 238} 239 240builtin_rblock(ctxt: ref Context, argv: list of ref Listnode): string 241{ 242 argv = tl argv; 243 if (len argv > 1) 244 ctxt.fail("usage", "usage: rblock [tag]"); 245 t := envgettag(ctxt, argv, argv != nil); 246 if (t == nil) 247 ctxt.fail("bad tag", "rblock: cannot find tag"); 248 t.blocked = 1; 249 return nil; 250} 251 252sbuiltin_rget(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode 253{ 254 n := len argv; 255 if (n < 2 || n > 3) 256 ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]"); 257 argv = tl argv; 258 t := envgettag(ctxt, tl argv, tl argv != nil); 259 if (t == nil) 260 ctxt.fail("bad tag", "rget: cannot find tag"); 261 s := ""; 262 case (hd argv).word { 263 "data" => 264 s = string etw(ctxt, "rget", t).data; 265 "count" => 266 s = string etr(ctxt, "rget", t).count; 267 "offset" => 268 s = string t.offset; 269 "fid" => 270 s = string t.fid; 271 * => 272 ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]"); 273 } 274 275 return ref Listnode(nil, s) :: nil; 276} 277 278builtin_fetchwdata(ctxt: ref Context, argv: list of ref Listnode): string 279{ 280 argv = tl argv; 281 if (len argv > 1) 282 ctxt.fail("usage", "usage: fetchwdata [tag]"); 283 t := envgettag(ctxt, argv, argv != nil); 284 if (t == nil) 285 ctxt.fail("bad tag", "fetchwdata: cannot find tag"); 286 d := etw(ctxt, "fetchwdata", t).data; 287 sys->write(sys->fildes(1), d, len d); 288 return nil; 289} 290 291builtin_putrdata(ctxt: ref Context, argv: list of ref Listnode): string 292{ 293 argv = tl argv; 294 if (len argv > 1) 295 ctxt.fail("usage", "usage: putrdata [tag]"); 296 t := envgettag(ctxt, argv, argv != nil); 297 if (t == nil) 298 ctxt.fail("bad tag", "putrdata: cannot find tag"); 299 mt := etr(ctxt, "putrdata", t); 300 buf := array[mt.count] of byte; 301 n := 0; 302 fd := sys->fildes(0); 303 while (n < mt.count) { 304 nr := sys->read(fd, buf[n:mt.count], mt.count - n); 305 if (nr <= 0) 306 break; 307 n += nr; 308 } 309 310 rreply(mt.rc, buf[0:n], nil); 311 deltag(t.tagid); 312 return nil; 313} 314 315builtin_rerror(ctxt: ref Context, argv: list of ref Listnode): string 316{ 317 # usage: ferror [tag] error 318 n := len argv; 319 if (n < 2 || n > 3) 320 ctxt.fail("usage", "usage: ferror [tag] error"); 321 t := envgettag(ctxt, tl argv, n == 3); 322 if (t == nil) 323 ctxt.fail("bad tag", "rerror: cannot find tag"); 324 if (n == 3) 325 argv = tl argv; 326 err := word(hd tl argv); 327 pick mt := t { 328 Read => 329 rreply(mt.rc, nil, err); 330 Write => 331 wreply(mt.wc, 0, err); 332 } 333 deltag(t.tagid); 334 return nil; 335} 336 337envgettag(ctxt: ref Context, args: list of ref Listnode, useargs: int): ref Tag 338{ 339 tagid: int; 340 if (useargs) 341 tagid = int (hd args).word; 342 else { 343 args = ctxt.get("tag"); 344 if (args == nil || tl args != nil) 345 return nil; 346 tagid = int (hd args).word; 347 } 348 return gettag(tagid); 349} 350 351etw(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Write 352{ 353 pick mt := t { 354 Write => return mt; 355 } 356 ctxt.fail("bad tag", cmd + ": inappropriate tag id"); 357 return nil; 358} 359 360etr(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Read 361{ 362 pick mt := t { 363 Read => return mt; 364 } 365 ctxt.fail("bad tag", cmd + ": inappropriate tag id"); 366 return nil; 367} 368 369wreply(wc: chan of (int, string), count: int, err: string) 370{ 371 alt { 372 wc <-= (count, err) => ; 373 * => ; 374 } 375} 376 377rreply(rc: chan of (array of byte, string), d: array of byte, err: string) 378{ 379 alt { 380 rc <-= (d, err) => ; 381 * => ; 382 } 383} 384 385word(n: ref Listnode): string 386{ 387 if (n.word != nil) 388 return n.word; 389 if (n.cmd != nil) 390 n.word = sh->cmd2string(n.cmd); 391 return n.word; 392} 393 394isnum(s: string): int 395{ 396 for (i := 0; i < len s; i++) 397 if (s[i] > '9' || s[i] < '0') 398 return 0; 399 return 1; 400} 401 402iscmd(n: ref Listnode): int 403{ 404 return n.cmd != nil || (n.word != nil && n.word[0] == '}'); 405} 406 407addtag(t: ref Tag) 408{ 409 taglock.obtain(); 410 t.tagid = maxtagid++; 411 slot := t.tagid % len tags; 412 tags[slot] = t :: tags[slot]; 413 taglock.release(); 414} 415 416deltag(tagid: int) 417{ 418 taglock.obtain(); 419 slot := tagid % len tags; 420 nwl: list of ref Tag; 421 for (wl := tags[slot]; wl != nil; wl = tl wl) 422 if ((hd wl).tagid != tagid) 423 nwl = hd wl :: nwl; 424 else 425 (hd wl).tagid = -1; 426 tags[slot] = nwl; 427 taglock.release(); 428} 429 430gettag(tagid: int): ref Tag 431{ 432 slot := tagid % len tags; 433 for (wl := tags[slot]; wl != nil; wl = tl wl) 434 if ((hd wl).tagid == tagid) 435 return hd wl; 436 return nil; 437} 438 439pathsplit(p: string): (string, string) 440{ 441 for (i := len p - 1; i >= 0; i--) 442 if (p[i] != '/') 443 break; 444 if (i < 0) 445 return (p, nil); 446 p = p[0:i+1]; 447 for (i = len p - 1; i >=0; i--) 448 if (p[i] == '/') 449 break; 450 if (i < 0) 451 return (".", p); 452 return (p[0:i+1], p[i+1:]); 453} 454 455reporterror(ctxt: ref Context, err: string) 456{ 457 if (ctxt.options() & ctxt.VERBOSE) 458 sys->fprint(sys->fildes(2), "%s\n", err); 459} 460