9sh

9P shell
Log | Files | Refs

main.go (3968B)


      1 package main
      2 
      3 import (
      4 	"context"
      5 	"errors"
      6 	"fmt"
      7 	"io"
      8 	"io/fs"
      9 	"log"
     10 	"net"
     11 	"os"
     12 	"path"
     13 	"strings"
     14 
     15 	"git.mtkn.jp/lib9p"
     16 	"git.mtkn.jp/lib9p/client"
     17 )
     18 
     19 
     20 type stat struct {
     21 	cwd  string // current working directory. absolute path.
     22 	f    lib9p.File
     23 	fsys *unionFS
     24 }
     25 
     26 type token struct {
     27 	cmd string
     28 	err error
     29 }
     30 
     31 type cmd struct {
     32 	args []string
     33 	err error
     34 }
     35 
     36 func (c *cmd) run(s *stat) error {
     37 	defer func() {
     38 		p := recover()
     39 		if p != nil {
     40 			log.Printf("s.cwd: %q", s.cwd)
     41 			panic(p)
     42 		}
     43 	}()
     44 	if len(c.args) < 1 {
     45 		return errors.New("no command specified")
     46 	}
     47 	switch c.args[0] {
     48 	case "cd":
     49 		var p string
     50 		if len(c.args) == 1 {
     51 			p = "."
     52 		} else if len(c.args) == 2 {
     53 			p = absPath(s.cwd, c.args[1])
     54 		} else {
     55 			return fmt.Errorf("usage: cd <dir>")
     56 		}
     57 		if s.f != nil {
     58 			s.f.Close()
     59 		}
     60 		fi, err := fs.Stat(lib9p.ExportFS{s.fsys}, p)
     61 		if err != nil {
     62 			return err
     63 		}
     64 		if !fi.IsDir() {
     65 			return fmt.Errorf("not a directory")
     66 		}
     67 		f, err := s.fsys.OpenFile(p, lib9p.OREAD)
     68 		if err != nil {
     69 			return err
     70 		}
     71 		s.f = f
     72 		s.cwd = p
     73 		return nil
     74 	case "pwd":
     75 		fmt.Println(s.cwd)
     76 		return nil
     77 	case "ls":
     78 		p := s.cwd
     79 		if len(c.args) > 1 {
     80 			p = absPath(p, c.args[1])
     81 		}
     82 		files, err := fs.Glob(lib9p.ExportFS{s.fsys}, path.Join(p, "*"))
     83 		if err != nil {
     84 			return err
     85 		}
     86 		for _, f := range files {
     87 			fmt.Println(f)
     88 		}
     89 		return nil
     90 	case "mount":
     91 		if len(c.args) != 3 {
     92 			return fmt.Errorf("usage: mount servename mtpt")
     93 		}
     94 		servname, mtpt := c.args[1], c.args[2]
     95 		conn, err := net.Dial("tcp", servname)
     96 		if err != nil {
     97 			return err
     98 		}
     99 		var p string
    100 		if len(mtpt) != 0 && mtpt[0] == '/' {
    101 			p = absPath("", mtpt)
    102 		} else {
    103 			p = absPath(s.cwd, mtpt)
    104 		}
    105 		cfsys, err := client.Mount(context.TODO(), conn, conn, "kenji", "")
    106 		if err != nil {
    107 			return err
    108 		}
    109 		s.fsys.Mount(cfsys, p)
    110 		return nil
    111 	case "fstab" :
    112 		fmt.Println(s.fsys.fstab)
    113 		return nil
    114 	default:
    115 		return fmt.Errorf("unknown command %v", c.args[0])
    116 	}
    117 }
    118 
    119 func main() {
    120 	conn, err := net.Dial("tcp", "127.0.0.1:5640")
    121 	if err != nil {
    122 		log.Fatalf("dial: %v", err)
    123 	}
    124 	defer conn.Close()
    125 	ctx, cancel := context.WithCancel(context.Background())
    126 	fsys, err := client.Mount(ctx, conn, conn, "kenji", "")
    127 	if err != nil {
    128 		log.Fatalf("mount: %v", err)
    129 	}
    130 	defer cancel()
    131 	tc := runTokenizer(ctx, os.Stdin)
    132 	cc := runParser(ctx, tc)
    133 	s := &stat{
    134 		cwd: ".",
    135 		fsys: &unionFS{fstab: map[string]lib9p.FS{".": fsys}},
    136 	}
    137 	for {
    138 		fmt.Printf("9%% ")
    139 		c := <-cc
    140 		if c.err == io.EOF {
    141 			break
    142 		} else if c.err != nil {
    143 			log.Println(c.err)
    144 			continue
    145 		}
    146 		if err := c.run(s); err != nil {
    147 			log.Println(err)
    148 		}
    149 	}
    150 }
    151 
    152 func read(r io.Reader) (string, error) {
    153 	b := make([]byte, 128)
    154 	n, err := r.Read(b)
    155 	if err != nil {
    156 		return "", err
    157 	}
    158 	return string(b[:n]), nil
    159 }
    160 
    161 func runTokenizer(ctx context.Context, r io.Reader) <-chan *token {
    162 	tc := make(chan *token)
    163 	go func() {
    164 		defer close(tc)
    165 L:
    166 		for {
    167 			select {
    168 			case <-ctx.Done():
    169 				break L
    170 			default:
    171 			}
    172 			str, err := read(r)
    173 			if err != nil {
    174 				select {
    175 				case tc <-&token{cmd: "", err: err}:
    176 					continue L
    177 				case <-ctx.Done():
    178 					break L
    179 				}
    180 			}
    181 			ts := strings.Split(str, " ")
    182 			for _, s := range ts {
    183 				t := &token{cmd: s, err: nil}
    184 				select {
    185 				case tc <- t:
    186 				case <-ctx.Done():
    187 					break L
    188 				}
    189 			}
    190 		}
    191 	}()
    192 	return tc
    193 }
    194 
    195 func runParser(ctx context.Context, tc <-chan *token) <-chan *cmd {
    196 	cc := make(chan *cmd)
    197 	go func() {
    198 		defer close(cc)
    199 L:
    200 		for {
    201 			c := new(cmd)
    202 M:
    203 			for {
    204 				select {
    205 				case <-ctx.Done():
    206 					break L
    207 				case t := <-tc:
    208 					if t.err != nil {
    209 						c.err = t.err
    210 						break M
    211 					}
    212 					end := false
    213 					if len(t.cmd) == 0 {
    214 						continue M
    215 					}
    216 					if t.cmd[len(t.cmd)-1] == '\n' {
    217 						t.cmd = t.cmd[:len(t.cmd)-1]
    218 						end = true
    219 					}
    220 					c.args = append(c.args, t.cmd)
    221 					if end {
    222 						break M
    223 					}
    224 				}
    225 			}
    226 			select {
    227 			case <-ctx.Done():
    228 				break L
    229 			case cc<- c:
    230 			}
    231 		}
    232 	}()
    233 	return cc
    234 }