GShell 0.5.6 − WindowSurfer 0.0.4

基盤:残念ながら Windows Zero というのはさんざん既出でした。逆に Windows 2020のほうがレア。

社長:Windows 1-2-3 とかどうでしょうね。

開発:社名にちなんで Windows More とか。

社長:Winix とかどうでしょうね。

基盤:さんざん既出です。

社長:この際 Satox とでもしますか。

開発:Window Surfing とか。

基盤:ネットサーフィンっぽいですね。

社長:WindowSurfer は良いかもですね。少なくともデスクトップというかウィンドウマネジャーの名前としては。昔、キャッシングプロキシサーバでキャッシュサーファーと標榜してた事もあります。

開発:そういえば学生時代にスケートボードに帆を付けて遊んでましたっけ。

社長:では今日からこのこは WindowSurfer という名前にしましょう。まあ、幼名ってやつかもです。

命名:WindowSurfer2020

社長:早速ドメイン名を… ありゃ、.com は取られてますね。使われてはないようですが。Buy Now: $5395ですって。

経理:有りえません。

社長:じゃあ .net を 140円で。ぽちっと。

基盤:ネームサーバを設定。WindowSurfer.Net、ただいま発足しました。

社長:今日はひと仕事終えた感ですね。一旦寝ます。

基盤:WordPressで「詩」ブロックのCSSを改訂しました。テストします。

一生を棒に振りし男此処に眠る。

社長:変なラベル貼らないでください。

* * *

社長:今日も筑波山のほうによい空が出ています。

開発:夏には見なかった雲も出てますね。

基盤:では今日の壁紙はやや嗜好を変えてこういう感じで。

* * *

社長:タバコと缶ビール買ってきました。今日はゥァの上に面白い空が見えなかったので隣のSHIMAZUさんをパチリ。

開発:つくば支店て何をやってるんでしょうね?なぜこんな住宅街に。

社長:SHIMAZU@AZUMAとか。

基盤:田中さんの開発した試薬とかをぅぁで先行販売してたら面白いです。

開発:あそこのロボット店員さんはSHIMAZU製かも知れませんね。

基盤:シマヅセイサク君とかですか。

社長:基本、分析装置の会社じゃないですかね。いずれにしてもうちが逆立ちしても買えるような製品ラインナップでは無い。

開発:田中さんのサイン会とかやったら絶対に行きます。

社長:ソフトウェアの質量分析計とか共同開発を持ちかけてみますかね。一緒にイグノーベル賞を目指しましょう、みたいな。

基盤:将来亡くなったあかつきには各支店にストゥーパとか建立するんでしょうか。

社長:隣のH2より高い五重の塔を希望。

開発:コンパクトにビリケンさんみたいな形態に落ち着くんじゃないですかね。

基盤:H-IIが正式表記のようです。高さ50m。ちなみにせんぬきタワーは45m

開発:あのダッサイカラーリングはなんとかならないですかね。それにあの記念公園はAZUMA3からつくば駅に到達する前の障害物っていう認識です。なんで斜めに突っ切れないのか。

社長:でも一度プラネタリウムには行ってみたいです。

経理:プラネタリウム券1000円だそうです。

基盤:まるめて2LSUですね。

開発:まるめて .net ドメイン代一年分ですか。

社長:まるめて生中2杯ぶんですね。

経理:まるめて40kWh、50Wで連続一ヶ月ぶんですね。

* * *

基盤:おや、もうこんな時間に。今日はまだ何も作ってませんが。

開発:どうも気合が入らなくてですね。

基盤:なにかこう、ぱーっとしたネタを。

社長:そうですね。自分を別のブラウザにドラッグ&ドロップして移動できるか?ってのはどうでしょう?

開発:盛り上がって参りました。

開発:ではまず準備。これはログを見ながらでないと効率が悪いのですが、各ブラウザごとにログ窓を開いて見比べるのが面倒で仕方がありません。気が乗らない原因の一つです。それで、ログは GJ Link に投げて、一箇所で見れるようにします。

ブラウザ間でのウィンドウ複製

開発:あら?

社長:何か?

開発:準備中になにげにドラッグ&ドロップしたら、ウインドウがコピーできてしまいました。

開発:左上が移転先と想定するEdge、右上が移転元のFireFox、下がGJ LinkサーバーのGShellの画面です。右上で何気にDeleGateのホームページをWirtual Browserで開いて、ドラッグして左上のEdgeの中の Wirtual Desktopにドロップしたら、コピーされて動いてしましました。

開発:確認のため、GJ Linkは落としてます。で、ドロップ先のEdgeは、昨日版のWirtual Desktop 0.0.3にします。で、右のFirefoxでGolang のトップページを開いて、左のEdgeにドラッグ。

①ドラッグ

基盤:ドロップ可能サインの緑丸+印が出てますね。

開発:で、ドロップする。

②ドロップ

社長、基盤:おおー。

開発:イベントリスナーとか各種スタイル情報のほとんどが伝わっていないようですが、WDのdivの下にあることは確かです。

開発:iframe内での状態遷移は伝わっていません。iframe の src と class だけがコピーされた感じです。

開発:状況から推測すると、こういうことです。Wirtual Desktopはcontenteditableなので、ここでキー入力をするとそのままデスクトップが編集できます。外からプレーンテキストをドラッグ&ドロップすることはそれと等価だと思われます。それの拡張として、HTMLテキストをドラッグ&ドロップできる。ということでは無いだろうかと。

基盤:要するにクリップボードのようなものだという事ですね。

開発:たぶん。例えば今日のブログから、SHIMAZUをドラッグ&ドロップ。

社長:なるほど… そう考えるればシンプルな話ですね。ドラッグ&ドロップというか、単なるコピー&ペーストでは無いかと。シンプルなので最強。

基盤:それってつまり、img も data URL でコピペできるって話ですかね。

開発:GShellのロゴは data URLですが、コピー&ペーストできますね。

開発:ただ、コピーの場合、何がコピーされるかはドラッグの場合と違うようです。Golangのiframeの上でコピーしてペーストしたら、こういうことになりました。

基盤:もろに data URL ですね。Goのマスコットの画像データですかね?

社長:なるほど。だいたいわかりました。いずれにしても、Wirtual Desktopの現在のDOM内容をHTMLダンプしてみればタネも仕掛けも明らかになるということですね。

開発:実際最初にそれで覗こうとおもたのですが、ペーストのショックでJavaScriptの変数からエレメントへのリンクが飛んでしまっているみたいで、覗けなかったという次第です。まあ、ちゃんとgetElementById()して探し直せば見つかると思います。

基盤:ブラウザのインスペクタで覗けば良いのでは。

開発:それだと敗北感が・・・

社長:あとは、自作でGShell内蔵専用のクリップボードを作るとよいですね。

開発:そうしましょう。

社長:ビジュアルにコピペとかドラッグ&ドロップできるのは、操作モデル的には良い事だと思います。問題は裏にあるデータを全部ひっぱってはいけない事ですね。

開発:なので、それはGJ Linkで裏方根回しをすればよいのだと思います。こういうのが窓から降って来たけど、これはなんのつもりですかというように。

社長:オブジェクトにユニークIDをふる必要がありそうです。

開発:それと、この動作は今のところ、FirefoxからChromium系へのドラッグ&ドロップでしか観察されていません。しかるべき環境、おそらくドロップエリアの設定をしてないからでは無いかと思われます。

基盤:ついに表舞台と裏舞台が連携する実用例ができそうですね。

開発:でも、なんだか疲れたのでまた明日ですかね…

社長:ここでまた血圧を測定。おー、やっぱり上がってます。

基盤:血圧計が久しぶりに稼働してますね。

社長:明後日の女医さんとの対決に備えてグラフを書いてます。

ブラウザが投下していったものは

開発:やはりどこに何を落としていったのか気になるので、インスペクタで覗いてみました。こういことでした。

開発:ということで、落下物の本体は src の指定された iframeでした。これはまあ当然ですが。たぶん出発地のz-index付きなのがやや不思議です。なぜこのポイントに投下したのかも。あと、ちゃんとオリジナルのid付きで来ているので、これを一意IDにすれば、身元確認をGJ Link系で出来ます。

基盤:投下先のidの重複とか調整しないで置いてっちゃうんですね。

開発:自前イスペクタで見れなくなっていたのはそのためでした。今は直したので、ちゃんと読めます。

基盤:このビューアで編集したのを実際のDOMに反映できると良いですね。

開発:それはGJ Factoryのテーブル編集でもやりましたし、できると思います。ただ、JavaScriptを反映させるのは制約があるかも。

基盤:evalすればなんでも行けるんじゃ無いですかね。

開発:それで、投下物の包み紙は、最外核の id=gshから中間の id=WirtualDesktopCodeSpan のたまねぎ状です。で、gshのほうにつけてあるユーザ定義の data-author="sato@its-more.jp" も付いて来てますから、ここに電子署名を入れておけば認証とアクセス制御が可能です。

社長:HTML電子署名の内蔵方式はいくつか検討してペンディングでしたけど、タグのdata-*属性に入れ込むというやり方が有力になりましたね。大体見えて来ましたので、あとは明日の楽しみにしましょうか。

* * *

基盤:終業にあたり、シメの詩をどうぞ。

社長:はあ。ではバクマンのエンディング Moshimo No Hanashiより借用。

目指す場所があまりにも遠く
ぼくはもう道に迷いそうだ
壊れた磁石をあてにしてるんだ
だれかの影に隠れてさ

開発:影にしてくれる誰かはいないですけどね今。

社長:バクマンを見てた当時はこれが心に刺さったもので。今は隠れる必要もなくなりました。

基盤:全体を読むと一貫性の無い歌詞ですね。

社長:まあ前言撤回の詩ですしね。道を迷うから思わぬ所へ行ける。このブログそのものです。重要なのは曖昧さと柔軟性を持つスジだと思います。

開発:憧れてたのは空じゃなくて、空を見てたあのこダー♪

-- 2020-0930 SatoxITS

GShell 0.5.6

/* */ /* GShell-0.5.6 by SatoxITS
GShell version 0.5.6 // 2020-09-30 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.6" DATE = "2020-09-30" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /* */ /*
Live HTML Snapshot
*/ /*
Event sharing

Inter-window communicaiton

frame0 >>> frame1 and frame2
frame1 >>> frame0 and frame2
frame2 >>> frame0 and frame1




*/ /* // /*
Wirtual Desktop

Window Surfer 0.0.4

WD-WallPaler03.png

Window Surfer 0.0.4 2020/09/01 01:38:50
01:38
Reload
Reload
Reload
*/ //
// // /*
Work Template

Template of Work

*/ //
// //

祝300投稿到達

社長:WordPressのadminコンソールで見たら、昨日の投稿で300投稿になってました。

基盤:昨日というか、さっき書き終えたやつですね。

社長:書き始めた時は、どういう形式にしようか模索状態だったんですが、まあ300回くらい書いてみたら何か見えてくるかなと思ってました。

基盤:要するに、トピックものと日誌ものになりましたね。

開発:6ヶ月で300、一日平均2投稿です。

"祝300投稿到達" の続きを読む

GShell 0.5.5 − Wirtual Desktop 0.0.3

開発:昨日の0.5.4のコードの後始末がずれこんでしまいました。ようやく決着。

社長:今日のぶんを始める前に疲れてしましましたね (^-^;

開発:styleに top, left を設定するには px を付ける必要がある事は以前にもハマって痛い目にあってたんですが。あれとあわせて3時間くらいロスしてます。

社長:座標がpx以外で指定できるというのは逆に意外というか、盲点ですね。

開発:意味不明なのですが、素だとできちゃうのがまた混乱します。WordPressに貼り付けると、効かなくなる。CSSOMの状態というか先行する定義がある場合にダメになるような感じです。

基盤:この際なので iMac をアップデートさせといて一休みしましょう。

社長:ところでこのRetinaに米粒文字を詰め込んだら何文字までいけますかね。

開発:一文字16ドットとして、5120ピクセルだと320文字、80 x 4 ですね。

社長:あれ?意外と大したことないですね。

基盤:そういえば最近やたらスクリーンショットのPNGがデカイと感じていたのですが、Retina だからですね。約3倍になります。

開発:こないだまでは画面共有でMacMiniからフィリップス君のスナップショットをとってたんですね。

基盤:Catalina 10.15.7 へのアップデート終了しました。所要時間約25分。

基盤:それと、Screen Shareing + Mission Control で画面の上下サイズがおかしくなる件、これのせいだったようです。

基盤:意味はわかりませんが、ScalingというのををOnにすると良い模様。

社長:なんかの拍子にOffをクリックしちゃったんでしょうかね。これで生活環境大改善です。

Wirtual Desktop 0.0.3 第1ヒート

開発:あー、やれやれ。ようやくWirtual Desktop がリサイズできるようになりました。

社長:何が問題だったのでしょう?

開発:width の指定に pxが着いてなくて、CSSが無視された模様です。CSSの中のresize:bothが効いてませんでした。で、これは直したのですが、Safariでのみ、オリジナルサイズより小さくできないという状況です。

開発:何が違うのか、開発者ツールでみてみました。まず縮小ができる Chromium系、Vivaldiで状態を検査。

開発:一方shrinkを拒否する Safariで状態を検査。

開発:ぱっと見、現在より小さくできない理由が見えません。検索すると、自動shrink関係とか viewportがどうとかいう記事は見かけます。

基盤:「ユーザエージェント・スタイルシート」って、Client-side CSSのことですかね?

社長:まあJavaScriptで自力で設定すればできるんでしょうし、とりあえず先に進めましょう。

開発:次に、中身のウィンドウを小さくするテスト。

基盤:おーっ、カーリングのストーンのようですね。

開発:これで 60px x 30px です。

社長:ここまで小さいと全体のサムネールは無理っぽいですね。逆に今みたいに、実物の一片が生で見えた方が良いように思います。

開発:オリジナルコンテンツを作るときに、左上というか先頭にそのページを区別できるようなマークというかロゴ的なものを配置するということですね。

基盤:faviconのインライン版みたいな。

社長:読み手がそのページを特徴付けられると良いですね。ユーザがページにノートをつけられることも必要です。

開発:最終的にそのウィンドウにアクセスしたタイムスタンプ+ノートにしましょうか。ローカルストレージかGJ Linkでどこかに永続的に保管する。

社長:最後に見た時にカーソルのあった座標位置も記憶したいですね。そこを中心とした画像を作ってサムネール化すると良いのかも知れません。

基盤:ウィンドウのサイズも記憶させたいですね。

開発:最小、最大の間に、いろんな段階があると思います。

社長:ユーザが手動でリサイズして、その状態、その位置に名前をつけて記憶させると良いと思います。

基盤:ところで、ドラッグした先に落ち着いてくれないですね。

開発:これが謎だったんですが、これもまた 'px' 問題だったのではないかというオカンがしています。

社長:だいぶ見えて来ましたので、一休みしましょう。というか、最近飲みに出るのを忘れていたので、どこかでぱーっと。

基盤:昨日の祝勝会ですね。

命名 Windows-Now

社長:ただいま゛ー

経理:酒くさ〜

社長:今日はぼーっとして出かけて、メガネもケータイも忘れました。

開発:そのぶん、いろんな事を考えられましたね。

社長:行きがけに考えたのは、このウィンドウの名前です。BNW、Onew、Now、などなど。BNWはBrandNew Windowの略。

開発:ブランニューっていい響きですけどね。まっさらな感じ。

基盤:省略形のあやかり臭がキツイですけどね。

社長:ただこれは、いまこの時点で新しいかどうかという一時的な問題では無いなと。もっと何か本質的な形容詞。Ultimateというのとも違うんです。そういう時間軸的なものとか比較では無い。そもそも昔からそうあるべきであった的な形容詞が良いなと。新しい足し算とか新しい万有引力では無いわけです。

開発:ベストプラクティスではなくてグッドプラクティスだというのを思い出します。

基盤:Onewというのはどこかで聞いたことがありますw

社長:本質の一つはウィンドウの再帰構造なのでたまねぎ、 OnionのOnを引いています。Nowは、N-Onion Windowとか。

開発:結局時間軸が折り込まれてるわけですね。

基盤:いっそWindows-Now とかどうでしょうね?

開発:残念。2013年に取られてますね。ああ、でもDEADってあります。

社長:その線で検討しましょう。

社長:あー、でも、原点回帰的な名前がいいですね。Wiindows Zeroみたいな。

基盤:Windows 2020とか、Y Window というのはどうでしょう。

社長:それも検討しましょう。

開発:作ってて思うのですが、デスクトップというのはウィンドウという表現の特殊系にすぎないので、これを名前にするのは違うかなと。対してウィンドウというのは本質的な表現形式の一つというか代表なので、これを名前に冠するのは良いと思います。

社長:本質という意味では、HTMLだCSSだJavaScriptだというのも違いますね。Golangについても。これらは実現技術のひとつに過ぎない。

基盤:いっそ「たまねぎビュー」で良いのではないでしょうか。

開発:MVC的に言えばDOMは「たまねぎモデル」ですかね。

社長:秘密のたまねぎとか疑惑のたまねぎとかも統合的に扱いたいですね。

基盤:黒柳徹子と光るたまねぎも。

Wirtual Desktop 0.0.3 第2ヒート

開発:あー、やれやれ。ようやくドラッグ先に止められるようになりました。

社長:どのようなブレークスルーが?

開発:style 属性で要素の配置を設定するプロパティは x, y ではなくて、left, top であるということに気がついた事ですね。

基盤:重大な気づきですね。

開発:明るい未来が見えた気がしました。

開発:実際、以前 GJ Consoleウィンドウを動かした時はそうしたんですが、なぜか今回は x, yにしてしまっていたのです。

社長:どれどれ、やってみましょう… …

社長:これ、動かすのに少しコツがいりますね。特に小さい表示の時。

基盤:掴むところの問題のような。

社長:というか、選択状態のようにも見えます。複数まとめて動いちゃったりとかします。まあ、そうしたい事もありますが。

開発:どうも、dragstart イベントの発生ターゲットと、その後の一連の drag イベントとの関連がよくわからないのです。consoleログを見ながら操作しないとw しかもそのあたり、Firefox は他と違うので、現時点では Firefox は未サポートということになります。リサイズ、ドラッグともに。

社長:でもまあ、原理的には出来たと。

基盤:でもコンソールログを見ながらというのはいかがなものかと。

開発:これは、ポジションやサイズに限らず、ウィンドウの全てのstyleプロパティを表示したり編集したりする窓を表示すれば良いかなと思います。窓の配置というかstyleの編集モードに入ったら、その編集用のスクリーンというか窓を最前面に表示する。

社長:まあ、窓というか枠というかエンベロープの属性に関心を持っているときはコンテンツは二の次ですからね。でも、半透明でコンテンツが見えてた方が安心感はあります。

開発:そうしましょう。

-- 2020-0929 SatoxITX

/* */ /* GShell-0.5.5 by SatoxITS
GShell version 0.5.5 // 2020-09-29 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.5" DATE = "2020-09-29" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
Web technology HTML: The Living Standard (September 2020) Developer Version CSS Working Group Editor Drafts (September 2020) MDN web docs HTML CSS : selectors repeat JavaScript HTTP Go language (August 2020 / Go 1.15) The Go Programming Language Packages WebSocket Stackoverflow
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /* */ /*
Live HTML Snapshot
*/ /*
Event sharing

Inter-window communicaiton

frame0 >>> frame1 and frame2
frame1 >>> frame0 and frame2
frame2 >>> frame0 and frame1




*/ /* // /*
Wirtual Desktop

Wirtual Desktop 0.0.3

Wirtual Desktop 0.0.3 2020/08/30 04:32:58
04:32
Reload
Reload
Reload
*/ //
// // /*
Work Template

Template of Work

*/ //
// /* *///

GShell 0.5.4 − Wirtual Desktop

開発:今日は久しぶりに良い天気ですね。

社長:筑波山のほうに良い雲が出ています。パシャ。

基盤;今日はこれを壁紙で行きましょう。

ヴィルト入ってる

社長:ああそれで、Wirtual という綴りについてですが、少なくともトルクメニスタン語ではそう綴るようです。

基盤:それってトルコ語のことですか?

開発:ならターキーでしょう。

社長:トルクメニスタンという国が実在します。

基盤:トルクメン人の国ですね。

開発:イケメンと違って力がありそうです。

社長:われわれの世代だと、Wがヴなのは、ニクラウス・ヴィルトです。Niklaus Wirthですね。

開発:Wirtual と Wirth は4文字まで一致します。

基盤:誰ですか?

開発:「アルゴリズム+データ構造=プログラム」の著者です。コンピュータ系の学生のバイブルでした。今も社内のどこかにあるはずです。大学の書籍部の棚で金文字の背表紙がこうごうしかったものです。ビルトの法則とかいうのもありました。

社長:まあ、我々の身近的には Pascal の作者ということですね。好き嫌いは別として、影響力のある言語+処理系でした。Cに染まってからは、ペケ言語の代表になりましたが。

開発:それからのパスカルっていう漫画のタイトルにピピっと来たのもそのせいですね。

社長:Appleの初期の作品も、Pascalで書かれてた記憶があります。筑波までデモに来ましたっけ。Lisaとか言って。しののめでしたっけ?

開発:Pascal?これ、ダメじゃね?と思ったものですw デモの人にツッコミ入れた記憶も。

社長:デモと言えば大学の4階に来た… PERQで画面に4枚の画像を出して、スムーズにシャッフルするデモには感動しました。あれもPascalですね。

開発:Xeyesのようなシンプルな訴求力ですね。今なら、透明画像を重ね合わせながらシャッフルすると面白いかもです。今ならわたしにも出来るw

社長:ビルトの発言として、どこかの学会で、あなたの名前はどう読めば良いのかと聞かれたというくだりが記憶にあります。たぶん bit あたりに載ってた。日本語版のWikiにも同様の記述が載ってますね。英語版にはないから、こういう発音系の話に敏感なのは日本人だからなのかなという気がします。

開発:どのみち日本語ではヴィルトもビルトですから、うちはヴィルトインを標榜すると良いかもですね。

基盤:略してヴィトンとか。

社長:インラインおよびビルトインはうちの重要なキーワードです。

開発:命名は Niklaus Wirth にちなむとか言ってみたりして。

社長:まだご健在のようですが。

基盤:びるといーんびるといん♪

開発:そうこうするうちに、12時を回ってしまいましたw

基盤:そういえば昨日のブログがまだクローズされてないです。

社長:おっと…

開発:Wirtual Desktopで疲れて寝ちゃいましたからね。

社長:〆て魚拓しておきます。

スマホ対応Wirtual Desktop

社長:スマホでWirtual Desktopがこんな風になっちゃって。

社長:スクロールすれば全景が見えますが、できればスクリーンサイズに全景が収まる表示モードが欲しいです。

開発:まあ、%指定にすればデスクトップ自体は自動的にぴったり納まるようになります。JavaScriptで計算してstyleを設定しても良いです。でも、やるなら中にあるWirtual Browserのほうも小さくするようにしたいですね。フォントを小さくするとか。

社長:それがうまくできると、ウェブページのライブサムネールができますね。最近の高解像度モニタなら8ptくらいの表示で十分読めます。

基盤:でもiframeに入れてるだけだと、ひとんちのHTMLとかCSSには手を出せないですよね。

社長:CSSに @media thumbnail みたいなのがあって、書く側の人がみんながそれをやってくれるといいのに。

開発:そのへんは結局、HTTPのリクエスト・応答のやりとりを自分でやらないとどうにもならないですね。かたっぱしから !important で無理やり小さくするw。いずれやりたいですが。

社長:要するにリアルブラウザがやってることをやらないといけない。

基盤:client-side CSSが使えれば良いのに。

開発:まあ道具は全部揃えてもらってますから、取ってきてちょっと加工して表示系に渡すだけじゃないかとは思うんですが。

基盤:そのへんはMessageEventである程度の指示をするみたいな規約はないのでしょうか?

開発:コンテンツの表示の扱いについてはほとんど標準化されて無いんじゃ無いですかね。Firefox独自で、iframeからfaviconを指示するメッセージが実験的にあるくらいしか見とことないです。

社長:そのへんの標準化が進むと良いのに。まあブラウザとしては囲い込みたいでしょうけど。

開発:結局のところキモは、ウィンドウ間のメッセージ通信における認証と権限管理。必要に応じて暗号化、とうところかなと思います。

社長:同一オリジンとかのシンプルなモデルに帰着させてしまってますからね。

開発:大きなサイトで同一オリジンとかあまり有効じゃないんじゃないかという気もします。

基盤:ユーザの代理権限で、プロキシでCSSを改竄しちゃうとか。

開発:same-originルールがCSSにも適用されるかですけどね。そもそもHTTPSだと、プロキシがコンテンツに介入できない可能性が高い。

社長:あー、ムカつく。macOSの画面共有とMission Controlがぶつかると、画面の縦サイズというかペイロードというかの認識がおかしくなっちゃって。縦スクロールして使えなくもないですが。

基盤:これって以前からそうでしたっけね?

社長:いったん普通サイズウィンドウに戻してまたデスクトップ化すれば治るんですけど。一連の手順に15秒くらいかかるんです。いっそこれの手順全体をショートカットキーにできないですかね。IMEに覚えさせてやらせちゃう。

開発:(1)リモートマシン画面からローカルマシンのミッションコントロールに戻す、(2)ミッションコントロール内の選択中のデスクトップを通常画面に戻す、(3)通常画面表示に戻す、(4)通常画面中に表示中のウィンドウをフルスクリーン表示に戻す。ですね。(2)をやるショートキーがあるかどうかですが。

基盤:ブラウザからGShell経由でマシン間のキーボードとマウスを連携させてできると良いですね。

社長:・・・IMEで実現するよりカッコイイですね・・・

開発:で、インラインフレームと親との通信の話ですが、メッセージでやるのが常道だとは思うのですが、画像でやる、つまりQRコードでやると面白いかなと。子フレームの表示領域を親は知ってるし、その中身は直接読めないにしても、表示している画像自体は何とか読めるわけです。

社長:わたしはこのフィリップス君の制御をバーコードでできたら良いのにと思うんです。特定の領域に特定のタイミングでバーコードを表示すると、モニターに対するコマンドになる。いたずらされると嫌なので、モニターから提示されるワンタイムな認証コードを使って。

開発:というか今日はボウリングの試合の日ですね。あと2時間半もありません。

基盤:がっくりして帰って来たら続きがない可能性がありますね。

社長:そろそろ片付けモードにしましょうか。

* * *

社長:ただいま。

基盤:今日はどうでした?

社長:ふっふっふ。

開発:良かったようですね。

社長:じゃーん。

基盤、開発:おーっ。パチパチパチ。

開発:で、この745というのは何ですか?

社長:ハンデ込みのスコアです。前回ボロボロだったおかげで現在個人順位最下位、ハンデは男子のリミッターで40ピンです。

開発:ある意味最強ですね。

社長:260ピンを超えるとハンデを合わせて300を超えるということで、最後はちょっと盛り上がりました。まあ、おそらく今季のCクラスでのハンデ込みハイスコア確定じゃないですかね。スクラッチの625もCクラスとしては立派なものです。

基盤:スコアは気にしてないと言ってましたが、出ればうれしそうですね。

社長:チーム戦で勝敗が掛かってるし、メンバーが喜んでくれますからね。誰かと喜びを共有したいというのはおそらく人間の根源的な欲求です。プロのいるチームと引き分けたのもうれしかった。

開発:プロは好調なんですか?

社長:前の試合ではパーフェクトしてましたね。マイナスのハンデをあげないと勝負にならない。今日も280から始まって、これはもう手に負えないと思っていたのですが、2ゲーム目途中から割れ始めちゃって。2−8-10のスプリットをバックアップボールで取ろうとして受けてました。

基盤:レーンを作ってる本人でもそうなるんですね。

社長:6人で投げてるとレーンはどんどん荒れますからね。3ゲームだけでも最後はかなりボロだと思いますよ。その点わたしのようなへなちょこフックだとレーンコンディションの影響が小さいです。

社長:ただそれよりも、今日は練習投球中に新しい投げ方を思いついて、最後にそれが思い通りにハマったのがうれしかったのです。

基盤:毎回何か思いついているようですが。

社長:最近ハマる確率が高くなった気がします。

開発:秘技白鳥の舞みたいなやつですか。

社長:まあ似たようなものです。私の場合要するにダウンスイングからバックスイングにかけてのリズムが問題で、ここがボウリングを始めてからずっと苦手なんです。出だしから台無しになってしまう。今日のイメージは、一言で言えば、サクっとやる。慎重にやり過ぎて振り子の自然な運動を邪魔しない。はじめちょろとろ中ぱっぱで、上死点のランチャー位置まで上がったらきっちりためる。それだけです。まあ、類似の事は何十回となく思いついては来たわけですが、今日は実際に実行できた。グリップの仕方との組み合わせがミソでした。それと呼吸法ですw

基盤:このところ睡眠不足気味で体調は万全では無かったかと。

社長:いつも食事しないで行って、途中でエネルギー切れになってへろへろしてダメになるので、今日は行きがけにサンドイッチを食べて行きました。セブンのハムカツサンドです。美味しかったですが値段が260円もすることに気づいて、愕然としました。おかげで試合開始時間ギリギリ着で、メンバーに心配掛けてしまいました。

開発:カップラーメンでお祝いしましょう。今日はサッポロ一番塩ラーメンで行きます。

基盤:具とかススープの後入れとかなんだかすごく抵抗感があるんですよね。ずずー。

開発:ずず。インスタント袋麺でもたしか粉末スープは後入れですよね。ず。

社長:ずず。これは、本物のラーメンじゃなくて、インスタント袋麺をカップ麺で再現しているところが面白いですね。安物にしてはフリーズドライの加薬もすごく良い。

開発:パクるの簡単そうな味ですが、これのパチモンて聞かないですね。ず。

社長:オリジナルが安いですからねえ。ず。

基盤:この軽いノリが良いと思います。本格志向ばかりだと飽きますよね。ずー。ごちそうさまでした。

社長:わが社には食品ロスというものがないですね。

iMac パニック

基盤:おっと、またしても iMac がパニック。前回、9/18と同じバックトレースのように見えます。とりあえずアップルに送信をぽちっ。

開発:diffを取ると、走ってるメンツも、Parallelsが一つアップデートしただけで同じですね。

社長:再現性があるということは、増設メモリの問題である可能性が低くなったということでしょうか。

基盤:MacMiniでも一時、知らない間にクラッシュしてリカバリしてた事が何度かありました。やはりブラウザをぱんぱんにしてた頃です。このiMacでは最近JavaScriptのテストでたくさんのブラウザから同時出力してウィンドウサーバがひーひー入ってることが多いです。何か排他制御的にクリティカルなタイミングを突いてしまっているのかも。

社長:スタックフレームでオフセットが違うのはわかる感じがしますが、プログラムカウンタのほうもオフセットが違うということは、コードを複製して実行しているということですかね?

開発:どこにロードされるかも起動された順番というかタイミングによるんではないでしょうか。

基盤:シンボリックダンプなら面白いのに。

社長:まあmacOSの中の人ならこれを見ればすぐに原因がわかるんじゃないでしょうか。

基盤:1a65dから先あたりでループしちゃってる感じですかね。エラーメッセージでぐぐって見る・・・ああ、以前からよく知られてる問題みたいですね。再現法まで書いてあります

開発:この2月に問題にされてるのに、まだ治ってないってどういう事ですかね?

基盤:今も続いてますね。9/24リリースのCatalina 10.15.7で治ったという人と、治ってないと言っている人がいるような。あー、この人はブチ切れてますね。

CMMChris macrumors 6502  #743

As explained like 100 times or so: This watchdog panic is a software issue. The bug was introduced with the release of first Navi GPU drivers. Hackintosh and Mac Pro users thus have been the first group to experience the panic.
...
Me and others have filed bug reports regarding this as well and - you guess it - no response from Apple. More than a year after initial Navi support, there is still not a single fix for this.
...

開発:Hackintosh www

社長:マルチコアCPUとかのシンプルな問題じゃなくてGPUとの連携の関係なんですね。

開発:おそらく Hackintosh だって問題は十分に把握しているんだと思いますが、直すのが難しい類の問題なんでしょうね。

基盤:あ、アップデートのお知らせがポップアップしました。

開発:うちのiMacはまさにこれですよね。

社長:それはそうと、10.15.7 が出ているというのは知りませんでした。Apple からプッシュされた記憶もないです。10.15.6 のマイナーフィックスの時は来てましたが。

基盤:寝る前にアップデートを走らせときます。

基盤:それはそうと、リブート後に iMac のファンがずっとうなってます。

基盤:何かとおもったら、先日も異常負荷で問題になったこのページでした。

開発:メモリを食ってるわけではないのですね。

基盤:リブート後の自動回復でおかしくなったのかもしれません。

開発:「を待機しています」って、GShellでもやらかした、インラインフレームに直接writeしてcloseしない状態を思い出します。

社長:iframe は使い方に十分気をつけないといけないですね。

基盤:この問題はchromeで、このサイトで起きたのが見つかりましたが、これ実は、iframeの内側にあるこれのせいじゃないでしょうか。aaaaaってやつ。

開発:これでしらばく経過を観察しましょう。

* * *

開発:ひー。ようやくまもとに表示できるようになりました。

社長:何が問題だったんでしょう。

開発:前にもハマったのですが、style に top や left を設定するには単位をつける必要があるということです。'px' をつけて解決しました。これ、間違った書式だよって、consoleに出てくれると良いのですが・・・

-- 2020-0928 SatoxITS

/* */ /* GShell-0.5.4 by SatoxITS
GShell version 0.5.4 // 2020-09-28 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.4" DATE = "2020-09-28" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
Web technology HTML: The Living Standard (September 2020) Developer Version CSS Working Group Editor Drafts (September 2020) MDN web docs HTML CSS : selectors repeat JavaScript HTTP Go language (August 2020 / Go 1.15) The Go Programming Language Packages WebSocket Stackoverflow
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /* */ /*
Live HTML Snapshot
*/ /*
Event sharing

Inter-window communicaiton

frame0 >>> frame1 and frame2
frame1 >>> frame0 and frame2
frame2 >>> frame0 and frame1




*/ /* // /*
Wirtual Desktop

Wirtual Desktop

Wirtual Desktop 0.0.2 2020/08/29 14:00:48
14:00
Reload
Reload
Reload
*/ //
// // /*
Work Template

Template of Work

*/ //
// /* *///

GShell 0.5.3 − WWWirtualデスクトップ

社長:今日は仮想デスクトップを作ってみたいです。

開発:いきなり最終目的地ですか。

社長:そろそろモックアップを作るくらいの素材はそろったかなと。目的地の景色を描いてみたい。

基盤:今のタイトルは「そうだ、1Noteを作ろう」になってますが。

社長:寝る前に、今日はそれで行こうと思って、メモして置いたんです。

開発:OneNote のHTML版ですかね。

基盤: 名前は WanNote とか、WAN-Note とかがいいんじゃ無いですけどね。中国語で王帳とか汎帳とか。

社長:検討しましょう。それはともかく、わたしは当時 Office 365 で色々面白いWebアプリを見たのですが、中でも超絶面白かったのは OneNote だったわけです。で、あれの簡易版ならHTMLとJavaScriptだけで出来そうだと思いました。

開発:まあ実際、ウェブ版OneNoteはそうして実現してるわけですね。

社長:で、共有はDOM - HTMLの保存回復機能で、同時編集は WebSocket 上で MessageEvent でDOMのオブジェクトを飛ばしてやれば良いと思います。DOMをシンクロさせる。

開発:できそうです。

社長:でもせっかくだから、ぺたぺた貼り付けるのはパッシブなノートじゃなくてアクティブなウィンドウのほうが面白いかなと。

開発:iframe ですね。

社長:Firefox の回復異常の結果、フレームの中で GShell のバナーが流れているのを見て、強くそう思いました。で、これって要するにデスクトップだなと思ったのです。

開発:まあ制御できるアプリがウェブ窓に限られますけど、結構使えるものができるような気もします。

基盤:たまねぎ化して管理できるとありがたいです。

開発:階層的に管理できるデスクトップは是非欲しいです。

基盤:脱Mission Controlですね。

社長:で、これはいずれにしても仮想的なデスクトップですから、バーチャルなんですが、virtual という単語が vi で始まっていることに思い当たりました。なのでこれを目立たせたい。vi-rtual desktop とかどうだろうって。

基盤:Moz://a みたいに、V:rtual でも良いかもですね。

社長:検討しましょう。

開発:Wも言語によってはヴと発音しますよね。起源が double V ですし。なので、VVirtual Desktop とかどうでしょう。

社長:検討しましょう。でもそれなら、WWWirtual Desktop のほうが良いかな。Viがなくなるのは残念ですが。省略系は Wirtual Desktop、WD。

基盤:ウェスタンデジタルみたいですね。

開発:今日もよた話に熱中して終わってしまう危険を感じます。手を動かしましょう。

基盤:本日の壁紙はこれでどうでしょう?Azuma3のほんとうの空。

開発:賛成。

社長:やはりほんとうの空の写真は良いですね。

壁紙

開発:やはりデスクトップの壁紙は重要だと再認識しました。ではまず、Wirtual Desktopに壁紙を貼りましょう。こんな感じですかね。

社長:黒枠にRetinaとかトリニトロンとか書きたいですね。

基盤:画面の下にシルバーの袴が無いですね。

開発:draggableにしてあるので持ち出し可能です。

社長:メニューバーにドロップしたら新しい仮想デスクトップにフルスクリーンで開くと良いですね。

基盤:ブラウザからブラウザへのコピーはこれでやれると良いですね。

社長:ルーペも作りたいですね。

開発:次はランチャーというか、メニューバーを。

社長:ちょっと待った。お腹がすいたので何か食べましょう。

開発:最近ブームのレトルトカレーにしましょう。

基盤:この美濃焼の丼は、アマゾンに載ってた写真そのまんまでですね。以上でも以下でも無い。はふ。

社長:やはり内側は良いのに外側がさみしい。はふはふ。

開発:糸ジリが大きいこともあってか、さわって熱く無いですね。はふ。

社長:レトルトカレー用にはもうひとまわり小さいと良いですね。ごちそうさま。

基盤:カレーの後だし、紅茶でも。

社長:やはり、リアルワールドに永くあるものに似せることは重要ですね。わかりやすさが全く違う。

基盤:テレビっぽいので模擬リモコンもあると良いかもですね。

飛び出すウィンドウ

開発:さて、それではランチャーを。

社長:ちょっと待った。モックアップだし、見た目をもうちょっとやりましょう。このWirtual Desktopに親ページを映してみたいです。

開発:そうですか。

* * *

開発:なぜかiframeのスタイルが設定できなくて不調です。

社長:自分でsrcdocの中身を作る場合と違って、srcの場合は制約が強いんじゃないでしょうかね。

開発:今のところ、とりあえず、こんな感じで。

社長、基盤:おおーっ。

開発:外側がWirtual Desktopなので、内側もWirtual Browserと呼ぶことにします。

基盤:これはやはり、ディスプレイの下に足というか台が欲しいですね。

社長:電源スイッチとパイロットランプなんかもあると良いですね。

基盤:明るさ調整スライダーとかも。

開発:ウィンドウにつきましては、最大、最小の他に、通常の大、中、小、を設定できるようにしたいと思います。

社長:背景をいずらしたらり拡大縮小して動画みたいにみせると面白いかもですね。

基盤:フチの部分にてかりとか反射の質感があると良いかもですね。

開発:「親」を自分に映しているのですが、Wirtual Desktop の中の Wirtual Browser にその情報を渡していないので、無限に再帰はしていません。なので、画像でうめ草を。

社長:これで万華鏡を作ると面白いですね。

基盤:やっぱりリモコンは欲しいですね。

社長:少なくとも画面の外に制御部が欲しいですね。動画の再生制御が画面内にあるのはほんとうに邪魔です。

基盤:リモコンを別ウィンドウのフレームに置けるとか。

社長:タスクバーにあっても良いですね。

基盤:要するにコントローラは元々分離しておいて、必要なら制御対象に位置的にくっつけられれば良いということですね。

開発:リモートのgshからコマンドで制御できるのも良いかと思います。

基盤:見た目が iPhone なエミュレータがあると良いですね。

社長:これ、仮装デスクトップて呼ぼうかな。

社長:仮装ブラウザをもう一枚入れるとどうなりますかね。

開発:こんな感じですね。

基盤:おー、それっぽいですね。

開発:ここで、リアルのスクリーンでは絶対できない技を一つ。

開発:はみだしの術!

基盤:飛び出す絵本。

開発:思うに、ここってまあじゃん放浪記入ってますよね。

社長:もともとそういう意識です。

デスクトップたまねぎ

社長:この仮想デスクトップ自体はiframeでは無いということですね。

開発:今は単なるdivですが、そこはどうとでも。とにかく、内容物の通信基地と座表起点ではあるけれど、位置的な縛りは緩いという感じの造りにしたいと思います。

社長:ポストイットモデルですね。

基盤:普通のアプリって自分のウィンドウに閉じ込められてるのが悲しいですね。外に広大なデスクトップがあっても。

社長:デスクトップとブラウザという別の階層を作る必要はあるでしょうか?

開発:なくても良い気はします。全部が仮想デスクトップ、あるいは何でもブラウザでも。たぶん何でもデスクトップとしておいて、一つのURLの表示だけに特化された、つまり子フレームが一つしかなくて内部全画面表示の状態が、ブラウザ「状態」、というのが良いのではないかと。動的に遷移できる。クラスとして分けない方が良いような。

社長:要するにDOMのwindowというクラスそのものですね。

開発:まあ、windowのデフォルトのdocumentが第0 iframeだという風にできていれば、まったくそのままですね。

基盤:たまねぎデスクトップですね。

社長:デスクトップオニオンという名前にしようかしら。

基盤:この仮想デスクトップはresizableなので、一つで広大なデスクトップもできるわけですね。100万 x 100万ピクセルとか。

社長:Pointillismで見た夢が実現しそうです。

開発:あとは、ウィンドウのメンツやサイズや位置を決めたら、現在のDOMをHTMLとして保存して、後で呼び出せるようにしておけば、カスタマイズ出来るデスクトップということになると思います。

社長:ちょっと休みましょう。忘れないうちに、WirtualDesktop、ドメイン登録して置きます。

基盤:WWWirtual じゃないんですか?

社長:やはりかったるいような。

* * *

開発:ふうぅ。なんだかどっと疲れました。Wirtual Desktopの現状です。今日はこのへんで店じまいかなと。

社長:ブラウザの互換性の問題が色々出てきましたね。

開発:一つのブラウザでだけやってたら、ハマるとこでした。

社長:Firefox も Safari も独自路線でやってて何の意味があるのと思ってましたが、開発する立場から見るととても有益ですね。

開発:利用する側から見た仕様はともかく、基盤の実装が複数あるのは助かります。

社長:ウェブ技術は今も生々しく発展中って感じがしますね。

-- 2020-0927 SatoxITS

/* */ /* GShell-0.5.3 by SatoxITS
GShell version 0.5.3 // 2020-09-27 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.3" DATE = "2020-09-27" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
Web technology HTML: The Living Standard (September 2020) Developer Version CSS Working Group Editor Drafts (September 2020) MDN web docs HTML CSS : selectors repeat JavaScript HTTP Go language (August 2020 / Go 1.15) The Go Programming Language Packages WebSocket Stackoverflow
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /* */ /*
Live HTML Snapshot
*/ /*
Event sharing

Inter-window communicaiton

frame0 >>> frame1 and frame2
frame1 >>> frame0 and frame2
frame2 >>> frame0 and frame1




*/ /* // /*
Wirtual Desktop

Wirtual Desktop

Wirtual Desktop 0.0.1 2020/08/28 04:18:24
04:18
http://im3/gsh/gsh-0.5.3.go.html
https://its-more.jp/ja_jp/
../gshell/gsh.go.html
*/ //
/* *///

GShell 0.5.2 − ブラウザ間通信

社長:今日はいよいよ、ブラウザ間通信ですね。

開発:昨日からやってて思うのですが、やはり Go より JavaScript のほうが手軽。ブラウザの別ウィンドウより単一ウィンドウの中の iframe のほうがやりやすいです。なので、GJ Link の大枠はこれで開発して、WebSocket の listen が必要な部分と、バックエンドのリソースへのアクセスが必要な部分だけ Go に飛ばすのが良いかなって。

社長:両方を同じクラス名、同じメソッドにして、トランスペアレントに通信できると良いですね。

基盤:オブジェクトの名前で内部のノード限りか、Goまで出ていくものか判別してルーティングする感じでしょうか。

社長:Go側からJavaScript仮想マシンに入ってくる時も同じようにできると良いですね。

開発:クラスのオブジェクトをシリアライズして、Go と JavaScript 間で自由に飛ばせると良いなと思います。

基盤:Js - Js, Js - Go, Go - Js, Go - Go が全部同じようにできると。

社長:そうなると、相手が別のブラウザかどうかとか、パラメータの一つに過ぎなくなりますね。

ブラウザ間互換性

開発:今日の本題に入る前にちょっと昨日の整理ですが。Safariで子供のフレームのウィンドウにメッセージが送れない件は、というか、contentWindow が null になってしまう問題は、ページを file URL で開いた場合だけのようです。

基盤:ぱっと見、origin が設定されないとダメな模様。

社長:JavaScript で origin を設定できるとしたら、cross-orgin アクセスのプロテクションはどうなるんでしょうね?

開発:関連して面白い現象です。このブログに埋め込むとこういうことになります。

開発:このページを表示すると速攻でGoogleから何かメッセージイベントが飛んできます。ページの中にある reCAPTCHA の iframe からのものかもしれません。

社長:自分と同じレベルにあるフレームにはメッセージを飛ばせるということですね。

開発:互換性に関して残念な話。Firefox はiframeの resize をサポートしてないか、流儀が違うもうよう。iframeに対するCSSのresize:both が効きません。

開発:あ、また死んだ。

社長:再現条件を特定してレボートしたいですね。

基盤:最新版でやりたいですね。

開発:いや、一応最新版のようです。

開発:リロードボタンがバッテン印になってて、何かの通信終了待ちのまま終わらないという症状もあります。これはまあ、Safari以外では皆さんそうです。これも、file URL の場合に限るようにも見えます。HTTPサーバ経由なら発生しません。

基盤:Safari だけ file URL で開いた時の振る舞いが違うという意味では一貫してますね。

社長:実は file URL で開いた時には origin が操作できるとかいう話だとすると、すごく可能性が開けるんですが。パスワードマネジャーとか。

開発:一番信頼できるオリジンですからね。

開発:ところで機能からこれをやってて、これまで多用して来た textarea をやめて、iframe に移行しようかなと思っているところです。リサイズできるし、当然フルセットのHTMLでできるから、editableにできるし、スクロールもできる。閉じ込めもできる一方で、同一オリジンであれば外から自由に読み書きできる。

社長:午後のために昼食でパワーを注入して来ます。

GJドメイン

社長:ただいま。壁紙にいいかなと思って何枚か撮ってきました。

基盤:デスクトップの壁紙にして比べてみます。

開発:さすがに実物の壁は壁紙にしてもはまりますね。今日はこれで行きましょう。

社長:それから、帰ったら郵便受けのところに大家さんから栗のおすそ分けが。

基盤:お待ちかねでした。

開発:でも今年の栗は小さめですね。

社長:今年はほとんどの農作物が不作だったみたいな。7月頃涼しめでエアコン要らずだったのですが、要するに天候不純だったんですね。

基盤:でも、短期に育ちそうな野菜なんかも未だに高いようです。コロナの影響も重なってるんですかね。

社長:それで、食事への道すがら考えたのですが、このシステムは GJ Onion と言う名前にしようかと。GJ Union とかから考え始めたのですが、ユニオンよりオニオン、玉ねぎのほうがいいかなって。

開発:ブラウザユニオンを目指すと言うととおこがましく聞こえますが、ブラウザオニオンというとなんだかわからなくて微笑ましいと思います。

基盤:疑惑のデパートを経営しているイケメンのようです。

社長:で、その中に存在する点というか階層的実体は階層的ドメイン名で識別する。仮想的なTLDは .gj とする。でどうでしょう。

開発:Onion というと Tor みたいですね。

基盤:.gj という実在の tld は無いようですので、その点では問題ないかと思います。

開発:xso で TLD が取れないですかね。1000円くらいで(笑)

経理:今日23時59分まで250円とか。

社長:既存のURLと互換にしてスキーム名を受け継ぐ、ドメイン名の部分だけ .gjで終わる、という感じかなと思います。

広報:.GJ入ってるマークを作ってみました。

開発:GShell入ってるマークより良いかもですね。

社長:Georgia は完璧なフォントだと思ってたんですが、こうやってみるとJが少し痩せてますね。

社長:おっと、タバコが切れたので買ってきます。

スペーストレンチ

社長:これにはネットでもウェブでもクラウドでもない言葉を充てたいですね。

開発:モグラのトンネルみたいなイメージですね。Gopherの。

基盤:トンネルも使い古されてますね。

社長:トレンチでどうかな。塹壕。表舞台には見えない地下の連絡通路みたいな。ワールドワイドとかじゃなくてスペース。スペーストレンチ。

基盤:Googleで検索。そういう言葉は無いようですね。海溝なら ocean trenchですが。

開発:空間の溝、宇宙の溝、・・・チューブワームが住んでいそうな。

社長:よし、久々にドメイン名をとりましょう。

経理:比較的最近に chimella で取ってます。

社長:おやー、デフォで .com が240円になってますが… じゃ、これとこれとこれで。

社長:ぷちっと。…え゛

経理:ありえない。

基盤:足し算の精度が低過ぎます。

開発:ハッシュでもしてるんでしょうか?ああ、.net が表示されてないですね。たぶん、1280円x2 ぶんの .net が表示されてない。

基盤:この料金設定って、ハイフンがすごく高くなったとかですかね?

社長:削除、削除、うーん、削除するとまた何か湧いてくる。

開発:真空のエネルギーですか。

社長:削除、削除。これでどうですかね?

経理:問題ないと思います。

社長:では会計へ… あれ?Safariにはパスワード教えてなかったです。Vivaldi から入り直して… さっきの人のを横取りと。ぷちっ。

基盤:ドメイン名は早い者勝ちですからねw。ネームサーバ変更しておきます。

開発:母音に濁点をつけるのを見たのは弓月光が最初だったと思います。

社長:あれは確か、高校受験前の夏の合宿みたいので、他の学校から来てた子に単行本を借りたんです。おでんぐつぐつと新婚は甘く無い。

基盤:1973年の作品ですね。

開発:返しましたかね?

社長:記憶にありません。

開発:弓月光と言えばやはりエリート狂走曲。1977-1978年。これは掲載リアルタイムで読んでいた可能性がありますが、週刊は買ってなかったようにも思います。

基盤:単行本を社内で見かけた記憶があります。

社長:卒論の時の研究室の棚にあったような。「天上天下唯我独尊の唯」ってセリフがありました。小女誌掲載でしたけど、あれは女の子より男の子にウケるタイプの漫画だったように思います。

開発:Wikiに「神戸大学を落ちて漫画家になった」とあります。そのあたりの経験が何か生きてるんでしょうね。

経理:毎月の年貢の請求が来ているのでペイジーで行きます。

開発:自動振替にできないんですかね?

基盤:うちの銀行では年金は出来なかったような?

開発:このログイン手続き、最初のフィールドが自動fillされないようにしてるのはわざとでしょうね。パスワードマネージャは覚えているんだけど、一段階ユーザの関与を噛ませている。

経理:でもこの銀行は、トークンからワンタイムパスワードを入れるのが楽しくて。ぷち。ぷちっ。終了。

基盤:法人口座を置くのにはもっと恥ずかしい銀行名になるんだとか。

社長:昨日の電話で税務署の人も最初、鼻で笑ってたような空気がありましたw

開発:以前、地元の銀行にも法人口座を持ちたいという話がありました。

基盤:しかし田舎の銀行でこういうしっかりしたネットワークバンキング機能を提供しているとは考え難いです。

経理:不定期のパートキャラなのでネットバンク以外使いたくないです。

社長:月に一度1分。年貢を意識して楽しく納めるセレモニーというのは、悪く無いと思います。あと、協会や機構からのお知らせも結構面白い。

基盤:でも、紙でなくてもメールで良いですよね。あるいは、サーバにログインして読めるとか。

経理:今月のお知らせは「標準報酬月額の上限が改定されました」ですね。

社長:おいらにゃ関係ねえ。

開発:ところで大家さんの栗ですが、ここは一つレンジでチンに挑戦したいと思います。

基盤:爆発しますよね。

開発:ナイフで穴を開けてやって低温設定でやったらどうかなと。今日来たナルミのレンジ対応ボウルに3粒入れて。えー、75度で5分。ぷちっ。

レンジ:Go ...

社長:以前にもトライして失敗したような。デジャヴですかね。

レンジ:Go ... ボンッ!

基盤:やはり。

開発:緊急停止!あー、庫内が大変な事に… でも、爆発したのは 1/3。それも、残った部分が結構食べられます。うん。実験続行。ぷちっ。

レンジ:Go ... ... ... ぱぺぽ、ぱぺぽ、

開発:完走です。どれ。あちちっ。ムキやすくするためにも、もっと大きく切り込みを入れてからチンしたほうが良いみたいですね。うん。美味しい。

基盤:ホクホクしてますね。

社長:天津甘栗なんかとは違ってさっぱりした甘みです。ごちそうさま。

基盤:でも、普通に茹でた方が良いと思います。

開発:ガスだと温度x時間調理が… きっと忘れますし。

社長:引っ越した時にどこかに隠れちゃいましたが、IH調理器がどこかにあります。

基盤:鍋をやるには必須ですね。

* * *

開発:おや、もうこんな時間。今日も残りわずかとなりました。

社長:でも色々考えはしました。人間は考えるアシモです。

基盤:アシモは腰がスッキリのびましたかね。

開発:杖をついて歩くロボットがいたら、それはそれでハイテクな感じはします。

基盤:道具を使うロボットですね。

社長:やはり人型ロボットというのはそれだけで魅きつけるものがあります。

基盤:人形にディスプレィ貼り付けただけの手抜きですら。

社長:iPhoneでGoogleがゴリ押ししてくるので時々見るのですが、F1ホンダエンジンもすっかり復活してるようです。

開発:最先端技術のキャッチアップには時間がかかるものですね。復帰当初は悲惨だったようですが。

社長:あれは、日本の技術とか体制とかもうダメなんじゃないかっていう気分にすらさせました。

開発:再来年以降は未定のようですが、ぜひ続けて欲しいです。

基盤:老人搭載のミサイル車とか、会長が乗り込めるトランクの会社とは、目指している方向が違いますよね。

社長:私が日本車を買うとしたらホンダか、fan to drive マツダですね。

経理:でも買わないですよね。

社長:エンジンで走る車って、我々の世代で乗り納めかもですね。

経理:国土交通省で自動車燃費一覧… リッター10km走らない車って、存在が許されなくなると思います。

開発:明日はロシアGPの決勝のようです。

社長:ロシアで開催するようになってたんですね。

開発:ソチ跡地ですか。今も鈴鹿ではやってるんですかね。

基盤:コロナの影響で中止になった模様です。

バグ修正

開発:iframeを開いたらブラウザがいつまでも何かを待ってくるくるしている原因がわかりましたので、修正しました。全6ブラウザで一斉テスト。

基盤:回って無いですね。

社長:で、原因は?

開発:iframe 表示するデータを渡すにのに、srcdoc属性の値としてではなくて、iframe.contentWindow.document.write() で直接書いてた関係です。書いた後に、document.close()してみたら直りました。でMDNを見たら、こう書いてありました。

開発:srcdoc属性で与えると、コンテナ側のDOMの値に反映されることになります。これは、DOMのダンプをとって再生したい時には便利ですが、ソースの状態を再現したい時には邪魔になります。

社長:場合によって使い分けですね。

開発:なんにしても、開いたままのdocumentが溜まっていくと、FirefoxとSafariはおかしくなるようです。あるページから開いたままのdocumentがある状態で、そのページをリロードした時に、それを回収してないのではないかという疑いがもたれます。今のところ、Chromium系では異常は起こらないように見えます。もっとも、これまで試験に使ってきたのが主に Safari と Firefox だったということはあります。

開発:ただ、Firefox はリロードとかリカバリの際になにかしら異常な振る舞いをします。さっきも、このインラインフレームの中にsrcdocではなくて、src="" と勘違いしたのか、自分自身を表示していました。

開発:面白かったので録画しようとしたのですが、そのまえにちょっと画面サイズを整えようとしたとたん、お詫びの画面がでて再起動してしまいました。面白かったので、その状態にするボタン「ShowSelf」をつけてみました。

基盤:これって、ロード時に自動的に、再帰的に自分の中に自分を開くようにしたら死ぬんですかね?

開発:面白いと思いますが、まなまじリカバリ機能のおかげでその状態が永続化されちゃうと業務に支障をきたしますので、見合わせます。

社長:でも、普通にやっちゃいそうなことでもありますし、対応はされてるんじゃないですかね。

基盤:何にしても、JavaScirptでリソース枯渇させてブラウザを落とすとか、普通にできちゃう気がするのですが、それってどうやって防御してるんでしょう?

開発:こないだの暴走事案の時にもそれを見ましたが、防御してないようにも見えますね。あれはCPU食いまくり事案でしたけど、昔からメモリ使用量がギガバイトに達してるタブとか時々みますし。

社長:ブラウザはおかしくなったら再起動すればいいって感じの位置付けのアプリだったと思いますが、重要なデータの変更中におかしくなったりすると嫌ですよね。リカバリー機能も台無しというか、逆に異常を永続かしてしまって害を及ぼしてしまう。

開発:おっと、もうこんな時間に。今日の主題に入れずに終わってしまいそうです。

社長:また明日、ですね。

開発:ところで、WordPressのカスタムHTMLにgsh.go.htmlを貼り付けると、-- とか ... とか " が置き換えれてまって、Goのコードとしては壊れてしまっていることに気づいていたのですが、この変換をやめさせる方法がわかりました。

社長:これは、コード以外にも害があったので助かります。

基盤:なんでこんな書き換えをするんでしょうね?

社長:IMEを使わない人たちのための入力支援かもですね。

基盤:WordPressとは色んな面で前提というか考え方が違う気がしています。

社長:まあいずれ、さようならする日が来るんだと思います。でも、違う世界の中に租界を作るってのも面白いと思っています。

開発:WordPressから壊れてないデータが取り出せるようになりましたので、実際これをソースプログラム配布用のメディアの一つとして使うことが現実的になったと思います。となるとあとはやはり、コードに対する署名ですね。

社長:今の流れだと、指定したHTMLのエレメントをGolang側に送って署名を返してもらってHTMLに埋め込むって形になるでしょね。

開発:そういう形になるとは、これを始めた頃は想像しませんでした。

社長:おー、このページSafariで印刷したら、面白い位置にGShellのスクリーンがかかってます。

開発:現在画面に表示している位置をy=0にしてるんですかね。

基盤:面白すぎる。

-- 2020-0926 SatoxITS

/* */ /* GShell-0.5.2 by SatoxITS
GShell version 0.5.2 // 2020-09-26 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.2" DATE = "2020-09-26" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
Web technology HTML: The Living Standard (September 2020) Developer Version CSS Working Group Editor Drafts (September 2020) MDN web docs HTML CSS : selectors repeat JavaScript HTTP Go language (August 2020 / Go 1.15) The Go Programming Language Packages WebSocket Stackoverflow
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /* */ /*
Live HTML Snapshot
*/ /*
Event sharing

Inter-window communicaiton

frame0 >>> frame1 and frame2
frame1 >>> frame0 and frame2
frame2 >>> frame0 and frame1




*/ /* *///

GShell 0.5.1 − ウィンドウ間通信

社長:おなかパンパン…

経理:焼き魚臭い。

社長:ようやく日本人のやってる店が再開しましたね。今日はオススメのかますの一夜干し+しらす丼です。あの店では初めてビンじゃなくて生大。

開発:生の注ぎ方にはうるさいんですよね。

社長:まあいいじゃないですか昼間だし。キノコ臭がしなければ合格です。

社長:で、今日は雨で空模様がつまらなかったので、地上の写真を撮ってきました。

基盤:壁紙にしてみましょう。2案。自販機 vs 庭のローズマリーです。

開発:ローズマリーにしましょう。

税金滞納インシデント

携帯:うーん。うーん。うーん。

社長:はい。あ、どうもお世話になります。はい。あ、そこんとこがよく分からなくてですね。せっかく送って頂いたマニュアルも、その時は見当たらなくて。あ、なるほど。そういうことなんですね。はあ。ほう。へえ〜。ところで間違って納めたぶんはどうなるんでしょうか?はあ。 … … … …。わかりました。早速 e-Tax でやっておきます。ぶちっ。

基盤:ずいぶん長電話でしたね。

社長:税務署からでした。耳寄りな情報です。

  • 4月に申請した6ヶ月まとめ払い特例は5月ぶんからの適用となる。つまり4月分は5月に払っていなければならず、現在延滞している状態にある。
  • ただし、1万円までは延滞金はかからない。
  • 他で働いていなければ税額表の甲の扶養親族ゼロが適用できる。
  • 払い過ぎたぶんはこちらの口座に戻してくれる。

経理:いろんな疑問が氷解しました。

社長:ただ、戻してくれる手続きの担当は別の方だということで、手順を相談しますとのこと。

携帯:うーん。うーん。

社長:はい。あ、お世話になっております。はい。えーとペイジーで払いましたので、同じ口座に戻していただければと。え?そうなんですか。でもうちは法人口座がネットバンクにしかなくてですね。はい。… … …。そうですか。それは助かります。

社長:いえ、ウェブ版e-Taxはよく出来ていると思うんですが、ちょっと用語と入り口が分かりにくいかなと。実はすごく簡単なんだという事がわかるまでに時間がかかりました。はい。では早速そのように手続きしていおきます。はい。ありがとうございました。ぷちっ。

社長:払い過ぎてる分を差額として戻すのはちょっと手間なので、間違った納付はちゃらにして、さらからやり直しましょう、という事でした。

経理:そもそも入り口を間違っちゃったから、実はまだ申告してない状態とも言えます。やっておきます。

社長:案の定、税務署の中の人も非常に親切ですね。社長って呼んでくれるし (^-^)/

基盤:ライトセール代程度の納税者に対してありがたいことです。

社長:ベーシックインカムは超えていると自負しています。タバコ税も払っているし。

経理:役員報酬ゼロなら手間が少し省けると思うのですが。

開発:そういうコストに合わない指導や相談のためにどんだけ税金が失われてんだとも言えます。

ブラウザ間マウス共有

社長:それで食事しながら考えたんですが、複数のブラウザで同じページを開いて、マウスとキーのイベントを共有できたらいいかなって。

基盤:ウィンドウのサイズが一致してないとおかしなことにならないでしょうか?

開発:いや、マウスがどのエレメントのどの座標にあるかっていう論理的な情報を送ってやれば、現在地の共有はイケると思います。もっとも、スクロールとかすると大変な事になるかもしれませんが。

社長:複数のマシンの複数のディスプレイの間をマウスがシームレスに飛んでくれるとよいのにとずっと思っていたのですが、それの前段階が実現出来そうです。面白いですね。ただ、ちょっと眠くなって来てしまいました…

* * *

社長:ふあぁ。すっきりしました。再開しましょう。

開発:まずは、イベントの生成ですね。そのまえにWordPressに埋め込んだ時の表示の調整を…

開発:やれやれ、こんなとこですかね。

基盤:なんだか和風の壁紙がとてもマッチしてますね。

* * *

開発:あ、もうこんな時間になってましたか… ようやく実験の仕掛けができました。まずコードはこう。

実験1

開発:見た目はこうです。

社長:なるほど、iframeはwindowなんですね。まあそりゃそうか。

開発:それぞれのフレームの中でクリックされると、それを自身のテキストとして記録するようにしています。

開発:で、自分で起きたイベントを相手に送って、相手に自分で起きたと思わせられたら、成功ということです。

基盤:つまりwindow間でイベントを送る実験ですね。

開発:で、違うブラウザ間のインスタンス間でこれをやるには GJ Link でやるんだと思いますが、一つのブラウザのインスタンスの中ではもっと直接的に送れるに違いないと思ったわけです。で、MDNで検索すると、postMessage() を使えということでした。

開発:問答無用では送り込めないけど、受取り手が message event をlisten して受け取って処理する分には問題ないということと思います。

社長:福音ですね。

開発:しかもこうです。

社長:至れり尽くせりですね。

開発:まあとりあえずの実験ではクリックイベントだけ受け取れれば良いと思います。作るだけですね。やってみる必要もないくらいです。

社長:ところで、iframeは親というかホストウィンドウの中にあるので、iframe の上空でクリックイベントを捕まえて、解釈して、加工してからiframeに送る、ってできるんでしょうか?

開発:それがちょっとやってみたところでは、mouseover とかはホストで捕まえられますが、clikckとかはダメなように見えます。・・・あれ、そうじゃなくて、ゲストがlistenしていると、そっちに取られるようです。

社長:例の、上空に透明シートを被せてブロックするという手段は?

基盤:空を飛べるわけじゃないし時を止めることも出来ない♪

開発:クリスタルケイはまじもんの天才歌姫と思いましたね。今はどうしてますかね。

基盤:今34歳ですね。大ヒットはないけど普通に活動してる模様。

* * *

開発:うーん、親子のウィンドウ間での通信はわかるんですが、兄弟とか他人のウィンドウとの通信方法がわかりません。message event に window object の参照をシリアライズして送るとかできるですかね・・・

社長:とりあえず親が中継すればいいんじゃないですかね。制御も効くし。GJ Linkで中継するのとシームレスだし。ちょっと休みましょう。

基盤:久々に辛辛魚で!

開発:これ、すごく注意しても、絶対むせますよね。

社長:げほっ。

基盤:ぐふ。

開発:けほっ。・・・

社長:これは一種の戦いですね。ふー、汗かいた。

* * *

開発:ふう、だいたいこんな感じですかね。

開発:なにやらデータ転送中のようになる問題はあります。開発者ツールのネットワークでは何も待ってないようなのですが。

開発:message event を listen している事の影響なのだとは思います。

開発:あと、6大ブラウザのうちで Safari だけがおかしい。

開発:子から親、window.parent へは送れるんですが、親から子、iframe.contentWindow へは送れてない、とうような感じです。で、開発者ツールで見たら、こう出ていました。

開発:で、もしやと思ってMDNでcontentWindowの項をみたらこうなってました。

基盤:さすがMDN。

開発:でも、もう Opera では動くので、この情報は更新してもらったほうが良いですね。

社長:準備は整いましたね。明日、本実験をやりましょう。

開発:まあ、JavaScript でブラウザ内できることは分かりましたので、あとは GJ Link経由でブラウザを跨った通信が焦点ですね。

基盤:ウェブサーバ経由でもできるんじゃないでしょうか?

開発:できそうな気がしますが、となりにいるブラウザにWebSocket経由でなら1msで通信できるのに、サーバ経由で10msも20msもかかるのはどうかと思います。しかも、ウェブサーバに知られずに通信したい事もあるし。

-- 2020-0925 SatoxITS

/* */ /* GShell-0.5.1 by SatoxITS
GShell version 0.5.1 // 2020-09-25 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.1" DATE = "2020-09-25" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
Web technology HTML: The Living Standard (September 2020) Developer Version CSS Working Group Editor Drafts (September 2020) MDN web docs HTML CSS : selectors repeat JavaScript HTTP Go language (August 2020 / Go 1.15) The Go Programming Language Packages WebSocket Stackoverflow
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /* */ /*
Live HTML Snapshot
*/ /*
Event sharing

Inter-window communicaiton

frame0 >>> frame1 and frame2
frame1 >>> frame0 and frame2
frame2 >>> frame0 and frame1




*/ /* *///

GShell 0.5.0 − HTMLスナップショット

社長:GShellのキラーアプリってなんでしょうね?

開発:ここのところの流れだと、WebSocket経由でブラウザ間が通信できるって所がインパクトがあるかなと。

基盤:DOMの魚拓をHTMLにする機能と組み合わせて、ウィンドウの現状を別のブラウザに複製するってのはウケるのではないかと思います。

社長:今日はそれで行ってみますか。

基盤:仕事を始める前に壁紙を変えるというのはどうでしょう?ちょっといつもと雰囲気を変えて、こういう感じとか。

開発:ああ、これはターミナルがスケスケになった時に見やすいですね。

社長:良し悪しは別として気分は変わるので、今日はこれで行きますか。

開発:壁紙のあちこちに機能を持たせたいものです。他のデスクトップへの入り口をサムネールで表示するとか。

社長:Mission Control にはあまり面白い事は期待できない感じですね。ブラウザをフチなしでフルスクリーンにして背後に置くとかすると良いかも知れません。

開発:壁紙ブラウザの窓の間で通信させて、アクティブな窓を切り替えると、デスクトップも切り替わるというようなにすると良さそうでう。

社長:仕事を始める前に食事に行ってきます。

基盤:そして午睡から覚めると外が暗くなっているとw

* * *

社長:今日は取り柄を見出しにくい空でした。が、帰りにぅエルシァに寄った時に何かいつもと違う… とみると、いつもののぼり旗がたってませんでした。そのかわりにフェンスにこのようなシースルーのバナーが目につきました。

開発:青系に黄色の取り合わせはあそこのイメージカラーですかね。

基盤:それを推してる一派が社内に居るのかなと。まあ薬局派でしょうね。劣勢のようにも見えますが。

開発:しじみ72杯分のちから派に敗れたパワー派のようなものですかね。

社長:ほんわか系ですね。

* * *

開発:やはり、目が覚めたら外は真っ暗でした。

基盤:買って帰ったレモンサワーがやたら効いたみたいですね。

社長:夢で考えたのですが、ブックマーク機能を作ったらどうかなと。複数のURLをセットにして同時に開く。

開発:できてるものを開くのはGShellがなくても出来ますね。保管場所というかブックマークのページは提供できますが、ローカルストレージがあればそれで良いとも思います。逆に保存を自動化するのは、extension でやらないと難しそうです。

社長:ブックマークのページをブラウザ間で共有できるのは意味があると思います。

基盤:パスワード保管ページと同じですね。

開発:とりあえず共有のメモ帳ページみたいな感じですかね。

社長:ブックマークとかそのセットにはメモが付けられると良いですね。あと、インラインフレームでスタートページみたいに表示できる。

基盤:自分自身のページを編集して保管できると良いですね。

社長:要するに編集機能付きのウェブサーバですね。WordPress互換にしたりして。

開発:そもそも昔はそうやって、個人用に作ったブックマークページを公開してシェアしてましたよね。なまじブラウザにブックマーク機能が出来て、そういうシェアがされなくなったように思います。

社長:現在のページ、つまりgsh.go.htmlをセーブしたりロードする機能が良いかもしれません。セーブする時にはGoで署名する。

開発:それからやりましょう。

基盤:保管場所は ~/private_html なんてどうでしょう。

開発:採用。

社長:現在のページの全体とか部分を contenteditable にするモードがあると良いですね。

開発:まずそれからやりましょう。

編集できるウェブページ

開発:ということで、Editボタンを付けてみました。これで、全体の contentable を on/off します。

社長:… 面白すぎる。

開発:完璧な WYSIWYGエディタですよね。これを活用しない手は無い。

基盤:スペルチェックモードにもなるわけですね。

開発:それがデフォルトになってるのが、日本人というか日本語人にはピンと来ないところですけどね。

社長:さすがに、本文の編集に入るのに認証というか、パスワードくらいはかけたいですね (^-^;

開発:まあ、パスワードをハッシュして内蔵しておけば良いかと。

社長:あと、これはいつ誰が編集したものだという表示が欲しいですね。

開発:まあrepeatな背景画像の透明シートを被せればよいかなと。

基盤:オリジナルではない、編集・改竄されてるってわかると良いですね。

開発:電子署名すればいいんじゃないですかね。

社長:署名を検証するJavaScriptをプロテクトするか、バックグランドのGShellか、オリジナルのサーバで検証する必要がありますね。

開発:サーバレスの場合もありますから、GShellでやるんじゃないでしょうか。

社長:今日の成果はもうこれでいいかなって感じですが、昼間の酒が残ってて飲みに行く気分にならないです。

基盤:社長のブログはこれで書けるんじゃないでしょうか?

開発:ウィジェット的な部品も用意できます。まず付箋は欲しいですよね。

基盤:当然 vi 互換ですよね。ESCでコマンドモードになる。

社長:まあ文章の部分は… でも、WordPressのエディタのえらいとこは、インライン画像の編集能力なんですよね。あれを自前で出来るかですが…

開発:表示サイズを変更するのはCSSで簡単に出来ますが、トリミングとか解像度の変更は難しそうです。

基盤:GShellサーバに送って、画像編集ツールを起動すれば良いのでは。

社長:わたし的には macOSのPreviewで満足というか、あれは実に素晴らしいツールだと思います。

社長:そういえばお昼を食べながらiPhone見てたら、ノートがアップグレードしたってこういうのが。

社長:わたしはNotesも大好きですが、メモには妙な拡張機能じゃなくて、あくまでシンプルな使いやすさを求めたいです。最近、表示がゴテゴテして来ました。機能的にはPhotosとの連携強化はして欲しい。検索とかは、デスクトップでやりたい。デスクトップで、選択したメモをまとめてPDFに出来る機能がいつ出来るかと期待しているのですが。

基盤:iTunesじゃなくなっちゃったアプリもアップデートしましたね。

社長:iPhone買った当時は私のキラーアプリだったんですけどね iTunesが。腐ってしまった。

開発:「購入した項目」のプレイリストはさすがにツボばかりですね。表示が「購入した音楽」になり変わりましたが。

社長:一曲200円で買いまくってました。いつ購入したというタイムスタンプで、ああ、これはココスで飲みながらポチったなとかまで覚えているのもあります。で、2年ばかりブランクがあって、また買おうかと思った時には腐ってしまっていた。

開発:引越し前はほぼ毎日ココスでしたね。

基盤:そういえばアンドロイド端末の件、iPhoneの前に使ってたのをどこかで見かけました。たぶんこの部屋の中にあります。まだ使えるんじゃないでしょうか?WiFiで。

開発:ていうか、SIMをうつせば携帯として復活するんですかね?

経理:すばらしいご提案だと思います。

社長:ララ、夏の少女よ♪

生きたページの保管と呼び出し

社長:そろそろ続きをやりましょう。次は保存かなと思います。

開発:まず Save と Load をメニューに追加。とりあえずローカルストレージに保存して、呼び戻す形で試します…

開発:あれれ、なんか動かないと思ったら、document.write を docuemnt.write にしてました… でも直してもなぜかムジナ。こんな簡単なコードなのに。fork用の関数をコピペして使用。当然動く。元の関数に戻す。あれ、直った。なんか状態が残ってるんですかね。

基盤:コンパイルコードの記憶が変になってたとか。

開発:やれやれ動きました。あれー、Chromeでは、動的に作ったページで開発者ツールが出ないですね… こんな時に頼りになるのがFirefox。おー、ちゃんと生成されてますね。

社長:ありがとうって伝えたくて♪これ歌ってた時はカワウソさんも20代なかばだったんですね。

開発:God bless you, please, Mrs. Robinson♪。柴門ふみってS&Gから取ったんですよね。

基盤:おや、12時を回っています。

社長:まあ、うちは今そういうシフトなんですね。

開発:とりあえずこんな感じになりました。

開発:テストです。まず初期状態。

開発:ちょっとEdit。

開発:でこれをSave。

基盤:編集状態のまま保存するんですか。

開発:その状態もコミで回復されるというテストです。

開発:次にこれをLoad。

社長:この alert はポップアップにしたいですね。

開発:いずれそうします。

開発:すると編集中を示す赤文字の Lock が出る状態で回復されます。

社長:Lock はちょっと意味がわかりにくいかもですね。

開発:で、現在の版を確認。保存した版のままです。

開発:気持ちが悪いので編集状態を抜けてもう一度バージョン確認。

基盤:チェックサムというかダイジェストの表示も欲しいですね。

社長:あとは、最終編集者の情報とか、編集履歴とか。

基盤:履歴は足していくので、どう表示するかだけですね。

社長:バージョンの枝番も付けたいですね。

開発:ありゃ、ロードしたページでSource表示ができなくなってますね。

社長:まあそれは後ほど。

基盤:神様の宝石で出来た島で〜♪

開発:ふーけーばーとぶようなー♪

社長:藤井棋聖がこれ聴いたらどういうふうに感じますかね (^-^;

基盤:まっ、その、えっと… はい。ですかね。

社長:あー、今日のタイトルは「ブラウザ間通信」じゃ無いですね。

開発:これまでも中身との乖離はよく有りましたが。

社長:「HTMLスナップショット」にしておきます。

-- 2020-0924 SatoxITS

/* */ /* GShell-0.5.0 by SatoxITS
GShell version 0.5.0 // 2020-09-24 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

Edit Save Load Vers 0 Fork Stop Unfold Digest Source
*/ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Cross-browser communication

... to be written ...

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.5.0" DATE = "2020-09-24" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
Web technology HTML: The Living Standard (September 2020) Developer Version CSS Working Group Editor Drafts (September 2020) MDN web docs HTML CSS : selectors repeat JavaScript HTTP Go language (August 2020 / Go 1.15) The Go Programming Language Packages WebSocket Stackoverflow
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
(^_^)//{Hit j k l h}
CLOSE
*/ /*
GJ Console

*/ /*
Form Auto. Filling
Location: Username: Password: SessionId:
*/ /*
BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*
GJ Link

Execute command "gsh gj server" on the localhost and push the Join button:



*/ /*
Live HTML Snapshot
*///

GShell 0.4.9 − client-side script++

社長:ブログの記事が長くなったかどうかは、ブラウザ上で隣のサイドバーを超えたかどうかで見ているのですが、昨日の記事はそれをはるかに超えてしまいました。どうも納得がいきません。

基盤:でも実際、Safariで印刷して30ページあります。

開発:電子レンジ対応どんぶりの選定が長かったですかね。

基盤:チン丼屋ですね。

社長:さっと読み直したらタイポだらけで、それはいつものことですが、意味不明な部分があります。

開発:「あんかな」ってなんですかね?

社長:文脈からして「案外」か「なかなか」のどっちかだと思うんですが。ankana、nakanaka。似てます。なかなかを採用しましょう。これを直して再魚拓。

開発:書いた直後にしかわからないことはありますね。

社長:文章では特にありがちですが、プログラムでもそうだったりするんですよ。構文エラーにはならない意味的なもの。書いちゃったら忘れちゃうので、思い出すのが大変です。

開発:プログラムの場合、そこが何か変だなと思って整えたら壊れちゃったって事もよくあります。一つのケースで考えると無駄か不自然だと思うわけです。でも別のケースで考えると必要だったみたいな。

社長:なので、若い頃は共通化して一般化してコンパクトに書くことを心がけてましたが、その後はべたべた具体例的に並べて書くことが多くなりましたね。

開発:若い頃って、放っておくと際限なく一般化したくなったりする感じでしたね。汎用に作りたい、一粒でなんでもできちゃうのが最高みたいな。

社長:だから変更の影響範囲が大きかったし、後から読みにくかった。さりとて、標準ライブラリみたいにはインタ−フェイスを詰めて無いから、中身を読まないと仕様が完全にはわからないという。まあ、ずっと、落ち着いて考える余裕がありませんでした。

開発:とりあえず動いてうれしい刹那的プログラミングに比べると、仕様を見渡して整合させて確定させる作業は地道で時間が掛かりますからね。

社長:抽象的にトップダウンに考えてるとものが進まないし動くものができない。具体例を作って動かすほうがためになるわけです。具体例を幾つか作ってるうちに共通性が見えてくるので、一般化してマージする。この進め方は開発効率の上でもモチベーションの上でも悪くないと思うんです。少なくとも、あるフェーズまでは。

開発:実装を整理すると連続性がなくなるので、あとで問題が発生した時に追いかけるのが大変になる面はあります。

社長:diff の ! は見にくいので + だけにするようにしたりとかしてましたね。決して引かないw

開発:それもあって DeleGate は30万行になりました。

社長:そもそも人間は物理的に離れた2つのものを比較するのが得意じゃないですよね。しかも diff が、差分のかたまりを大きく作ろうとする傾向のアルゴリズムなところが大問題。人間の読み易さとかはあと付けの -c 程度で、もともと何か違う事を意図しているのでは。patch の処理効率とか?

開発:確か昔CACMにdiffのアルゴリズムが乗ってて、へーこういうものがCACMに乗るのかと驚いたような記憶があるんです。

社長:まあ、あこがれの学会誌でしたからね。

基盤:一番長いのは http.c の 13654行かと思ったら、X.c が 13820行で1位です。Xプロトコルの中継にそんなに力を入れてたんでしょうか?

開発:そんな記憶はないですね… ああ、これは yymux プロトコル関係ですね。今で言えば WebSocket みたいなやつ。Y11 というプロトコル名にしたので、X.c に同居させたみたいな雰囲気です。

社長:というか、yysh もその中に入ってます。

基盤:こういうコメントが入ってます。

基盤:これはこのコメントの通り動くんでしょうか?

開発:10年前の人が書いた事ですからね… 書いた当時はこの通りに動いたんでしょうけど。

社長:Manual.htmlに書いた事以外はよくわからないですね。まあ終わりの頃はマニュアルに書くほど詰めてないのを色々実験的に作ってた記憶があります。確かに、yysh が最後の頃の作業でした。

開発:などと昔話を書いているうちに、お昼の時間になってしまいました。

社長:今日は、GShell + JavaScript で、client-side CSS と JavaScript をやれないか、トライしてみたいです。

開発:他人のHTMLへのインラインスクリプトとインラインCSSの追い焚きですね。それができるなら最高。もともと技術的に難しい話では無い。でもたぶんセキュリティ上禁止という可能性が大、というところかと思います。

社長:おなかがすいたので食事して来ます。今日は謎の定食屋に再チャレンジ。

* * *

社長:帰りました。今日はあちらこちらに良い曇り空が出てました。

基盤:今日もあそこは飲み放題食べ放題で原価でしたか。

社長:いや、閉まってました。くいだおれ方面の和食系は全滅。そういう日なんですね。それでこっちの岸に戻って来たら焼き魚もアウトで、結局一番近くのそば屋でけんちんうどんでした。

開発:まあやる気のないそば屋でも年越しそばとかは頑張りますからね。

社長:それで帰りに郵便受けを見たら、でんこのご使用量のお知らせが入ってました。

基盤:玄関ドアに投げ込む方式はやめたんですね。

開発:4階まで上がるのが大変なんですかね。

社長:実際にドアの前まで来て検針して投函してるんだと思ってたんですが。てことは、物理的な検針はしてないのか…

開発:票に書いてある検針員の名前はロボットでは無いようですけどね。

経理:やはり、ついに大台を超えましたか。401kWh。3段目の料金が痛い。

開発:GShellの暴走事故か?で5kWhは食ってますね。

基盤:支配的なのはエアコンの消費電力と思われます。概算で、0.2kW x 20x24h = 96kWh。

社長:でも、あの時冷房の決断をしなかったら熱中症か何かで、今の私達は存在しなかったかも知れないのです。

基盤:我慢して月2000円のために命を落としてどうするんだってことですね。

開発:暑さに茹だることもなくて、思考力・生産性を落とさずに済んだと思います。1ヶ月間、室温を気にしたこともありませんでした。

社長:まあ、「脱冷房宣言」の頃は、あまり思考してなかったのかもですね (^-^;

* * *

開発:ふあぁ… ありゃ、目が醒めたあらまた外が暗くなってました。

社長:また document.write の夢でうなされてしまいました。

開発:早めにカタを付けましょう。

* * *

開発:表示を調整するのにとても時間がかかりました。慣れてないというのもあるのですが。

開発:本題のほうですが、自分のサイト内のスクリプトで開いた内容はいじれるけれど、他のサイトはダメっていう感じですね。same origin、要は同じサーバの下に無いURLは開けても、その中身にはアクセスできない。

社長:当然と言えば当然というか、逆に同じサーバの下に他人が同居してたら、もっと制約が必要なんだろうとは思いますね。

開発:それはそうと、どうも JavaScript の変数名のスコープがブラウザによって違うような違わないようなで混乱してます。export が必要だったり、逆にexport を受け付けなかったり。

社長:見た目は整いましたので、内容的に詰めて行きましょう。

-- 2020-0923 SatoxITS

http://gshell.org/gshell/

/* */ /* GShell-0.4.9 by SatoxITS
GShell version 0.4.9 // 2020-09-23 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

0 Fork Stop Unfold Digest Source */ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.4.9" DATE = "2020-09-23" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "serve": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var AtConsoleLineTop = true var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } AtConsoleLineTop = false } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); AtConsoleLineTop = true break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "server" { go gj_server(argv[2:]); } if argv[2] == "serve" { go gj_server(argv[2:]); } if argv[2] == "client" { go gj_client(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
GJScript
 function gjtest1(){ alert('Hello GJScript!'); }
  gjtest1()
(^_^)//{Hit j k l h}
CLOSE

*/ /*

Form Auto. Filling
Location: Username: Password: SessionId:

class BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ if( false ){ txa.setAttribute('class','textField VisibleText'); }else{ newclass = txa.getAttribute('class'); if( and(newclass != null, newclass != '') ){ newclass += ' '; } newclass += 'VisibleText'; txa.setAttribute('class',newclass); } //console.log('SetupVisibleText class='+txa.class); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; //console.log('VisualText#'+txa.id+' cols='+cols) }else{ //console.log('VisualText#'+txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '30px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_server = "localhost:9999" const gshws_port = 9999 const gshws_path = "gjlink1" const gshws_url = "ws://"+gshws_server+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) mark := "" if( !AtConsoleLineTop ){ mark += "\n" AtConsoleLineTop = true } mark += "["+now.Format(time.Stamp)+"."+us+"] -GJ-" + what + ": " return mark } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) //fmt.Print("\n") glog("CO","accepted connections[%v]",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} wsx := 0 for i,v := range WSV { if( v != ws ){ wsx = i wsv = append(wsv,v) } } WSV = wsv //glog("CO","closed %v",ws) glog("CO","closed connection [%v/%v]",wsx+1,len(WSV)+1) ws.Close() } // url ::= [scheme://]host[:port][/path] func decomp_URL(url string){ } func full_wsURL(){ } func gj_server(argv []string) { gjserv := gshws_url gjport := gshws_server gjpath := gshws_path gjscheme := "ws" //cmd := argv[0] argv = argv[1:] if( 1 <= len(argv) ){ serv := argv[0] if( 0 < strings.Index(serv,"://") ){ schemev := strings.Split(serv,"://") gjscheme = schemev[0] serv = schemev[1] } if( 0 < strings.Index(serv,"/") ){ pathv := strings.Split(serv,"/") serv = pathv[0] gjpath = pathv[1] } servv := strings.Split(serv,":") host := "localhost" port := 9999 if( servv[0] != "" ){ host = servv[0] } if( len(servv) == 2 ){ fmt.Sscanf(servv[1],"%d",&port) } //glog("LC","hostport=%v (%v : %v)",servv,host,port) gjport = fmt.Sprintf("%v:%v",host,port) gjserv = gjscheme + "://" + gjport + "/" + gjpath } glog("LS",fmtstring("listening at %v",gjserv)) http.Handle("/"+gjpath,websocket.Handler(serv1)) err := error(nil) if( gjscheme == "wss" ){ // https://golang.org/pkg/net/http/#ListenAndServeTLS //err = http.ListenAndServeTLS(gjport,nil) }else{ err = http.ListenAndServe(gjport,nil) } gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*

Execute command "gsh gj server" on the localhost and push the Join button:



*///

パスワードマネジャー召喚法

社長:今日はもうお昼近いのに、妙に静かな日ですね。

開発:お正月みたいですね。交通音も生活音もして来ない。カラスが鳴いてるし。

基盤:そういえばイツモは何の音が聞こえてるんでしょうね。今聞こえるのはレノボがわずかにモーっていってる音くらいです。

社長:静かだと耳鳴りに気づきます (^-^;

社長:おなかがすいたので食事して来ます。今日は久しぶりにしらす丼かな。

* * *

社長:案の定、今日もよい空がが出ていました。

開発:日常生活の中にこんな風景があったというのがちょっと驚きですね。

基盤:スラムの上にも美しい空が平等に出ているのですね。

開発:ここはスラムではないですけどね。ただ、ともかく電線がうざい。

社長:最近は歩く時にも眼鏡をかけるようになったせいか、空を見るのが楽しいです。地球の空の素晴らしさを再認識させられているところです。

開発:空の再発見。ディスカバースカイですね。

社長:それで帰りに寄ったゥエルシァでは目の大きなロボットお姉さんがレジをしてました。

基盤:目のパッチリした人がマスクしてると、いつも驚いているように見えますよねw

開発:本当にロボットなんですかね。

基盤:直接聞いてみては。

開発:まあ「私はロボットではありません」と答えるのではないでしょうかw

社長:でも最近は学習が進んだみたいで、レジ袋はどうなさいますかとか、スプーンはお付けしますかとか聞いてくるようになりました。

経理:それはそうと、レノボ機はほとんど使っていないようですので、スリープさせておいてはいかがでしょう?

基盤:確かに。では1分でスリープ…

開発:どうやって起こすんでしょう?

基盤:ネットで特定ポートを監視して起きるとかできるはずですね。RemoteDesktopで起きてくれるのではないかと。

社長:いや、手を伸ばせば電源スイッチがありますから、それでも良いです。

* * *

社長:おや、目が醒めたら外はもう暗いです。

開発:日が短くなりましたね。

基盤:そういえば、今日は秋分の日ってやつです。

開発:さすがに昨日は朝から深夜までやったので疲れました。

基盤:gsh.go.html も500行近く大きくなりましたね。

HTMLでウェブサイト魚拓

社長:実質的に一番大きな進歩があった日なのかも知れません。@media screen の記念写真をSafariでパシャ。

開発:コンテンツだけの印刷とはまた違った壊れ方をします。

社長:サイトの印刷ではDOMの現状を吐いて印刷するみたいですから、DOMの状態を印刷用の状態にする、というかスクリーン表示モードを、GShellで作ってやると良いのでしょうね。

開発:そう思います。といいますか、そもそもサイト全体のスクリーン表示の現状を吐き出してPDFにしてくれる機能って、ひょっとすると Safari の File > Export as PDF しかなかったりしないですかね。

基盤:Opera にも、File > Save Page as PDF はありますね。ただ、ページの前半が無くなってたり、details が化けたりと、悲惨ですが。

社長:なぜ details のような基本中の基本の印刷が壊れるんですかね。Chromium系に共通のようですが。

開発:Opera にあるなら、Vivaldi にもあるんじゃないですかね。

基盤:見当たらないですね。メニューのその付近には、得意のセッション保存機能が表示されてます。

社長:ブラウザの黎明期には、Mozaic とか Netscape とか、画面に表示されてる状態をそのまま印刷してましたよね。だから、画面の表示幅を変えて調整してから印刷とかしてました。

開発:画面表示の完璧な再現というか魚拓を期待するなら、それが正しい方法ですね。

社長:つまり、表示する前のDOMではなくて、表示された後のビットマップが手に入れば良いと。

開発:どの x, y 座標に表示されてるのはどのエレメントだと逆探知できるくらいですから、イメージを全部作ってるんじゃないですかね。

基盤:それを頂いて、PNGにしちゃうとかw

開発:非常にひょうきんなサイズの画像ができると思いますが、それが正しい道の一つかなと思います。

基盤:とうかPNGじゃなくて、スゴーク縦長の1ページのPDFに吐いてくれるブラウザがあったような記憶もあるんですが。割と最近に見た記憶が。

社長:バイナリのダンプとしては正しいと思いますが、ソースレベルでのダンプも欲しい。GShellでやってる、DOMの現状を、なるべくオリジナルに近いHTMLで吐くというのも、ひとつの有るべき道だと思います。

開発:まあ「har / はあ」形式のアーカイブですね。生きたDOMの状態というか値がHTMLのインラインで展開されてしまうのが嫌ですが、あそこは、その値を別途JavaScriptで生成するようなscriptに分離すればよいのかなと思います。

社長:人間が見るのに耐える、というのはかなり難しいと思いますが、なんでもいいからダンプして保存しよう、ありのままを保存して後でそのとおりに表示しよう、実行を継続しよう、という意味では、単にDOMのダンプというか、outerHTMLを履けば良いのだと思います。

開発:DOMと、style のバイナリであるCSSOMもですね。あれをCSSのごとくか、設定script形式にして吐く。生成された script コードについてはどうか、不明ですが。

基盤:WebAssembly の逆コンパイルみたいなことになるんですかね。

社長:なんにしても、これは、バイナリの実行状態をソース形式でダンプするという、普通のコンピュータ・プログラムでは難しいことが、ウェブ処理系ではできる可能性があるわけです。面白いですね。

開発:ほんとうの実行状態という意味では、JavaScript のスタックとかヒープのダンプも必要じゃないかとは思いますけど。

社長:まあ、動いてる途中というのまで望まないないですが。少なくともDOMに紐付けられた静的なデータ構造については。

開発:まあ、Lispなら出来るって言う人もいるでしょうけどね。

基盤:WebAssembly って、Lisp みたいなやつじゃなかったでしたっけ。

GShellでブラウザ間パスワード移転

社長:さて、今日もまだ数時間はありますので、何かやりますかね。

開発:テーマとしては、パスワードの移転が良いと思います。

基盤:iMacへの移行が完了しない理由もそれですね。

社長:といいますか、6ブラウザそれぞれに良いところはありますので、どれも並行して使いたいのです。なので、移転というよりは、理想的には常に共有していたい。そもそも、ブラウザにパスワードを記憶させるのも嫌ですし。なので、自前の秘密記憶に置いておいて、それをブラウザから取りに来れるようにしたい。

開発:任意のサイトへのログイン用には、ブラウザに手を入れるか、少なくとも extention は必要でしょうけどね。

社長:clinet-side CSSみたいに、client-side スクリプトを添加できると良いのですが。

基盤:プロキシで突っ込んでやるとか。

開発:HTTPSだとそれが結構むずかしいのです。SSL的には復号・再暗号化で通せますが、エンドポイントがMITMを検査している可能性が高い。昔はDeleGateでやってましたけど、最近は通用しないんじゃないですかね?それができてしまうなら、ネットバンクなんて使う気にならなくなります。

社長:まあ、エンドポイントと同じ秘密鍵を持ってて再暗号化できれば、ってくらいじゃないですかね。

基盤:通信じゃなくてコンテンツが部品単位に署名されてれば良いのでは。自分の署名のある添加スクリプトなら信頼するというのは普通だと思います。

開発:そのへんは逆に、HTTPじゃなくてS/MIMEならできるという話でしょうね。

社長:利用者側クライアント側エージェントとしては許しても、サーバ側、提供者が、スクリプトでの改変を許さない可能性は高いですね。

基盤:利用者ファーストじゃないんですね。まあ、確かに利用者自身は信頼できないですがw

開発:そもそもHTML/HTTPSの通信はGoでやって、表示は既存のブラウザに任せる、という構造がが有力なのかなと思います。これならば、ブラウザのコードとかextensionに手を染める必要は鳴い。ただし、開発にはかなりコストがかかりそう。

社長:一点突破的に、ログイン時のパスワードの移転にだけ使えるエンジンならなんとかなるんじゃないですかね。

開発:そうかも知れません。

現在のページのユーザ名を知る

開発:それではまず、気合を入れるためにこのブログ記事にGShellを貼り付けます。で、昨日寝る前にMDNで読んだLocation API。 location.username を eval。これだけでいけるのではないかと。

基盤:残念!

開発:時代錯誤だった模様。

開発:思うに、それはもうobsoleteだから、今後はこれを使えっていう指示があって欲しいですよね。ワンストップサービスでない。

社長:規格系の情報源は正確な一次情報だけの記載に留めるのは仕方がないかなと思います。でも実際昨年 table 関係の col だったかが思ったように効かなくて探したら「HTML5ではこれは削除された」とだけあって、なぜ、いつそうなったのか、がわからない。代替は何かも検索するのに時間がかかって。あの時は非常に、HTML5とかいうものに腹がたったものです。

開発:ですが一方でMDNは、ブラウザでの実装状況という非常にライブな情報もサービスしているわけです。それがMDNを便りにしている理由でもあるんですが。

社長:そのブラウザの版は何年のものだ、っていう情報がないのも残念です。ドキュメントの更新の時間軸もわからない。

開発:機械的に、昔の記載を残して、そこへのリンクがはってあれば良いと思うのですが。

基盤:WikiPediaはその点が完璧ですね。どういう議論があってそうなったかもちゃんと残されている。

パスワードマネジャー召喚の術

開発:というわけで仕切り直しです。

社長:まあ、JavaScriptからダイレクトに現在のパスワードが見えちゃうなんて、のどかすぎますよね。あー、長閑って書くんですか。でも、自分では書けない漢字とか使いたくないですね。

開発:それで思い出したんですが、昨日 BlinderText を作った時に、「このエレメントにはパスワードを入れてね」っていう、autofil 機能がCSSにあったなって。HTTPS文脈じゃないとブラウザの警告がうるさかったか何かでやめましたが。

レンジでチンできる丼

基盤:ちょっとお腹が空きました。

開発:我が社ではカップラーメンブームが下火でレトルトブームがきていますが、レンジで具を乗せてチンしても熱くならない丼があると良いと思います。

経理:アマゾンで検索。安いのでは500円、3000円あたりが多数、高めだと5000円、10000円なんていうのもありますね。

社長:軽くて断熱性のあるのがいいです。

基盤:陶器だといとぢ理… macOSのデフォルトIMEってやっぱりダメじゃないですか?糸尻が熱くなったりするのがあります。

開発:見た目にこれいいなと思うと、5000円くらいしますね。

社長:これ、食材の入った写真が無いとスケール感がわからないですね。寸だインチだと書かれましても…

基盤:これって、≡GShellとかロゴの入ったボウルだとかわいいでしょうね。

開発:この美濃焼ラーメン丼はいいですね。1800円だし。

社長:内側がかっこいいけど、外側がいまいちのような。19.5cm てデカすぎませんかね。

経理:食材の入った利用例をみるとそうでも無いですね。

社長:じゃこれをひとつ。ちょっとこのメーカーで探しましょう。

基盤:この皿がすごく良いと思います。24.2cmですけど。

開発:カレーを食べるのに良さそうです。ああ、こっちのも良いですね。やっぱり日本製。

基盤:あ、この小さめの3点セット、いい感じだしすごくお手頃価格。

社長:それも追加しましょう。そろそろ探し疲れました。

経理:ただいま、カートの総計、10点で33,661円です(笑)

社長:3000円以上のと、プライムじゃないのを全削除。

経理:ぷちぷちぷちっ… 3件で5090円になりました。

社長:それでGo。

経理:Operaにアマゾンbusinessのパスワードを教えていないようです。

社長:かちゃかちゃ。でも、これは経費ですかね?

基盤:ちょっと待った。その安い3点セット、電子レンジ対応ではないようです。

開発:何から何までチンするわけでもないですけどね。

社長:もっと小さめのも欲しいかな。このまっしろ無地でふにゃっと形状のが良いです。お値打ちだし。

経理:11cm, 14cm, 18cm、追加しました。

社長:じゃあそれで。

基盤:コーヒーカップも欲しいですね。この何十年ものの安物しかなくて。

社長:割れたら考えましょう。

経理:ではこれで。あ、いちど見積書をダウンロードして見ます。

開発:一点だけ法人価格ってありますね。

基盤:この会社は税込みでジャストになるようになってます。

経理:まあ普通の会社だとこれを上に上げて承認をとるわけですね。

社長:承認しました。

経理:ではぷちっ。明後日午前中着です。対面配達選択可能ですが。

社長:置き配で。

経理:注文を確定する。ぷちっ。

基盤:そういえば今回は、注文IDの設定を聞かれませんでした。なんか入り口が違ったんですしょうかね?あの機能をやめちゃったとか。

社長:まあうちではとりあえず必要ないですから。

開発:値段とデザインで選んだら、結果的に日本の3社になりました。

基盤:コンピュータの部品とか、海外製が嘘みたいに安いですけどね。食器なんて原価は大したことないと思うのですが、センスの壁みたいなのがあるんでしょうか。

開発:食洗機っていうのはどうなんでしょうね?

社長:洗い物作業自体というより、腰が痛くなるのが問題だと思いますから、それの対策としては良いかもですね。

開発:昔、洗い物を一ヶ月くらい放置してたら、下積みになってた皿からメロン種がぞぞぞーっと芽を出して、さわったらさわさわっとして気持ちよかったことがあります。

基盤:メロン版のかいわれですね。食べてみましたか?

開発:気が回りませんでした。

社長:大根なんかも幼少のみぎりから大根らしさがありますからね。メロンの香りがするのかもしれません。

基盤:子供のころはただのきゅーちゃんかもしれないですが。

開発:ところでこのレトルトカレー、封を切らずにレンジにかけろと。蒸気を逃す弁がついてるんですね。

基盤:温まってから開けると中身が残らなくて無駄がないですね。

社長:まあ問題は味ですが… はふはふ… 合格です。

開発:ハフハフ。これって、醤油のパックの密閉口以来のヒットだと思います。

基盤:もぐもぐ。まあ有効性のインパクト的にはあれにまさる発明はなかなか無いと思いますが。

社長:醤油ってすぐに酸化して悲惨でしたからね。あれは醤油の歴史上みんながそういうものだと諦めていた問題を解決してしまった素晴らしい技術だと思います。もぐもぐ。

開発:技術が素晴らしいというか、今なら技術で安価に解決できる問題だと思い得たという点ですね。ごちそうさまでした。

社長:まあ、あれをあの形で店に出したらかっこ悪いから、体裁を気にする店では使わない。なので、高い店のほうが逆に醤油がまずかったりするわけです。

開発:次のフェーズは、見た目にカッコ良くて、使い捨てではない醤油さしの実現技術かと思います。

基盤:使い捨てでも大したことない洗剤の容器とかでも詰め替えパックが普通なので、そういう形でも抵抗感は少ないですね。

社長:小さく軽く作らないといけないのが技術的な困難かと思います。

社長:それで、思い出したのですが、そろそろ鍋の季節ですね。

開発:白菜が安くなると良いですが。

社長:そろそろ吉田拓郎も飽きたので、昔 iPhone にためたのを色々つくったプレイリストで聴きたいです。iMacで操作して、iMacのスピーカーで聴きたい。

基盤:iPhoneをiMacに接続… こういう事に。

開発:他のライブラリってなんですかね?

社長:自分で買ったCDとiTunesから買った曲しか入ってないですが、それも削除されちゃうて意味ですかね。

基盤:そういう事みたいですね。「このiPhoneをこのコンピュータと同期すると、昔のMacBookAir上の別のライブラリからのメディアは置き換えられます。iPhoneは一度に1台のコンピュータとしか同期できません」ときました。

開発:「置き換えられる」って「削除される」って意味ですよね。

社長:別に同期したいわけでなくて、単にiMacをiPhoneのリモコンにできれば良いのですが。いや、オーディオの出力先としても使いたいですが… ん?まてよ、昔のMacBookAirはキーボードがバーボン漬けになって死んでますが、他は生きてるんです。あれを復活させてはどうでしょう。画面共有で制御すれば十分かと。

基盤:オーディオは画面共有でiMacには持ってこれないような気もしますが。

開発:持ってこれたとしても、通信が混んでると雑音が入るとか悲しいですね。

社長:引き続き検討しましょう。

そうこうしているうちに、もう終業時間が近づいてきたようです。

認証情報のJavaScirptでの読み出し

開発:今日はまだ、パスワード送信テスト用のHTMLを数行書いただけですw

社長:ところで思ったのですが、ここで考えているユーザ名やパスワードって、HTTPで送られるbasicとかdigestのauthorizationじゃなくて、あくまでHTMLの中のform dataかURLの中の等価なそれの事じゃないかって。

開発:そう言えば。HTMLのコンテクストの中で語られてますしね。まあ、ブラウザに記憶する認証情報として、その2つを区別しているのかどうかわかりませんが。

開発:まあ調べるのも手間がかかりますから、実際に動かして確かめます。本日の GShell work product はこの6行です。で、これをブラウザで表示。

開発:あー、これですね昨日うざいと思ったのは。

開発:あ、でもパスワードの入力ではちょっと期待を持たせるような何かが。

開発:パスワーを入れてsubmit。

基盤:「開示」って、本人以外に教えちゃう事ですよね?

開発:保存する。で、もう一度フォームを見に行く…

社長:自動で fill はされてないですね。

開発:たぶん、"on" では指示が弱いのではないかと。ともかく「aaa このサイトから」をクリッ。

開発:ユーザのすべき操作としては、どうでもいい認証以外でなければ、このほうが良いかもですね。

開発:で、問題はこのUNとPWをevalで読めるかですが。

社長:問題なしですね。

開発:あれー、Firefox は autocomplete=current-password をサポートしてないんですかね。MDNを見る… おっと、こういうおすすめがポップアップされました。

社長:それをビューティフルワールドって言いますかね。

開発:きちんとしてる事が好きな人でしょうね。まあそういうウェブ開発者もいるでしょう。

社長:Aですからね。

基盤:でも内容は、まさにうちが欲しがってる情報の総まとめみたいな感じですね。

開発:autocompleteは、トラブルメーカーの第4位にランクインしています。

社長:しかしこのPDF、著者も日付もページ番号もないって、どうしたものですかね?

開発:まあ、著者は A Web Developer じゃないですかね。URLは以下。

https://mdn-web-dna.s3-us-west-2.amazonaws.com/MDN-Browser-Compatibility-Report-2020.pdf

基盤:まさかこれ、ライトセールですかね?

開発:別に困窮はしてないんじゃないかと思いますけどね。Wikipediaと違って。

開発:それはともかく、「保存されたログイン情報を表示」をクリックして、Firefoxのパスワードマネージャはなかなかかわかりやすく出来ていることもわかりました。

基盤:さすがに「開示」では無い模様ですが。

開発:この非表示のまま、通常のコピーで、隠されたテキストがクリップできる一般的な規約があると良いと思うんですけどね。BlinderTextとしても。

社長:まとめると、認証情報というかformのフィールドのfillには色々規約があるけれど、いったんfillされたら単にエレメントの値だと言う事ですね。

開発:だと思います。まあ、formの中、inputでなくても、autocomplete=password は使えるはずなんですけどね。 textareaでも。でないと、普通にtextareaであるBlinderText的にはちょっと残念なことです。

社長:で核心は、任意のサイトのログイン情報を召喚できるかという点ですが。

開発:もし action に他のサイトを書いて呼び出せたら可能ですね。

基盤:恐ろしすぎる。

開発:まあダメでしょうけど。何か特別の確認手順を経れば出来る可能性はなきにしもあらずです。

社長:やってみましょう。

開発:実験にしても恐ろしいので… まああのタコルータで試しましょう。認証画面のソースを見る… フィールド名は user と pass ですね。ではこれで、ぷちっ!

基盤:処理中ってなんですかね?

開発:例によってリブート準備中とか。

社長:どうやって自分じゃないサイトから飛ばされて来たかを識別してるかですね。

開発:Referer か Cookieですかね。でもRefererは簡単に詐称できるし。ああ、ログイン画面のformの中に、hidden でセッションIDが入ってます。

基盤:これ、一度ゲットしたセッションIDを同じままずっとフォームの中で投げ続けているように思えるのですが。一旦IDを盗まれたら成り済まされ放題のような。

社長:なんでHTTPのダイジェスト認証を使わないんでしょうかね?

開発:あ、いや、セッションIDは毎回変えてますね。だから割り込むチャンスは、本来の持ち主が次のリクエストを出す前に、盗んで繋いじゃうということでは。

基盤:それでセッションハイジャックて呼ばれるんですかね。

社長:TCPのソースのポート番号を見てるかもしれませんね。まあ、プロキシから来てたら区別つかないですが。同じソースポートを繰り返し使うのも一般ユーザ権限では出来なかったと思いますし。

開発:セッションレスで暗号化されてない通信でログインってやっぱりダメですね。

社長:なのでAPOPとかHTTPダイジェスト認証とかがあるんだと思うんですが、なんで使わないんでしょうね?公開記事みたいに、通信内容的には公開情報なんだけど、誰がそれをやってるかだけ証明したい事も多いと思うんですが。

基盤:多くはないんじゃないですかね。ネットニュースとかの時代と違って。

開発:これは面白いので、別のブラウザからセッションハイジャック実験もやりましょう。

基盤:なりすましサイトから飛ばされて来たのを知ったら、すぐにそのアカウントを凍結すべきですよね。

社長:ただ、Firefoxのパスワードマネージャは、action が、そのサイト自体を向いてない時にも、ダマでautofillしてくれちゃうということはわかりました。自分で他のサイトにつないで、それを送ってしまう。

開発:まあフィールドに値をフィルしてあげるところと、リクエストを送り出すところはアトミックではないですからね。その2つの操作の間の関連は規程されてないんじゃないですかね。値をゲットしてから、action とは無関係な処理をする事を妨げなれない。

社長:いや、値をフィルしてあげるのも、実際に action を起こすのも自分なんですから、値をフィルした時と違うアクションはしない、ということで良さそうな気もしますが。

開発:いや、そもそもそのフォームを実行するかも制約できないですよね。値をゲットしたら、たとえばそれを WebSocket でどっかに送っちゃうかもしれない。

社長:そもそもブラウザとしては結局、あくまで現在表示しているページというかサイトのためにフィルしてあげてるだけという立場なんですね。

セッション引き継ぎ実験

開発:しまった、もうこんな時間に… でも、ようやく準備が実験の整いました。

社長:何か難しい問題が?

開発:いや、結局、閉じる側の script タグをスペルミスしてたとわかったんですが、そのスクリプトが実行されなくて苦しんでました。閉じてない先は JavaScript として文法エラーなのですが、なぜかconsole.logにエラーが出なくて。

開発:というかそもそも決め打ちならスクリプトもいらなくてHTMLだけなんですが。仕掛けはこうです。

社長:決め打ちですね (^-^;

開発:で、Firefoxで発行してもらったセッションIDをSafariで引き継いでログインするという実験をします。

開発:で、Submitをポチッと。

開発:無事引き継ぎ成功です。

基盤:セッションIDと言っても認証前の整理番号みたいなものだから、問題ないのでは。

開発:整理番号を取られて怒る人もいるかもしれないじゃないですか。User-Agentが違う事くらい気づいてくださいよと。

社長:この先は本題ではないので、このへんで終業にしましょう。明るくなってしまいました。

開発:今日のまとめとしては、パスワードマネージャがJavaScriptに教えてくれるのは、現在接続しているサーバと接続している時に記憶されたパスワード、というかフォームのフィールド値だということですね。当たり前だとは思います。だからこれは、HTTPのプロトコルレベルでの認証のパスワードとは別物であると。

社長:まあ、外部にあるパスワードが必要なサイトに接続するには、まず自分ちの外出準備ページに行って、そこで出張先のアカウントをパスワードマネージャから教えてもらって、何かテンポラリに書き留めて、それを実際に出先に行った時に入力する、って感じですかね。

開発:そのフローなら、個別ブラウザのパスワードマネージャである必要は無いですね。現在の入力は、どのURLでユーザ名やパスワードを聞かれているものだという文脈がわかれば、IMEで出来る事。IMEで自動フィルも、必要なら自動送信もできると思われます。

社長:結局、IMEをパスワードマネージャにという方向ですね。実現できたら面白いです。

基盤:課題は、その文脈をIMEが知れるのかと言うところですね。

開発:まあなんなら、デスクトップの画像認識とかして、現在のマウスポンタは認証情報フィールドにあるというのを識別するんじゃ無いですかね。これは、ブラウザに限らない、どんなアプリでも使えるパスワードマネージャになります。

社長:そもそもログインまでは自前のウェブエンジンでやって、認証後はセッション情報を普通のブラウザに引き継ぐというのも、検討したいですね。document.writeとかして。認証段階ではGUIのブラウザでなくても良いと思います。

開発:認証前の玄関にreCAPTCHAみたいのが構えていると面倒かもしれませんけどね。

基盤:思うに、gmail とかは自動フィルとかなくて直接入りますから、あれはHTTPレベルでの認証なんではないでしょうか。それとも、自動フィルしてかつ自動接続というページになってるんでしょうか?

開発:まあそのへんは自動フィルしないブラウザというか、手動で http://mail.google.com をゲットして中身を見ればわかると思います。

社長:ウェブブラウザは認証のインフラにもなっているわけで、CUIなブラウザはグラフィカルな環境のない世界では有用だと思います。

開発:vi互換のプレーンテキストベースのブラウザとか面白そうです。

基盤:今日はGShell開始以来初めて、バージョン更新の無い日になりましたね。

社長:別の実りは多かった日でした。

開発:電子レンジ対応どんぶりとか。

-- 2020-0923 SatoxITS

開発:この投稿のタイトルは「ブラウザユニオン」では無いですね。

社長:魚拓とる前にタイトル変えました。

/* */ /* GShell-0.4.9 by SatoxITS
GShell version 0.4.9 // 2020-09-22 // SatoxITS

GShell // a General purpose Shell built on the top of Golang

It is a shell for myself, by myself, of myself. --SatoxITS(^-^)

0 Fork Stop Unfold Digest Source */ /*
Statement

Fun to create a shell

For a programmer, it must be far easy and fun to create his own simple shell rightly fitting to his favor and necessities, than learning existing shells with complex full features that he never use. I, as one of programmers, am writing this tiny shell for my own real needs, totally from scratch, with fun.

For a programmer, it is fun to learn new computer languages. For long years before writing this software, I had been specialized to C and early HTML2 :-). Now writing this software, I'm learning Go language, HTML5, JavaScript and CSS on demand as a novice of these, with fun.

This single file "gsh.go", that is executable by Go, contains all of the code written in Go. Also it can be displayed as "gsh.go.html" by browsers. It is a standalone HTML file that works as the viewer of the code of itself, and as the "home page" of this software.

Because this HTML file is a Go program, you may run it as a real shell program on your computer. But you must be aware that this program is written under situation like above. Needless to say, there is no warranty for this program in any means.

Aug 2020, SatoxITS (sato@its-more.jp)
*/ /*
Features

Vi compatible command line editor

The command line of GShell can be edited with commands compatible with vi. As in vi, you can enter command mode by ESC key, then move around in the history by j k / ? n N, or within the current line by l h f w b 0 $ % or so.

*/ /*
Index
Documents Command summary Go lang part Package structures import struct Main functions str-expansion // macro processor finder // builtin find + du grep // builtin grep + wc + cksum + ... plugin // plugin commands system // external commands builtin // builtin commands network // socket handler remote-sh // remote shell redirect // StdIn/Out redireciton history // command history rusage // resouce usage encode // encode / decode IME // command line IME getline // line editor scanf // string decomposer interpreter // command interpreter main JavaScript part Source Builtin data CSS part Source References Internal External Whole parts Source Download Dump
*/ //
//Go Source
// gsh - Go lang based Shell // (c) 2020 ITS more Co., Ltd. // 2020-0807 created by SatoxITS (sato@its-more.jp) package main // gsh main // Imported packages // Packages import ( "fmt" // fmt "strings" // strings "strconv" // strconv "sort" // sort "time" // time "bufio" // bufio "io/ioutil" // ioutil "os" // os "syscall" // syscall "plugin" // plugin "net" // net "net/http" // http //"html" // html "path/filepath" // filepath "go/types" // types "go/token" // token "encoding/base64" // base64 "unicode/utf8" // utf8 //"gshdata" // gshell's logo and source code "hash/crc32" // crc32 "golang.org/x/net/websocket" ) // // 2020-0906 added, // // CGo // #include "poll.h" // // to be closed as HTML tag :-p // typedef struct { struct pollfd fdv[8]; } pollFdv; // int pollx(pollFdv *fdv, int nfds, int timeout){ // return poll(fdv->fdv,nfds,timeout); // } import "C" // // 2020-0906 added, func CFpollIn1(fp*os.File, timeoutUs int)(ready uintptr){ var fdv = C.pollFdv{} var nfds = 1 var timeout = timeoutUs/1000 fdv.fdv[0].fd = C.int(fp.Fd()) fdv.fdv[0].events = C.POLLIN if( 0 < EventRecvFd ){ fdv.fdv[1].fd = C.int(EventRecvFd) fdv.fdv[1].events = C.POLLIN nfds += 1 } r := C.pollx(&fdv,C.int(nfds),C.int(timeout)) if( r <= 0 ){ return 0 } if (int(fdv.fdv[1].revents) & int(C.POLLIN)) != 0 { //fprintf(stderr,"--De-- got Event\n"); return uintptr(EventFdOffset + fdv.fdv[1].fd) } if (int(fdv.fdv[0].revents) & int(C.POLLIN)) != 0 { return uintptr(NormalFdOffset + fdv.fdv[0].fd) } return 0 } const ( NAME = "gsh" VERSION = "0.4.9" DATE = "2020-09-22" AUTHOR = "SatoxITS(^-^)//" ) var ( GSH_HOME = ".gsh" // under home directory GSH_PORT = 9999 MaxStreamSize = int64(128*1024*1024*1024) // 128GiB is too large? PROMPT = "> " LINESIZE = (8*1024) PATHSEP = ":" // should be ";" in Windows DIRSEP = "/" // canbe \ in Windows ) // -xX logging control // --A-- all // --I-- info. // --D-- debug // --T-- time and resource usage // --W-- warning // --E-- error // --F-- fatal error // --Xn- network // Structures type GCommandHistory struct { StartAt time.Time // command line execution started at EndAt time.Time // command line execution ended at ResCode int // exit code of (external command) CmdError error // error string OutData *os.File // output of the command FoundFile []string // output - result of ufind Rusagev [2]syscall.Rusage // Resource consumption, CPU time or so CmdId int // maybe with identified with arguments or impact // redireciton commands should not be the CmdId WorkDir string // working directory at start WorkDirX int // index in ChdirHistory CmdLine string // command line } type GChdirHistory struct { Dir string MovedAt time.Time CmdIndex int } type CmdMode struct { BackGround bool } type Event struct { when time.Time event int evarg int64 CmdIndex int } var CmdIndex int var Events []Event type PluginInfo struct { Spec *plugin.Plugin Addr plugin.Symbol Name string // maybe relative Path string // this is in Plugin but hidden } type GServer struct { host string port string } // Digest const ( // SumType SUM_ITEMS = 0x000001 // items count SUM_SIZE = 0x000002 // data length (simplly added) SUM_SIZEHASH = 0x000004 // data length (hashed sequence) SUM_DATEHASH = 0x000008 // date of data (hashed sequence) // also envelope attributes like time stamp can be a part of digest // hashed value of sizes or mod-date of files will be useful to detect changes SUM_WORDS = 0x000010 // word count is a kind of digest SUM_LINES = 0x000020 // line count is a kind of digest SUM_SUM64 = 0x000040 // simple add of bytes, useful for human too SUM_SUM32_BITS = 0x000100 // the number of true bits SUM_SUM32_2BYTE = 0x000200 // 16bits words SUM_SUM32_4BYTE = 0x000400 // 32bits words SUM_SUM32_8BYTE = 0x000800 // 64bits words SUM_SUM16_BSD = 0x001000 // UNIXsum -sum -bsd SUM_SUM16_SYSV = 0x002000 // UNIXsum -sum -sysv SUM_UNIXFILE = 0x004000 SUM_CRCIEEE = 0x008000 ) type CheckSum struct { Files int64 // the number of files (or data) Size int64 // content size Words int64 // word count Lines int64 // line count SumType int Sum64 uint64 Crc32Table crc32.Table Crc32Val uint32 Sum16 int Ctime time.Time Atime time.Time Mtime time.Time Start time.Time Done time.Time RusgAtStart [2]syscall.Rusage RusgAtEnd [2]syscall.Rusage } type ValueStack [][]string type GshContext struct { StartDir string // the current directory at the start GetLine string // gsh-getline command as a input line editor ChdirHistory []GChdirHistory // the 1st entry is wd at the start gshPA syscall.ProcAttr CommandHistory []GCommandHistory CmdCurrent GCommandHistory BackGround bool BackGroundJobs []int LastRusage syscall.Rusage GshHomeDir string TerminalId int CmdTrace bool // should be [map] CmdTime bool // should be [map] PluginFuncs []PluginInfo iValues []string iDelimiter string // field sepearater of print out iFormat string // default print format (of integer) iValStack ValueStack LastServer GServer RSERV string // [gsh://]host[:port] RWD string // remote (target, there) working directory lastCheckSum CheckSum } func nsleep(ns time.Duration){ time.Sleep(ns) } func usleep(ns time.Duration){ nsleep(ns*1000) } func msleep(ns time.Duration){ nsleep(ns*1000000) } func sleep(ns time.Duration){ nsleep(ns*1000000000) } func strBegins(str, pat string)(bool){ if len(pat) <= len(str){ yes := str[0:len(pat)] == pat //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,yes) return yes } //fmt.Printf("--D-- strBegins(%v,%v)=%v\n",str,pat,false) return false } func isin(what string, list []string) bool { for _, v := range list { if v == what { return true } } return false } func isinX(what string,list[]string)(int){ for i,v := range list { if v == what { return i } } return -1 } func env(opts []string) { env := os.Environ() if isin("-s", opts){ sort.Slice(env, func(i,j int) bool { return env[i] < env[j] }) } for _, v := range env { fmt.Printf("%v\n",v) } } // - rewriting should be context dependent // - should postpone until the real point of evaluation // - should rewrite only known notation of symobl func scanInt(str string)(val int,leng int){ leng = -1 for i,ch := range str { if '0' <= ch && ch <= '9' { leng = i+1 }else{ break } } if 0 < leng { ival,_ := strconv.Atoi(str[0:leng]) return ival,leng }else{ return 0,0 } } func substHistory(gshCtx *GshContext,str string,i int,rstr string)(leng int,rst string){ if len(str[i+1:]) == 0 { return 0,rstr } hi := 0 histlen := len(gshCtx.CommandHistory) if str[i+1] == '!' { hi = histlen - 1 leng = 1 }else{ hi,leng = scanInt(str[i+1:]) if leng == 0 { return 0,rstr } if hi < 0 { hi = histlen + hi } } if 0 <= hi && hi < histlen { var ext byte if 1 < len(str[i+leng:]) { ext = str[i+leng:][1] } //fmt.Printf("--D-- %v(%c)\n",str[i+leng:],str[i+leng]) if ext == 'f' { leng += 1 xlist := []string{} list := gshCtx.CommandHistory[hi].FoundFile for _,v := range list { //list[i] = escapeWhiteSP(v) xlist = append(xlist,escapeWhiteSP(v)) } //rstr += strings.Join(list," ") rstr += strings.Join(xlist," ") }else if ext == '@' || ext == 'd' { // !N@ .. workdir at the start of the command leng += 1 rstr += gshCtx.CommandHistory[hi].WorkDir }else{ rstr += gshCtx.CommandHistory[hi].CmdLine } }else{ leng = 0 } return leng,rstr } func escapeWhiteSP(str string)(string){ if len(str) == 0 { return "\\z" // empty, to be ignored } rstr := "" for _,ch := range str { switch ch { case '\\': rstr += "\\\\" case ' ': rstr += "\\s" case '\t': rstr += "\\t" case '\r': rstr += "\\r" case '\n': rstr += "\\n" default: rstr += string(ch) } } return rstr } func unescapeWhiteSP(str string)(string){ // strip original escapes rstr := "" for i := 0; i < len(str); i++ { ch := str[i] if ch == '\\' { if i+1 < len(str) { switch str[i+1] { case 'z': continue; } } } rstr += string(ch) } return rstr } func unescapeWhiteSPV(strv []string)([]string){ // strip original escapes ustrv := []string{} for _,v := range strv { ustrv = append(ustrv,unescapeWhiteSP(v)) } return ustrv } // str-expansion // - this should be a macro processor func strsubst(gshCtx *GshContext,str string,histonly bool) string { rbuff := []byte{} if false { //@@U Unicode should be cared as a character return str } //rstr := "" inEsc := 0 // escape characer mode for i := 0; i < len(str); i++ { //fmt.Printf("--D--Subst %v:%v\n",i,str[i:]) ch := str[i] if inEsc == 0 { if ch == '!' { //leng,xrstr := substHistory(gshCtx,str,i,rstr) leng,rs := substHistory(gshCtx,str,i,"") if 0 < leng { //_,rs := substHistory(gshCtx,str,i,"") rbuff = append(rbuff,[]byte(rs)...) i += leng //rstr = xrstr continue } } switch ch { case '\\': inEsc = '\\'; continue //case '%': inEsc = '%'; continue case '$': } } switch inEsc { case '\\': switch ch { case '\\': ch = '\\' case 's': ch = ' ' case 't': ch = '\t' case 'r': ch = '\r' case 'n': ch = '\n' case 'z': inEsc = 0; continue // empty, to be ignored } inEsc = 0 case '%': switch { case ch == '%': ch = '%' case ch == 'T': //rstr = rstr + time.Now().Format(time.Stamp) rs := time.Now().Format(time.Stamp) rbuff = append(rbuff,[]byte(rs)...) inEsc = 0 continue; default: // postpone the interpretation //rstr = rstr + "%" + string(ch) rbuff = append(rbuff,ch) inEsc = 0 continue; } inEsc = 0 } //rstr = rstr + string(ch) rbuff = append(rbuff,ch) } //fmt.Printf("--D--subst(%s)(%s)\n",str,string(rbuff)) return string(rbuff) //return rstr } func showFileInfo(path string, opts []string) { if isin("-l",opts) || isin("-ls",opts) { fi, err := os.Stat(path) if err != nil { fmt.Printf("---------- ((%v))",err) }else{ mod := fi.ModTime() date := mod.Format(time.Stamp) fmt.Printf("%v %8v %s ",fi.Mode(),fi.Size(),date) } } fmt.Printf("%s",path) if isin("-sp",opts) { fmt.Printf(" ") }else if ! isin("-n",opts) { fmt.Printf("\n") } } func userHomeDir()(string,bool){ /* homedir,_ = os.UserHomeDir() // not implemented in older Golang */ homedir,found := os.LookupEnv("HOME") //fmt.Printf("--I-- HOME=%v(%v)\n",homedir,found) if !found { return "/tmp",found } return homedir,found } func toFullpath(path string) (fullpath string) { if path[0] == '/' { return path } pathv := strings.Split(path,DIRSEP) switch { case pathv[0] == ".": pathv[0], _ = os.Getwd() case pathv[0] == "..": // all ones should be interpreted cwd, _ := os.Getwd() ppathv := strings.Split(cwd,DIRSEP) pathv[0] = strings.Join(ppathv,DIRSEP) case pathv[0] == "~": pathv[0],_ = userHomeDir() default: cwd, _ := os.Getwd() pathv[0] = cwd + DIRSEP + pathv[0] } return strings.Join(pathv,DIRSEP) } func IsRegFile(path string)(bool){ fi, err := os.Stat(path) if err == nil { fm := fi.Mode() return fm.IsRegular(); } return false } // Encode / Decode // Encoder func (gshCtx *GshContext)Enc(argv[]string){ file := os.Stdin buff := make([]byte,LINESIZE) li := 0 encoder := base64.NewEncoder(base64.StdEncoding,os.Stdout) for li = 0; ; li++ { count, err := file.Read(buff) if count <= 0 { break } if err != nil { break } encoder.Write(buff[0:count]) } encoder.Close() } func (gshCtx *GshContext)Dec(argv[]string){ decoder := base64.NewDecoder(base64.StdEncoding,os.Stdin) li := 0 buff := make([]byte,LINESIZE) for li = 0; ; li++ { count, err := decoder.Read(buff) if count <= 0 { break } if err != nil { break } os.Stdout.Write(buff[0:count]) } } // lnsp [N] [-crlf][-C \\] func (gshCtx *GshContext)SplitLine(argv[]string){ strRep := isin("-str",argv) // "..."+ reader := bufio.NewReaderSize(os.Stdin,64*1024) ni := 0 toi := 0 for ni = 0; ; ni++ { line, err := reader.ReadString('\n') if len(line) <= 0 { if err != nil { fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d (%v)\n",ni,toi,err) break } } off := 0 ilen := len(line) remlen := len(line) if strRep { os.Stdout.Write([]byte("\"")) } for oi := 0; 0 < remlen; oi++ { olen := remlen addnl := false if 72 < olen { olen = 72 addnl = true } fmt.Fprintf(os.Stderr,"--D-- write %d [%d.%d] %d %d/%d/%d\n", toi,ni,oi,off,olen,remlen,ilen) toi += 1 os.Stdout.Write([]byte(line[0:olen])) if addnl { if strRep { os.Stdout.Write([]byte("\"+\n\"")) }else{ //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } } line = line[olen:] off += olen remlen -= olen } if strRep { os.Stdout.Write([]byte("\"\n")) } } fmt.Fprintf(os.Stderr,"--I-- lnsp %d to %d\n",ni,toi) } // CRC32 crc32 // 1 0000 0100 1100 0001 0001 1101 1011 0111 var CRC32UNIX uint32 = uint32(0x04C11DB7) // Unix cksum var CRC32IEEE uint32 = uint32(0xEDB88320) func byteCRC32add(crc uint32,str[]byte,len uint64)(uint32){ var oi uint64 for oi = 0; oi < len; oi++ { var oct = str[oi] for bi := 0; bi < 8; bi++ { //fprintf(stderr,"--CRC32 %d %X (%d.%d)\n",crc,oct,oi,bi) ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } //fprintf(stderr,"--CRC32 return %d %d\n",crc,len) return crc; } func byteCRC32end(crc uint32, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len) li += 1 len >>= 8 if( len == 0 ){ break } } crc = byteCRC32add(crc,slen,uint64(li)) crc ^= 0xFFFFFFFF return crc } func strCRC32(str string,len uint64)(crc uint32){ crc = byteCRC32add(0,[]byte(str),len) crc = byteCRC32end(crc,len) //fprintf(stderr,"--CRC32 %d %d\n",crc,len) return crc } func CRC32Finish(crc uint32, table *crc32.Table, len uint64)(uint32){ var slen = make([]byte,4) var li = 0 for li = 0; li < 4; { slen[li] = byte(len & 0xFF) li += 1 len >>= 8 if( len == 0 ){ break } } crc = crc32.Update(crc,table,slen) crc ^= 0xFFFFFFFF return crc } func (gsh*GshContext)xCksum(path string,argv[]string, sum*CheckSum)(int64){ if isin("-type/f",argv) && !IsRegFile(path){ return 0 } if isin("-type/d",argv) && IsRegFile(path){ return 0 } file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- cksum %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- cksum %v %v\n",path,argv) } bi := 0 var buff = make([]byte,32*1024) var total int64 = 0 var initTime = time.Time{} if sum.Start == initTime { sum.Start = time.Now() } for bi = 0; ; bi++ { count,err := file.Read(buff) if count <= 0 || err != nil { break } if (sum.SumType & SUM_SUM64) != 0 { s := sum.Sum64 for _,c := range buff[0:count] { s += uint64(c) } sum.Sum64 = s } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32add(sum.Crc32Val,buff,uint64(count)) } if (sum.SumType & SUM_CRCIEEE) != 0 { sum.Crc32Val = crc32.Update(sum.Crc32Val,&sum.Crc32Table,buff[0:count]) } // BSD checksum if (sum.SumType & SUM_SUM16_BSD) != 0 { s := sum.Sum16 for _,c := range buff[0:count] { s = (s >> 1) + ((s & 1) << 15) s += int(c) s &= 0xFFFF //fmt.Printf("BSDsum: %d[%d] %d\n",sum.Size+int64(i),i,s) } sum.Sum16 = s } if (sum.SumType & SUM_SUM16_SYSV) != 0 { for bj := 0; bj < count; bj++ { sum.Sum16 += int(buff[bj]) } } total += int64(count) } sum.Done = time.Now() sum.Files += 1 sum.Size += total if !isin("-s",argv) { fmt.Printf("%v ",total) } return 0 } // grep // "lines", "lin" or "lnp" for "(text) line processor" or "scanner" // a*,!ab,c, ... sequentioal combination of patterns // what "LINE" is should be definable // generic line-by-line processing // grep [-v] // cat -n -v // uniq [-c] // tail -f // sed s/x/y/ or awk // grep with line count like wc // rewrite contents if specified func (gsh*GshContext)xGrep(path string,rexpv[]string)(int){ file, err := os.OpenFile(path,os.O_RDONLY,0) if err != nil { fmt.Printf("--E-- grep %v (%v)\n",path,err) return -1 } defer file.Close() if gsh.CmdTrace { fmt.Printf("--I-- grep %v %v\n",path,rexpv) } //reader := bufio.NewReaderSize(file,LINESIZE) reader := bufio.NewReaderSize(file,80) li := 0 found := 0 for li = 0; ; li++ { line, err := reader.ReadString('\n') if len(line) <= 0 { break } if 150 < len(line) { // maybe binary break; } if err != nil { break } if 0 <= strings.Index(string(line),rexpv[0]) { found += 1 fmt.Printf("%s:%d: %s",path,li,line) } } //fmt.Printf("total %d lines %s\n",li,path) //if( 0 < found ){ fmt.Printf("((found %d lines %s))\n",found,path); } return found } // Finder // finding files with it name and contents // file names are ORed // show the content with %x fmt list // ls -R // tar command by adding output type fileSum struct { Err int64 // access error or so Size int64 // content size DupSize int64 // content size from hard links Blocks int64 // number of blocks (of 512 bytes) DupBlocks int64 // Blocks pointed from hard links HLinks int64 // hard links Words int64 Lines int64 Files int64 Dirs int64 // the num. of directories SymLink int64 Flats int64 // the num. of flat files MaxDepth int64 MaxNamlen int64 // max. name length nextRepo time.Time } func showFusage(dir string,fusage *fileSum){ bsume := float64(((fusage.Blocks-fusage.DupBlocks)/2)*1024)/1000000.0 //bsumdup := float64((fusage.Blocks/2)*1024)/1000000.0 fmt.Printf("%v: %v files (%vd %vs %vh) %.6f MB (%.2f MBK)\n", dir, fusage.Files, fusage.Dirs, fusage.SymLink, fusage.HLinks, float64(fusage.Size)/1000000.0,bsume); } const ( S_IFMT = 0170000 S_IFCHR = 0020000 S_IFDIR = 0040000 S_IFREG = 0100000 S_IFLNK = 0120000 S_IFSOCK = 0140000 ) func cumFinfo(fsum *fileSum, path string, staterr error, fstat syscall.Stat_t, argv[]string,verb bool)(*fileSum){ now := time.Now() if time.Second <= now.Sub(fsum.nextRepo) { if !fsum.nextRepo.IsZero(){ tstmp := now.Format(time.Stamp) showFusage(tstmp,fsum) } fsum.nextRepo = now.Add(time.Second) } if staterr != nil { fsum.Err += 1 return fsum } fsum.Files += 1 if 1 < fstat.Nlink { // must count only once... // at least ignore ones in the same directory //if finfo.Mode().IsRegular() { if (fstat.Mode & S_IFMT) == S_IFREG { fsum.HLinks += 1 fsum.DupBlocks += int64(fstat.Blocks) //fmt.Printf("---Dup HardLink %v %s\n",fstat.Nlink,path) } } //fsum.Size += finfo.Size() fsum.Size += fstat.Size fsum.Blocks += int64(fstat.Blocks) //if verb { fmt.Printf("(%8dBlk) %s",fstat.Blocks/2,path) } if isin("-ls",argv){ //if verb { fmt.Printf("%4d %8d ",fstat.Blksize,fstat.Blocks) } // fmt.Printf("%d\t",fstat.Blocks/2) } //if finfo.IsDir() if (fstat.Mode & S_IFMT) == S_IFDIR { fsum.Dirs += 1 } //if (finfo.Mode() & os.ModeSymlink) != 0 if (fstat.Mode & S_IFMT) == S_IFLNK { //if verb { fmt.Printf("symlink(%v,%s)\n",fstat.Mode,finfo.Name()) } //{ fmt.Printf("symlink(%o,%s)\n",fstat.Mode,finfo.Name()) } fsum.SymLink += 1 } return fsum } func (gsh*GshContext)xxFindEntv(depth int,total *fileSum,dir string, dstat syscall.Stat_t, ei int, entv []string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) // sort entv /* if isin("-t",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].ModTime().Sub(filev[j].ModTime()) }) } */ /* if isin("-u",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].AccTime().Sub(filev[j].AccTime()) }) } if isin("-U",argv){ sort.Slice(filev, func(i,j int) bool { return 0 < filev[i].CreatTime().Sub(filev[j].CreatTime()) }) } */ /* if isin("-S",argv){ sort.Slice(filev, func(i,j int) bool { return filev[j].Size() < filev[i].Size() }) } */ for _,filename := range entv { for _,npat := range npatv { match := true if npat == "*" { match = true }else{ match, _ = filepath.Match(npat,filename) } path := dir + DIRSEP + filename if !match { continue } var fstat syscall.Stat_t staterr := syscall.Lstat(path,&fstat) if staterr != nil { if !isin("-w",argv){fmt.Printf("ufind: %v\n",staterr) } continue; } if isin("-du",argv) && (fstat.Mode & S_IFMT) == S_IFDIR { // should not show size of directory in "-du" mode ... }else if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { if isin("-du",argv) { fmt.Printf("%d\t",fstat.Blocks/2) } showFileInfo(path,argv) } if true { // && isin("-du",argv) total = cumFinfo(total,path,staterr,fstat,argv,false) } /* if isin("-wc",argv) { } */ if gsh.lastCheckSum.SumType != 0 { gsh.xCksum(path,argv,&gsh.lastCheckSum); } x := isinX("-grep",argv); // -grep will be convenient like -ls if 0 <= x && x+1 <= len(argv) { // -grep will be convenient like -ls if IsRegFile(path){ found := gsh.xGrep(path,argv[x+1:]) if 0 < found { foundv := gsh.CmdCurrent.FoundFile if len(foundv) < 10 { gsh.CmdCurrent.FoundFile = append(gsh.CmdCurrent.FoundFile,path) } } } } if !isin("-r0",argv) { // -d 0 in du, -depth n in find //total.Depth += 1 if (fstat.Mode & S_IFMT) == S_IFLNK { continue } if dstat.Rdev != fstat.Rdev { fmt.Printf("--I-- don't follow differnet device %v(%v) %v(%v)\n", dir,dstat.Rdev,path,fstat.Rdev) } if (fstat.Mode & S_IFMT) == S_IFDIR { total = gsh.xxFind(depth+1,total,path,npatv,argv) } } } } return total } func (gsh*GshContext)xxFind(depth int,total *fileSum,dir string,npatv[]string,argv[]string)(*fileSum){ nols := isin("-grep",argv) dirfile,oerr := os.OpenFile(dir,os.O_RDONLY,0) if oerr == nil { //fmt.Printf("--I-- %v(%v)[%d]\n",dir,dirfile,dirfile.Fd()) defer dirfile.Close() }else{ } prev := *total var dstat syscall.Stat_t staterr := syscall.Lstat(dir,&dstat) // should be flstat if staterr != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",staterr) } return total } //filev,err := ioutil.ReadDir(dir) //_,err := ioutil.ReadDir(dir) // ReadDir() heavy and bad for huge directory /* if err != nil { if !isin("-w",argv){ fmt.Printf("ufind: %v\n",err) } return total } */ if depth == 0 { total = cumFinfo(total,dir,staterr,dstat,argv,true) if !nols && !isin("-s",argv) && (!isin("-du",argv) || isin("-a",argv)) { showFileInfo(dir,argv) } } // it it is not a directory, just scan it and finish for ei := 0; ; ei++ { entv,rderr := dirfile.Readdirnames(8*1024) if len(entv) == 0 || rderr != nil { //if rderr != nil { fmt.Printf("[%d] len=%d (%v)\n",ei,len(entv),rderr) } break } if 0 < ei { fmt.Printf("--I-- xxFind[%d] %d large-dir: %s\n",ei,len(entv),dir) } total = gsh.xxFindEntv(depth,total,dir,dstat,ei,entv,npatv,argv) } if isin("-du",argv) { // if in "du" mode fmt.Printf("%d\t%s\n",(total.Blocks-prev.Blocks)/2,dir) } return total } // {ufind|fu|ls} [Files] [// Names] [-- Expressions] // Files is "." by default // Names is "*" by default // Expressions is "-print" by default for "ufind", or -du for "fu" command func (gsh*GshContext)xFind(argv[]string){ if 0 < len(argv) && strBegins(argv[0],"?"){ showFound(gsh,argv) return } if isin("-cksum",argv) || isin("-sum",argv) { gsh.lastCheckSum = CheckSum{} if isin("-sum",argv) && isin("-add",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 }else if isin("-sum",argv) && isin("-size",argv) { gsh.lastCheckSum.SumType |= SUM_SIZE }else if isin("-sum",argv) && isin("-bsd",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_BSD }else if isin("-sum",argv) && isin("-sysv",argv) { gsh.lastCheckSum.SumType |= SUM_SUM16_SYSV }else if isin("-sum",argv) { gsh.lastCheckSum.SumType |= SUM_SUM64 } if isin("-unix",argv) { gsh.lastCheckSum.SumType |= SUM_UNIXFILE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32UNIX) } if isin("-ieee",argv){ gsh.lastCheckSum.SumType |= SUM_CRCIEEE gsh.lastCheckSum.Crc32Table = *crc32.MakeTable(CRC32IEEE) } gsh.lastCheckSum.RusgAtStart = Getrusagev() } var total = fileSum{} npats := []string{} for _,v := range argv { if 0 < len(v) && v[0] != '-' { npats = append(npats,v) } if v == "//" { break } if v == "--" { break } if v == "-grep" { break } if v == "-ls" { break } } if len(npats) == 0 { npats = []string{"*"} } cwd := "." // if to be fullpath ::: cwd, _ := os.Getwd() if len(npats) == 0 { npats = []string{"*"} } fusage := gsh.xxFind(0,&total,cwd,npats,argv) if gsh.lastCheckSum.SumType != 0 { var sumi uint64 = 0 sum := &gsh.lastCheckSum if (sum.SumType & SUM_SIZE) != 0 { sumi = uint64(sum.Size) } if (sum.SumType & SUM_SUM64) != 0 { sumi = sum.Sum64 } if (sum.SumType & SUM_SUM16_SYSV) != 0 { s := uint32(sum.Sum16) r := (s & 0xFFFF) + ((s & 0xFFFFFFFF) >> 16) s = (r & 0xFFFF) + (r >> 16) sum.Crc32Val = uint32(s) sumi = uint64(s) } if (sum.SumType & SUM_SUM16_BSD) != 0 { sum.Crc32Val = uint32(sum.Sum16) sumi = uint64(sum.Sum16) } if (sum.SumType & SUM_UNIXFILE) != 0 { sum.Crc32Val = byteCRC32end(sum.Crc32Val,uint64(sum.Size)) sumi = uint64(byteCRC32end(sum.Crc32Val,uint64(sum.Size))) } if 1 < sum.Files { fmt.Printf("%v %v // %v / %v files, %v/file\r\n", sumi,sum.Size, abssize(sum.Size),sum.Files, abssize(sum.Size/sum.Files)) }else{ fmt.Printf("%v %v %v\n", sumi,sum.Size,npats[0]) } } if !isin("-grep",argv) { showFusage("total",fusage) } if !isin("-s",argv){ hits := len(gsh.CmdCurrent.FoundFile) if 0 < hits { fmt.Printf("--I-- %d files hits // can be refered with !%df\n", hits,len(gsh.CommandHistory)) } } if gsh.lastCheckSum.SumType != 0 { if isin("-ru",argv) { sum := &gsh.lastCheckSum sum.Done = time.Now() gsh.lastCheckSum.RusgAtEnd = Getrusagev() elps := sum.Done.Sub(sum.Start) fmt.Printf("--cksum-size: %v (%v) / %v files, %v/file\r\n", sum.Size,abssize(sum.Size),sum.Files,abssize(sum.Size/sum.Files)) nanos := int64(elps) fmt.Printf("--cksum-time: %v/total, %v/file, %.1f files/s, %v\r\n", abbtime(nanos), abbtime(nanos/sum.Files), (float64(sum.Files)*1000000000.0)/float64(nanos), abbspeed(sum.Size,nanos)) diff := RusageSubv(sum.RusgAtEnd,sum.RusgAtStart) fmt.Printf("--cksum-rusg: %v\n",sRusagef("",argv,diff)) } } return } func showFiles(files[]string){ sp := "" for i,file := range files { if 0 < i { sp = " " } else { sp = "" } fmt.Printf(sp+"%s",escapeWhiteSP(file)) } } func showFound(gshCtx *GshContext, argv[]string){ for i,v := range gshCtx.CommandHistory { if 0 < len(v.FoundFile) { fmt.Printf("!%d (%d) ",i,len(v.FoundFile)) if isin("-ls",argv){ fmt.Printf("\n") for _,file := range v.FoundFile { fmt.Printf("") //sub number? showFileInfo(file,argv) } }else{ showFiles(v.FoundFile) fmt.Printf("\n") } } } } func showMatchFile(filev []os.FileInfo, npat,dir string, argv[]string)(string,bool){ fname := "" found := false for _,v := range filev { match, _ := filepath.Match(npat,(v.Name())) if match { fname = v.Name() found = true //fmt.Printf("[%d] %s\n",i,v.Name()) showIfExecutable(fname,dir,argv) } } return fname,found } func showIfExecutable(name,dir string,argv[]string)(ffullpath string,ffound bool){ var fullpath string if strBegins(name,DIRSEP){ fullpath = name }else{ fullpath = dir + DIRSEP + name } fi, err := os.Stat(fullpath) if err != nil { fullpath = dir + DIRSEP + name + ".go" fi, err = os.Stat(fullpath) } if err == nil { fm := fi.Mode() if fm.IsRegular() { // R_OK=4, W_OK=2, X_OK=1, F_OK=0 if syscall.Access(fullpath,5) == nil { ffullpath = fullpath ffound = true if ! isin("-s", argv) { showFileInfo(fullpath,argv) } } } } return ffullpath, ffound } func which(list string, argv []string) (fullpathv []string, itis bool){ if len(argv) <= 1 { fmt.Printf("Usage: which comand [-s] [-a] [-ls]\n") return []string{""}, false } path := argv[1] if strBegins(path,"/") { // should check if excecutable? _,exOK := showIfExecutable(path,"/",argv) fmt.Printf("--D-- %v exOK=%v\n",path,exOK) return []string{path},exOK } pathenv, efound := os.LookupEnv(list) if ! efound { fmt.Printf("--E-- which: no \"%s\" environment\n",list) return []string{""}, false } showall := isin("-a",argv) || 0 <= strings.Index(path,"*") dirv := strings.Split(pathenv,PATHSEP) ffound := false ffullpath := path for _, dir := range dirv { if 0 <= strings.Index(path,"*") { // by wild-card list,_ := ioutil.ReadDir(dir) ffullpath, ffound = showMatchFile(list,path,dir,argv) }else{ ffullpath, ffound = showIfExecutable(path,dir,argv) } //if ffound && !isin("-a", argv) { if ffound && !showall { break; } } return []string{ffullpath}, ffound } func stripLeadingWSParg(argv[]string)([]string){ for ; 0 < len(argv); { if len(argv[0]) == 0 { argv = argv[1:] }else{ break } } return argv } func xEval(argv []string, nlend bool){ argv = stripLeadingWSParg(argv) if len(argv) == 0 { fmt.Printf("eval [%%format] [Go-expression]\n") return } pfmt := "%v" if argv[0][0] == '%' { pfmt = argv[0] argv = argv[1:] } if len(argv) == 0 { return } gocode := strings.Join(argv," "); //fmt.Printf("eval [%v] [%v]\n",pfmt,gocode) fset := token.NewFileSet() rval, _ := types.Eval(fset,nil,token.NoPos,gocode) fmt.Printf(pfmt,rval.Value) if nlend { fmt.Printf("\n") } } func getval(name string) (found bool, val int) { /* should expand the name here */ if name == "gsh.pid" { return true, os.Getpid() }else if name == "gsh.ppid" { return true, os.Getppid() } return false, 0 } func echo(argv []string, nlend bool){ for ai := 1; ai < len(argv); ai++ { if 1 < ai { fmt.Printf(" "); } arg := argv[ai] found, val := getval(arg) if found { fmt.Printf("%d",val) }else{ fmt.Printf("%s",arg) } } if nlend { fmt.Printf("\n"); } } func resfile() string { return "gsh.tmp" } //var resF *File func resmap() { //_ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, os.ModeAppend) // https://developpaper.com/solution-to-golang-bad-file-descriptor-problem/ _ , err := os.OpenFile(resfile(), os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Printf("refF could not open: %s\n",err) }else{ fmt.Printf("refF opened\n") } } // @@2020-0821 func gshScanArg(str string,strip int)(argv []string){ var si = 0 var sb = 0 var inBracket = 0 var arg1 = make([]byte,LINESIZE) var ax = 0 debug := false for ; si < len(str); si++ { if str[si] != ' ' { break } } sb = si for ; si < len(str); si++ { if sb <= si { if debug { fmt.Printf("--Da- +%d %2d-%2d %s ... %s\n", inBracket,sb,si,arg1[0:ax],str[si:]) } } ch := str[si] if ch == '{' { inBracket += 1 if 0 < strip && inBracket <= strip { //fmt.Printf("stripLEV %d <= %d?\n",inBracket,strip) continue } } if 0 < inBracket { if ch == '}' { inBracket -= 1 if 0 < strip && inBracket < strip { //fmt.Printf("stripLEV %d < %d?\n",inBracket,strip) continue } } arg1[ax] = ch ax += 1 continue } if str[si] == ' ' { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,str[sb:si],string(str[si:])) } sb = si+1 ax = 0 continue } arg1[ax] = ch ax += 1 } if sb < si { argv = append(argv,string(arg1[0:ax])) if debug { fmt.Printf("--Da- [%v][%v-%v] %s ... %s\n", -1+len(argv),sb,si,string(arg1[0:ax]),string(str[si:])) } } if debug { fmt.Printf("--Da- %d [%s] => [%d]%v\n",strip,str,len(argv),argv) } return argv } // should get stderr (into tmpfile ?) and return func (gsh*GshContext)Popen(name,mode string)(pin*os.File,pout*os.File,err bool){ var pv = []int{-1,-1} syscall.Pipe(pv) xarg := gshScanArg(name,1) name = strings.Join(xarg," ") pin = os.NewFile(uintptr(pv[0]),"StdoutOf-{"+name+"}") pout = os.NewFile(uintptr(pv[1]),"StdinOf-{"+name+"}") fdix := 0 dir := "?" if mode == "r" { dir = "<" fdix = 1 // read from the stdout of the process }else{ dir = ">" fdix = 0 // write to the stdin of the process } gshPA := gsh.gshPA savfd := gshPA.Files[fdix] var fd uintptr = 0 if mode == "r" { fd = pout.Fd() gshPA.Files[fdix] = pout.Fd() }else{ fd = pin.Fd() gshPA.Files[fdix] = pin.Fd() } // should do this by Goroutine? if false { fmt.Printf("--Ip- Opened fd[%v] %s %v\n",fd,dir,name) fmt.Printf("--RED1 [%d,%d,%d]->[%d,%d,%d]\n", os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd(), pin.Fd(),pout.Fd(),pout.Fd()) } savi := os.Stdin savo := os.Stdout save := os.Stderr os.Stdin = pin os.Stdout = pout os.Stderr = pout gsh.BackGround = true gsh.gshelllh(name) gsh.BackGround = false os.Stdin = savi os.Stdout = savo os.Stderr = save gshPA.Files[fdix] = savfd return pin,pout,false } // External commands func (gsh*GshContext)excommand(exec bool, argv []string) (notf bool,exit bool) { if gsh.CmdTrace { fmt.Printf("--I-- excommand[%v](%v)\n",exec,argv) } gshPA := gsh.gshPA fullpathv, itis := which("PATH",[]string{"which",argv[0],"-s"}) if itis == false { return true,false } fullpath := fullpathv[0] argv = unescapeWhiteSPV(argv) if 0 < strings.Index(fullpath,".go") { nargv := argv // []string{} gofullpathv, itis := which("PATH",[]string{"which","go","-s"}) if itis == false { fmt.Printf("--F-- Go not found\n") return false,true } gofullpath := gofullpathv[0] nargv = []string{ gofullpath, "run", fullpath } fmt.Printf("--I-- %s {%s %s %s}\n",gofullpath, nargv[0],nargv[1],nargv[2]) if exec { syscall.Exec(gofullpath,nargv,os.Environ()) }else{ pid, _ := syscall.ForkExec(gofullpath,nargv,&gshPA) if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),nargv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage) gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } }else{ if exec { syscall.Exec(fullpath,argv,os.Environ()) }else{ pid, _ := syscall.ForkExec(fullpath,argv,&gshPA) //fmt.Printf("[%d]\n",pid); // '&' to be background if gsh.BackGround { fmt.Fprintf(stderr,"--Ip- in Background pid[%d]%d(%v)\n",pid,len(argv),argv) gsh.BackGroundJobs = append(gsh.BackGroundJobs,pid) }else{ rusage := syscall.Rusage {} syscall.Wait4(pid,nil,0,&rusage); gsh.LastRusage = rusage gsh.CmdCurrent.Rusagev[1] = rusage } } } return false,false } // Builtin Commands func (gshCtx *GshContext) sleep(argv []string) { if len(argv) < 2 { fmt.Printf("Sleep 100ms, 100us, 100ns, ...\n") return } duration := argv[1]; d, err := time.ParseDuration(duration) if err != nil { d, err = time.ParseDuration(duration+"s") if err != nil { fmt.Printf("duration ? %s (%s)\n",duration,err) return } } //fmt.Printf("Sleep %v\n",duration) time.Sleep(d) if 0 < len(argv[2:]) { gshCtx.gshellv(argv[2:]) } } func (gshCtx *GshContext)repeat(argv []string) { if len(argv) < 2 { return } start0 := time.Now() for ri,_ := strconv.Atoi(argv[1]); 0 < ri; ri-- { if 0 < len(argv[2:]) { //start := time.Now() gshCtx.gshellv(argv[2:]) end := time.Now() elps := end.Sub(start0); if( 1000000000 < elps ){ fmt.Printf("(repeat#%d %v)\n",ri,elps); } } } } func (gshCtx *GshContext)gen(argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: %s N\n",argv[0]) return } // should br repeated by "repeat" command count, _ := strconv.Atoi(argv[1]) fd := gshPA.Files[1] // Stdout file := os.NewFile(fd,"internalStdOut") fmt.Printf("--I-- Gen. Count=%d to [%d]\n",count,file.Fd()) //buf := []byte{} outdata := "0123 5678 0123 5678 0123 5678 0123 5678\r" for gi := 0; gi < count; gi++ { file.WriteString(outdata) } //file.WriteString("\n") fmt.Printf("\n(%d B)\n",count*len(outdata)); //file.Close() } // Remote Execution // 2020-0820 func Elapsed(from time.Time)(string){ elps := time.Now().Sub(from) if 1000000000 < elps { return fmt.Sprintf("[%5d.%02ds]",elps/1000000000,(elps%1000000000)/10000000) }else if 1000000 < elps { return fmt.Sprintf("[%3d.%03dms]",elps/1000000,(elps%1000000)/1000) }else{ return fmt.Sprintf("[%3d.%03dus]",elps/1000,(elps%1000)) } } func abbtime(nanos int64)(string){ if 1000000000 < nanos { return fmt.Sprintf("%d.%02ds",nanos/1000000000,(nanos%1000000000)/10000000) }else if 1000000 < nanos { return fmt.Sprintf("%d.%03dms",nanos/1000000,(nanos%1000000)/1000) }else{ return fmt.Sprintf("%d.%03dus",nanos/1000,(nanos%1000)) } } func abssize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%.3fKiB",fsize/1024) } } func absize(size int64)(string){ fsize := float64(size) if 1024*1024*1024 < size { return fmt.Sprintf("%8.2fGiB",fsize/(1024*1024*1024)) }else if 1024*1024 < size { return fmt.Sprintf("%8.3fMiB",fsize/(1024*1024)) }else{ return fmt.Sprintf("%8.3fKiB",fsize/1024) } } func abbspeed(totalB int64,ns int64)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGB/s",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMB/s",MBs) }else{ return fmt.Sprintf("%6.3fKB/s",MBs*1000) } } func abspeed(totalB int64,ns time.Duration)(string){ MBs := (float64(totalB)/1000000) / (float64(ns)/1000000000) if 1000 <= MBs { return fmt.Sprintf("%6.3fGBps",MBs/1000) } if 1 <= MBs { return fmt.Sprintf("%6.3fMBps",MBs) }else{ return fmt.Sprintf("%6.3fKBps",MBs*1000) } } func fileRelay(what string,in*os.File,out*os.File,size int64,bsiz int)(wcount int64){ Start := time.Now() buff := make([]byte,bsiz) var total int64 = 0 var rem int64 = size nio := 0 Prev := time.Now() var PrevSize int64 = 0 fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) START\n", what,absize(total),size,nio) for i:= 0; ; i++ { var len = bsiz if int(rem) < len { len = int(rem) } Now := time.Now() Elps := Now.Sub(Prev); if 1000000000 < Now.Sub(Prev) { fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %s\n", what,absize(total),size,nio, abspeed((total-PrevSize),Elps)) Prev = Now; PrevSize = total } rlen := len if in != nil { // should watch the disconnection of out rcc,err := in.Read(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"--En- X: %s read(%v,%v)<%v\n", what,rcc,err,in.Name()) break } rlen = rcc if string(buff[0:10]) == "((SoftEOF " { var ecc int64 = 0 fmt.Sscanf(string(buff),"((SoftEOF %v",&ecc) fmt.Printf(Elapsed(Start)+"--En- X: %s Recv ((SoftEOF %v))/%v\n", what,ecc,total) if ecc == total { break } } } wlen := rlen if out != nil { wcc,err := out.Write(buff[0:rlen]) if err != nil { fmt.Printf(Elapsed(Start)+"-En-- X: %s write(%v,%v)>%v\n", what,wcc,err,out.Name()) break } wlen = wcc } if wlen < rlen { fmt.Printf(Elapsed(Start)+"--En- X: %s incomplete write (%v/%v)\n", what,wlen,rlen) break; } nio += 1 total += int64(rlen) rem -= int64(rlen) if rem <= 0 { break } } Done := time.Now() Elps := float64(Done.Sub(Start))/1000000000 //Seconds TotalMB := float64(total)/1000000 //MB MBps := TotalMB / Elps fmt.Printf(Elapsed(Start)+"--In- X: %s (%v/%v/%v) %v %.3fMB/s\n", what,total,size,nio,absize(total),MBps) return total } func tcpPush(clnt *os.File){ // shrink socket buffer and recover usleep(100); } func (gsh*GshContext)RexecServer(argv[]string){ debug := true Start0 := time.Now() Start := Start0 // if local == ":" { local = "0.0.0.0:9999" } local := "0.0.0.0:9999" if 0 < len(argv) { if argv[0] == "-s" { debug = false argv = argv[1:] } } if 0 < len(argv) { argv = argv[1:] } port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("--En- S: Address error: %s (%s)\n",local,err) return } fmt.Printf(Elapsed(Start)+"--In- S: Listening at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Listen error: %s (%s)\n",local,err) return } reqbuf := make([]byte,LINESIZE) res := "" for { fmt.Printf(Elapsed(Start0)+"--In- S: Listening at %s...\n",local); aconn, err := sconn.AcceptTCP() Start = time.Now() if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: Accept error: %s (%s)\n",local,err) return } clnt, _ := aconn.File() fd := clnt.Fd() ar := aconn.RemoteAddr() if debug { fmt.Printf(Elapsed(Start0)+"--In- S: Accepted TCP at %s [%d] <- %v\n", local,fd,ar) } res = fmt.Sprintf("220 GShell/%s Server\r\n",VERSION) fmt.Fprintf(clnt,"%s",res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %s",res) } count, err := clnt.Read(reqbuf) if err != nil { fmt.Printf(Elapsed(Start)+"--En- C: (%v %v) %v", count,err,string(reqbuf)) } req := string(reqbuf[:count]) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",string(req)) } reqv := strings.Split(string(req),"\r") cmdv := gshScanArg(reqv[0],0) //cmdv := strings.Split(reqv[0]," ") switch cmdv[0] { case "HELO": res = fmt.Sprintf("250 %v",req) case "GET": // download {remotefile|-zN} [localfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var in *os.File = nil var pseudoEOF = false if 1 < len(cmdv) { fname = cmdv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() in = xin dsize = MaxStreamSize pseudoEOF = true } }else{ xin,err := os.Open(fname) if err != nil { fmt.Printf("--En- GET (%v)\n",err) }else{ defer xin.Close() in = xin fi,_ := xin.Stat() dsize = fi.Size() } } } //fmt.Printf(Elapsed(Start)+"--In- GET %v:%v\n",dsize,bsize) res = fmt.Sprintf("200 %v\r\n",dsize) fmt.Fprintf(clnt,"%v",res) tcpPush(clnt); // should be separated as line in receiver fmt.Printf(Elapsed(Start)+"--In- S: %v",res) wcount := fileRelay("SendGET",in,clnt,dsize,bsize) if pseudoEOF { in.Close() // pipe from the command // show end of stream data (its size) by OOB? SoftEOF := fmt.Sprintf("((SoftEOF %v))",wcount) fmt.Printf(Elapsed(Start)+"--In- S: Send %v\n",SoftEOF) tcpPush(clnt); // to let SoftEOF data apper at the top of recevied data fmt.Fprintf(clnt,"%v\r\n",SoftEOF) tcpPush(clnt); // to let SoftEOF alone in a packet (separate with 200 OK) // with client generated random? //fmt.Printf("--In- L: close %v (%v)\n",in.Fd(),in.Name()) } res = fmt.Sprintf("200 GET done\r\n") case "PUT": // upload {srcfile|-zN} [dstfile] var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var fname string = "" var out *os.File = nil if 1 < len(cmdv) { // localfile fmt.Sscanf(cmdv[1],"%d",&dsize) } if 2 < len(cmdv) { fname = cmdv[2] if fname == "-" { // nul dev }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) //fmt.Printf("--In- S: open(%v) out(%v) err(%v)\n",fname,xout,err) if err != nil { fmt.Printf("--En- PUT (%v)\n",err) }else{ out = xout } } fmt.Printf(Elapsed(Start)+"--In- L: open(%v,w) %v (%v)\n", fname,local,err) } fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) fmt.Printf(Elapsed(Start)+"--In- S: 200 %v OK\r\n",dsize) fmt.Fprintf(clnt,"200 %v OK\r\n",dsize) fileRelay("RecvPUT",clnt,out,dsize,bsize) res = fmt.Sprintf("200 PUT done\r\n") default: res = fmt.Sprintf("400 What? %v",req) } swcc,serr := clnt.Write([]byte(res)) if serr != nil { fmt.Printf(Elapsed(Start)+"--In- S: (wc=%v er=%v) %v",swcc,serr,res) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",res) } aconn.Close(); clnt.Close(); } sconn.Close(); } func (gsh*GshContext)RexecClient(argv[]string)(int,string){ debug := true Start := time.Now() if len(argv) == 1 { return -1,"EmptyARG" } argv = argv[1:] if argv[0] == "-serv" { gsh.RexecServer(argv[1:]) return 0,"Server" } remote := "0.0.0.0:9999" if argv[0][0] == '@' { remote = argv[0][1:] argv = argv[1:] } if argv[0] == "-s" { debug = false argv = argv[1:] } dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf(Elapsed(Start)+"Address error: %s (%s)\n",remote,err) return -1,"AddressError" } fmt.Printf(Elapsed(Start)+"--In- C: Connecting to %s\n",remote) serv, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf(Elapsed(Start)+"Connection error: %s (%s)\n",remote,err) return -1,"CannotConnect" } if debug { al := serv.LocalAddr() fmt.Printf(Elapsed(Start)+"--In- C: Connected to %v <- %v\n",remote,al) } req := "" res := make([]byte,LINESIZE) count,err := serv.Read(res) if err != nil { fmt.Printf("--En- S: (%3d,%v) %v",count,err,string(res)) } if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res)) } if argv[0] == "GET" { savPA := gsh.gshPA var bsize int = 64*1024 req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) fmt.Printf(Elapsed(Start)+"--In- C: %v",req) fmt.Fprintf(serv,req) count,err = serv.Read(res) if err != nil { }else{ var dsize int64 = 0 var out *os.File = nil var out_tobeclosed *os.File = nil var fname string = "" var rcode int = 0 var pid int = -1 fmt.Sscanf(string(res),"%d %d",&rcode,&dsize) fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) if 3 <= len(argv) { fname = argv[2] if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"w") if err { }else{ xin.Close() defer xout.Close() out = xout out_tobeclosed = xout pid = 0 // should be its pid } }else{ // should write to temporary file // should suppress ^C on tty xout,err := os.OpenFile(fname,os.O_CREATE|os.O_RDWR|os.O_TRUNC,0600) if err != nil { fmt.Print("--En- %v\n",err) } out = xout //fmt.Printf("--In-- %d > %s\n",out.Fd(),fname) } } in,_ := serv.File() fileRelay("RecvGET",in,out,dsize,bsize) if 0 <= pid { gsh.gshPA = savPA // recovery of Fd(), and more? fmt.Printf(Elapsed(Start)+"--In- L: close Pipe > %v\n",fname) out_tobeclosed.Close() //syscall.Wait4(pid,nil,0,nil) //@@ } } }else if argv[0] == "PUT" { remote, _ := serv.File() var local *os.File = nil var dsize int64 = 32*1024*1024 var bsize int = 64*1024 var ofile string = "-" //fmt.Printf("--I-- Rex %v\n",argv) if 1 < len(argv) { fname := argv[1] if strBegins(fname,"-z") { fmt.Sscanf(fname[2:],"%d",&dsize) }else if strBegins(fname,"{") { xin,xout,err := gsh.Popen(fname,"r") if err { }else{ xout.Close() defer xin.Close() //in = xin local = xin fmt.Printf("--In- [%d] < Upload output of %v\n", local.Fd(),fname) ofile = "-from."+fname dsize = MaxStreamSize } }else{ xlocal,err := os.Open(fname) if err != nil { fmt.Printf("--En- (%s)\n",err) local = nil }else{ local = xlocal fi,_ := local.Stat() dsize = fi.Size() defer local.Close() //fmt.Printf("--I-- Rex in(%v / %v)\n",ofile,dsize) } ofile = fname fmt.Printf(Elapsed(Start)+"--In- L: open(%v,r)=%v %v (%v)\n", fname,dsize,local,err) } } if 2 < len(argv) && argv[2] != "" { ofile = argv[2] //fmt.Printf("(%d)%v B.ofile=%v\n",len(argv),argv,ofile) } //fmt.Printf(Elapsed(Start)+"--I-- Rex out(%v)\n",ofile) fmt.Printf(Elapsed(Start)+"--In- PUT %v (/%v)\n",dsize,bsize) req = fmt.Sprintf("PUT %v %v \r\n",dsize,ofile) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) count,err = serv.Read(res) if debug { fmt.Printf(Elapsed(Start)+"--In- S: %v",string(res[0:count])) } fileRelay("SendPUT",local,remote,dsize,bsize) }else{ req = fmt.Sprintf("%v\r\n",strings.Join(argv," ")) if debug { fmt.Printf(Elapsed(Start)+"--In- C: %v",req) } fmt.Fprintf(serv,"%v",req) //fmt.Printf("--In- sending RexRequest(%v)\n",len(req)) } //fmt.Printf(Elapsed(Start)+"--In- waiting RexResponse...\n") count,err = serv.Read(res) ress := "" if count == 0 { ress = "(nil)\r\n" }else{ ress = string(res[:count]) } if err != nil { fmt.Printf(Elapsed(Start)+"--En- S: (%d,%v) %v",count,err,ress) }else{ fmt.Printf(Elapsed(Start)+"--In- S: %v",ress) } serv.Close() //conn.Close() var stat string var rcode int fmt.Sscanf(ress,"%d %s",&rcode,&stat) //fmt.Printf("--D-- Client: %v (%v)",rcode,stat) return rcode,ress } // Remote Shell // gcp file [...] { [host]:[port:][dir] | dir } // -p | -no-p func (gsh*GshContext)FileCopy(argv[]string){ var host = "" var port = "" var upload = false var download = false var xargv = []string{"rex-gcp"} var srcv = []string{} var dstv = []string{} argv = argv[1:] for _,v := range argv { /* if v[0] == '-' { // might be a pseudo file (generated date) continue } */ obj := strings.Split(v,":") //fmt.Printf("%d %v %v\n",len(obj),v,obj) if 1 < len(obj) { host = obj[0] file := "" if 0 < len(host) { gsh.LastServer.host = host }else{ host = gsh.LastServer.host port = gsh.LastServer.port } if 2 < len(obj) { port = obj[1] if 0 < len(port) { gsh.LastServer.port = port }else{ port = gsh.LastServer.port } file = obj[2] }else{ file = obj[1] } if len(srcv) == 0 { download = true srcv = append(srcv,file) continue } upload = true dstv = append(dstv,file) continue } /* idx := strings.Index(v,":") if 0 <= idx { remote = v[0:idx] if len(srcv) == 0 { download = true srcv = append(srcv,v[idx+1:]) continue } upload = true dstv = append(dstv,v[idx+1:]) continue } */ if download { dstv = append(dstv,v) }else{ srcv = append(srcv,v) } } hostport := "@" + host + ":" + port if upload { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"PUT") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v // %v\n",hostport,dstv,srcv,xargv) fmt.Printf("--I-- FileCopy PUT gsh://%s/%v < %v\n",hostport,dstv,srcv) gsh.RexecClient(xargv) }else if download { if host != "" { xargv = append(xargv,hostport) } xargv = append(xargv,"GET") xargv = append(xargv,srcv[0:]...) xargv = append(xargv,dstv[0:]...) //fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v // %v\n",hostport,srcv,dstv,xargv) fmt.Printf("--I-- FileCopy GET gsh://%v/%v > %v\n",hostport,srcv,dstv) gsh.RexecClient(xargv) }else{ } } // target func (gsh*GshContext)Trelpath(rloc string)(string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(rloc) twd, _ := os.Getwd() os.Chdir(cwd) tpath := twd + "/" + rloc return tpath } // join to rmote GShell - [user@]host[:port] or cd host:[port]:path func (gsh*GshContext)Rjoin(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- current server = %v\n",gsh.RSERV) return } serv := argv[1] servv := strings.Split(serv,":") if 1 <= len(servv) { if servv[0] == "lo" { servv[0] = "localhost" } } switch len(servv) { case 1: //if strings.Index(serv,":") < 0 { serv = servv[0] + ":" + fmt.Sprintf("%d",GSH_PORT) //} case 2: // host:port serv = strings.Join(servv,":") } xargv := []string{"rex-join","@"+serv,"HELO"} rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Joined (%v) %v\n",rcode,stat) gsh.RSERV = serv }else{ fmt.Printf("--I-- NG, could not joined (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rexec(argv[]string){ if len(argv) <= 1 { fmt.Printf("--I-- rexec command [ | {file || {command} ]\n",gsh.RSERV) return } /* nargv := gshScanArg(strings.Join(argv," "),0) fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) if nargv[1][0] != '{' { nargv[1] = "{" + nargv[1] + "}" fmt.Printf("--D-- nargc=%d [%v]\n",len(nargv),nargv) } argv = nargv */ nargv := []string{} nargv = append(nargv,"{"+strings.Join(argv[1:]," ")+"}") fmt.Printf("--D-- nargc=%d %v\n",len(nargv),nargv) argv = nargv xargv := []string{"rex-exec","@"+gsh.RSERV,"GET"} xargv = append(xargv,argv...) xargv = append(xargv,"/dev/tty") rcode,stat := gsh.RexecClient(xargv) if (rcode / 100) == 2 { fmt.Printf("--I-- OK Rexec (%v) %v\n",rcode,stat) }else{ fmt.Printf("--I-- NG Rexec (%v) %v\n",rcode,stat) } } func (gsh*GshContext)Rchdir(argv[]string){ if len(argv) <= 1 { return } cwd, _ := os.Getwd() os.Chdir(gsh.RWD) os.Chdir(argv[1]) twd, _ := os.Getwd() gsh.RWD = twd fmt.Printf("--I-- JWD=%v\n",twd) os.Chdir(cwd) } func (gsh*GshContext)Rpwd(argv[]string){ fmt.Printf("%v\n",gsh.RWD) } func (gsh*GshContext)Rls(argv[]string){ cwd, _ := os.Getwd() os.Chdir(gsh.RWD) argv[0] = "-ls" gsh.xFind(argv) os.Chdir(cwd) } func (gsh*GshContext)Rput(argv[]string){ var local string = "" var remote string = "" if 1 < len(argv) { local = argv[1] remote = local // base name } if 2 < len(argv) { remote = argv[2] } fmt.Printf("--I-- jput from=%v to=%v\n",local,gsh.Trelpath(remote)) } func (gsh*GshContext)Rget(argv[]string){ var remote string = "" var local string = "" if 1 < len(argv) { remote = argv[1] local = remote // base name } if 2 < len(argv) { local = argv[2] } fmt.Printf("--I-- jget from=%v to=%v\n",gsh.Trelpath(remote),local) } // network // -s, -si, -so // bi-directional, source, sync (maybe socket) func (gshCtx*GshContext)sconnect(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -s [host]:[port[.udp]]\n") return } remote := argv[1] if remote == ":" { remote = "0.0.0.0:9999" } if inTCP { // TCP dport, err := net.ResolveTCPAddr("tcp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } conn, err := net.DialTCP("tcp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() fmt.Printf("Socket: connected to %s, socket[%d]\n",remote,fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() }else{ //dport, err := net.ResolveUDPAddr("udp4",remote); dport, err := net.ResolveUDPAddr("udp",remote); if err != nil { fmt.Printf("Address error: %s (%s)\n",remote,err) return } //conn, err := net.DialUDP("udp4",nil,dport) conn, err := net.DialUDP("udp",nil,dport) if err != nil { fmt.Printf("Connection error: %s (%s)\n",remote,err) return } file, _ := conn.File(); fd := file.Fd() ar := conn.RemoteAddr() //al := conn.LocalAddr() fmt.Printf("Socket: connected to %s [%s], socket[%d]\n", remote,ar.String(),fd) savfd := gshPA.Files[1] gshPA.Files[1] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[1] = savfd file.Close() conn.Close() } } func (gshCtx*GshContext)saccept(inTCP bool, argv []string) { gshPA := gshCtx.gshPA if len(argv) < 2 { fmt.Printf("Usage: -ac [host]:[port[.udp]]\n") return } local := argv[1] if local == ":" { local = "0.0.0.0:9999" } if inTCP { // TCP port, err := net.ResolveTCPAddr("tcp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } //fmt.Printf("Listen at %s...\n",local); sconn, err := net.ListenTCP("tcp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } //fmt.Printf("Accepting at %s...\n",local); aconn, err := sconn.AcceptTCP() if err != nil { fmt.Printf("Accept error: %s (%s)\n",local,err) return } file, _ := aconn.File() fd := file.Fd() fmt.Printf("Accepted TCP at %s [%d]\n",local,fd) savfd := gshPA.Files[0] gshPA.Files[0] = fd; gshCtx.gshellv(argv[2:]) gshPA.Files[0] = savfd sconn.Close(); aconn.Close(); file.Close(); }else{ //port, err := net.ResolveUDPAddr("udp4",local); port, err := net.ResolveUDPAddr("udp",local); if err != nil { fmt.Printf("Address error: %s (%s)\n",local,err) return } fmt.Printf("Listen UDP at %s...\n",local); //uconn, err := net.ListenUDP("udp4", port) uconn, err := net.ListenUDP("udp", port) if err != nil { fmt.Printf("Listen error: %s (%s)\n",local,err) return } file, _ := uconn.File() fd := file.Fd() ar := uconn.RemoteAddr() remote := "" if ar != nil { remote = ar.String() } if remote == "" { remote = "?" } // not yet received //fmt.Printf("Accepted at %s [%d] <- %s\n",local,fd,"") savfd := gshPA.Files[0] gshPA.Files[0] = fd; savenv := gshPA.Env gshPA.Env = append(savenv, "REMOTE_HOST="+remote) gshCtx.gshellv(argv[2:]) gshPA.Env = savenv gshPA.Files[0] = savfd uconn.Close(); file.Close(); } } // empty line command func (gshCtx*GshContext)xPwd(argv[]string){ // execute context command, pwd + date // context notation, representation scheme, to be resumed at re-login cwd, _ := os.Getwd() switch { case isin("-a",argv): gshCtx.ShowChdirHistory(argv) case isin("-ls",argv): showFileInfo(cwd,argv) default: fmt.Printf("%s\n",cwd) case isin("-v",argv): // obsolete emtpy command t := time.Now() date := t.Format(time.UnixDate) exe, _ := os.Executable() host, _ := os.Hostname() fmt.Printf("{PWD=\"%s\"",cwd) fmt.Printf(" HOST=\"%s\"",host) fmt.Printf(" DATE=\"%s\"",date) fmt.Printf(" TIME=\"%s\"",t.String()) fmt.Printf(" PID=\"%d\"",os.Getpid()) fmt.Printf(" EXE=\"%s\"",exe) fmt.Printf("}\n") } } // History // these should be browsed and edited by HTTP browser // show the time of command with -t and direcotry with -ls // openfile-history, sort by -a -m -c // sort by elapsed time by -t -s // search by "more" like interface // edit history // sort history, and wc or uniq // CPU and other resource consumptions // limit showing range (by time or so) // export / import history func (gshCtx *GshContext)xHistory(argv []string){ atWorkDirX := -1 if 1 < len(argv) && strBegins(argv[1],"@") { atWorkDirX,_ = strconv.Atoi(argv[1][1:]) } //fmt.Printf("--D-- showHistory(%v)\n",argv) for i, v := range gshCtx.CommandHistory { // exclude commands not to be listed by default // internal commands may be suppressed by default if v.CmdLine == "" && !isin("-a",argv) { continue; } if 0 <= atWorkDirX { if v.WorkDirX != atWorkDirX { continue } } if !isin("-n",argv){ // like "fc" fmt.Printf("!%-2d ",i) } if isin("-v",argv){ fmt.Println(v) // should be with it date }else{ if isin("-l",argv) || isin("-l0",argv) { elps := v.EndAt.Sub(v.StartAt); start := v.StartAt.Format(time.Stamp) fmt.Printf("@%d ",v.WorkDirX) fmt.Printf("[%v] %11v/t ",start,elps) } if isin("-l",argv) && !isin("-l0",argv){ fmt.Printf("%v",Rusagef("%t %u\t// %s",argv,v.Rusagev)) } if isin("-at",argv) { // isin("-ls",argv){ dhi := v.WorkDirX // workdir history index fmt.Printf("@%d %s\t",dhi,v.WorkDir) // show the FileInfo of the output command?? } fmt.Printf("%s",v.CmdLine) fmt.Printf("\n") } } } // !n - history index func searchHistory(gshCtx GshContext, gline string) (string, bool, bool){ if gline[0] == '!' { hix, err := strconv.Atoi(gline[1:]) if err != nil { fmt.Printf("--E-- (%s : range)\n",hix) return "", false, true } if hix < 0 || len(gshCtx.CommandHistory) <= hix { fmt.Printf("--E-- (%d : out of range)\n",hix) return "", false, true } return gshCtx.CommandHistory[hix].CmdLine, false, false } // search //for i, v := range gshCtx.CommandHistory { //} return gline, false, false } func (gsh*GshContext)cmdStringInHistory(hix int)(cmd string, ok bool){ if 0 <= hix && hix < len(gsh.CommandHistory) { return gsh.CommandHistory[hix].CmdLine,true } return "",false } // temporary adding to PATH environment // cd name -lib for LD_LIBRARY_PATH // chdir with directory history (date + full-path) // -s for sort option (by visit date or so) func (gsh*GshContext)ShowChdirHistory1(i int,v GChdirHistory, argv []string){ fmt.Printf("!%-2d ",v.CmdIndex) // the first command at this WorkDir fmt.Printf("@%d ",i) fmt.Printf("[%v] ",v.MovedAt.Format(time.Stamp)) showFileInfo(v.Dir,argv) } func (gsh*GshContext)ShowChdirHistory(argv []string){ for i, v := range gsh.ChdirHistory { gsh.ShowChdirHistory1(i,v,argv) } } func skipOpts(argv[]string)(int){ for i,v := range argv { if strBegins(v,"-") { }else{ return i } } return -1 } func (gshCtx*GshContext)xChdir(argv []string){ cdhist := gshCtx.ChdirHistory if isin("?",argv ) || isin("-t",argv) || isin("-a",argv) { gshCtx.ShowChdirHistory(argv) return } pwd, _ := os.Getwd() dir := "" if len(argv) <= 1 { dir = toFullpath("~") }else{ i := skipOpts(argv[1:]) if i < 0 { dir = toFullpath("~") }else{ dir = argv[1+i] } } if strBegins(dir,"@") { if dir == "@0" { // obsolete dir = gshCtx.StartDir }else if dir == "@!" { index := len(cdhist) - 1 if 0 < index { index -= 1 } dir = cdhist[index].Dir }else{ index, err := strconv.Atoi(dir[1:]) if err != nil { fmt.Printf("--E-- xChdir(%v)\n",err) dir = "?" }else if len(gshCtx.ChdirHistory) <= index { fmt.Printf("--E-- xChdir(history range error)\n") dir = "?" }else{ dir = cdhist[index].Dir } } } if dir != "?" { err := os.Chdir(dir) if err != nil { fmt.Printf("--E-- xChdir(%s)(%v)\n",argv[1],err) }else{ cwd, _ := os.Getwd() if cwd != pwd { hist1 := GChdirHistory { } hist1.Dir = cwd hist1.MovedAt = time.Now() hist1.CmdIndex = len(gshCtx.CommandHistory)+1 gshCtx.ChdirHistory = append(cdhist,hist1) if !isin("-s",argv){ //cwd, _ := os.Getwd() //fmt.Printf("%s\n",cwd) ix := len(gshCtx.ChdirHistory)-1 gshCtx.ShowChdirHistory1(ix,hist1,argv) } } } } if isin("-ls",argv){ cwd, _ := os.Getwd() showFileInfo(cwd,argv); } } func TimeValSub(tv1 *syscall.Timeval, tv2 *syscall.Timeval){ *tv1 = syscall.NsecToTimeval(tv1.Nano() - tv2.Nano()) } func RusageSubv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValSub(&ru1[0].Utime,&ru2[0].Utime) TimeValSub(&ru1[0].Stime,&ru2[0].Stime) TimeValSub(&ru1[1].Utime,&ru2[1].Utime) TimeValSub(&ru1[1].Stime,&ru2[1].Stime) return ru1 } func TimeValAdd(tv1 syscall.Timeval, tv2 syscall.Timeval)(syscall.Timeval){ tvs := syscall.NsecToTimeval(tv1.Nano() + tv2.Nano()) return tvs } /* func RusageAddv(ru1, ru2 [2]syscall.Rusage)([2]syscall.Rusage){ TimeValAdd(ru1[0].Utime,ru2[0].Utime) TimeValAdd(ru1[0].Stime,ru2[0].Stime) TimeValAdd(ru1[1].Utime,ru2[1].Utime) TimeValAdd(ru1[1].Stime,ru2[1].Stime) return ru1 } */ // Resource Usage func sRusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ // ru[0] self , ru[1] children ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) uu := (ut.Sec*1000000 + int64(ut.Usec)) * 1000 su := (st.Sec*1000000 + int64(st.Usec)) * 1000 tu := uu + su ret := fmt.Sprintf("%v/sum",abbtime(tu)) ret += fmt.Sprintf(", %v/usr",abbtime(uu)) ret += fmt.Sprintf(", %v/sys",abbtime(su)) return ret } func Rusagef(fmtspec string, argv []string, ru [2]syscall.Rusage)(string){ ut := TimeValAdd(ru[0].Utime,ru[1].Utime) st := TimeValAdd(ru[0].Stime,ru[1].Stime) fmt.Printf("%d.%06ds/u ",ut.Sec,ut.Usec) //ru[1].Utime.Sec,ru[1].Utime.Usec) fmt.Printf("%d.%06ds/s ",st.Sec,st.Usec) //ru[1].Stime.Sec,ru[1].Stime.Usec) return "" } func Getrusagev()([2]syscall.Rusage){ var ruv = [2]syscall.Rusage{} syscall.Getrusage(syscall.RUSAGE_SELF,&ruv[0]) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&ruv[1]) return ruv } func showRusage(what string,argv []string, ru *syscall.Rusage){ fmt.Printf("%s: ",what); fmt.Printf("Usr=%d.%06ds",ru.Utime.Sec,ru.Utime.Usec) fmt.Printf(" Sys=%d.%06ds",ru.Stime.Sec,ru.Stime.Usec) fmt.Printf(" Rss=%vB",ru.Maxrss) if isin("-l",argv) { fmt.Printf(" MinFlt=%v",ru.Minflt) fmt.Printf(" MajFlt=%v",ru.Majflt) fmt.Printf(" IxRSS=%vB",ru.Ixrss) fmt.Printf(" IdRSS=%vB",ru.Idrss) fmt.Printf(" Nswap=%vB",ru.Nswap) fmt.Printf(" Read=%v",ru.Inblock) fmt.Printf(" Write=%v",ru.Oublock) } fmt.Printf(" Snd=%v",ru.Msgsnd) fmt.Printf(" Rcv=%v",ru.Msgrcv) //if isin("-l",argv) { fmt.Printf(" Sig=%v",ru.Nsignals) //} fmt.Printf("\n"); } func (gshCtx *GshContext)xTime(argv[]string)(bool){ if 2 <= len(argv){ gshCtx.LastRusage = syscall.Rusage{} rusagev1 := Getrusagev() fin := gshCtx.gshellv(argv[1:]) rusagev2 := Getrusagev() showRusage(argv[1],argv,&gshCtx.LastRusage) rusagev := RusageSubv(rusagev2,rusagev1) showRusage("self",argv,&rusagev[0]) showRusage("chld",argv,&rusagev[1]) return fin }else{ rusage:= syscall.Rusage {} syscall.Getrusage(syscall.RUSAGE_SELF,&rusage) showRusage("self",argv, &rusage) syscall.Getrusage(syscall.RUSAGE_CHILDREN,&rusage) showRusage("chld",argv, &rusage) return false } } func (gshCtx *GshContext)xJobs(argv[]string){ fmt.Printf("%d Jobs\n",len(gshCtx.BackGroundJobs)) for ji, pid := range gshCtx.BackGroundJobs { //wstat := syscall.WaitStatus {0} rusage := syscall.Rusage {} //wpid, err := syscall.Wait4(pid,&wstat,syscall.WNOHANG,&rusage); wpid, err := syscall.Wait4(pid,nil,syscall.WNOHANG,&rusage); if err != nil { fmt.Printf("--E-- %%%d [%d] (%v)\n",ji,pid,err) }else{ fmt.Printf("%%%d[%d](%d)\n",ji,pid,wpid) showRusage("chld",argv,&rusage) } } } func (gsh*GshContext)inBackground(argv[]string)(bool){ if gsh.CmdTrace { fmt.Printf("--I-- inBackground(%v)\n",argv) } gsh.BackGround = true // set background option xfin := false xfin = gsh.gshellv(argv) gsh.BackGround = false return xfin } // -o file without command means just opening it and refer by #N // should be listed by "files" comnmand func (gshCtx*GshContext)xOpen(argv[]string){ var pv = []int{-1,-1} err := syscall.Pipe(pv) fmt.Printf("--I-- pipe()=[#%d,#%d](%v)\n",pv[0],pv[1],err) } func (gshCtx*GshContext)fromPipe(argv[]string){ } func (gshCtx*GshContext)xClose(argv[]string){ } // redirect func (gshCtx*GshContext)redirect(argv[]string)(bool){ if len(argv) < 2 { return false } cmd := argv[0] fname := argv[1] var file *os.File = nil fdix := 0 mode := os.O_RDONLY switch { case cmd == "-i" || cmd == "<": fdix = 0 mode = os.O_RDONLY case cmd == "-o" || cmd == ">": fdix = 1 mode = os.O_RDWR | os.O_CREATE case cmd == "-a" || cmd == ">>": fdix = 1 mode = os.O_RDWR | os.O_CREATE | os.O_APPEND } if fname[0] == '#' { fd, err := strconv.Atoi(fname[1:]) if err != nil { fmt.Printf("--E-- (%v)\n",err) return false } file = os.NewFile(uintptr(fd),"MaybePipe") }else{ xfile, err := os.OpenFile(argv[1], mode, 0600) if err != nil { fmt.Printf("--E-- (%s)\n",err) return false } file = xfile } gshPA := gshCtx.gshPA savfd := gshPA.Files[fdix] gshPA.Files[fdix] = file.Fd() fmt.Printf("--I-- Opened [%d] %s\n",file.Fd(),argv[1]) gshCtx.gshellv(argv[2:]) gshPA.Files[fdix] = savfd return false } //fmt.Fprintf(res, "GShell Status: %q", html.EscapeString(req.URL.Path)) func httpHandler(res http.ResponseWriter, req *http.Request){ path := req.URL.Path fmt.Printf("--I-- Got HTTP Request(%s)\n",path) { gshCtxBuf, _ := setupGshContext() gshCtx := &gshCtxBuf fmt.Printf("--I-- %s\n",path[1:]) gshCtx.tgshelll(path[1:]) } fmt.Fprintf(res, "Hello(^-^)//\n%s\n",path) } func (gshCtx *GshContext) httpServer(argv []string){ http.HandleFunc("/", httpHandler) accport := "localhost:9999" fmt.Printf("--I-- HTTP Server Start at [%s]\n",accport) http.ListenAndServe(accport,nil) } func (gshCtx *GshContext)xGo(argv[]string){ go gshCtx.gshellv(argv[1:]); } func (gshCtx *GshContext) xPs(argv[]string)(){ } // Plugin // plugin [-ls [names]] to list plugins // Reference: plugin source code func (gshCtx *GshContext) whichPlugin(name string,argv[]string)(pi *PluginInfo){ pi = nil for _,p := range gshCtx.PluginFuncs { if p.Name == name && pi == nil { pi = &p } if !isin("-s",argv){ //fmt.Printf("%v %v ",i,p) if isin("-ls",argv){ showFileInfo(p.Path,argv) }else{ fmt.Printf("%s\n",p.Name) } } } return pi } func (gshCtx *GshContext) xPlugin(argv[]string) (error) { if len(argv) == 0 || argv[0] == "-ls" { gshCtx.whichPlugin("",argv) return nil } name := argv[0] Pin := gshCtx.whichPlugin(name,[]string{"-s"}) if Pin != nil { os.Args = argv // should be recovered? Pin.Addr.(func())() return nil } sofile := toFullpath(argv[0] + ".so") // or find it by which($PATH) p, err := plugin.Open(sofile) if err != nil { fmt.Printf("--E-- plugin.Open(%s)(%v)\n",sofile,err) return err } fname := "Main" f, err := p.Lookup(fname) if( err != nil ){ fmt.Printf("--E-- plugin.Lookup(%s)(%v)\n",fname,err) return err } pin := PluginInfo {p,f,name,sofile} gshCtx.PluginFuncs = append(gshCtx.PluginFuncs,pin) fmt.Printf("--I-- added (%d)\n",len(gshCtx.PluginFuncs)) //fmt.Printf("--I-- first call(%s:%s)%v\n",sofile,fname,argv) os.Args = argv f.(func())() return err } func (gshCtx*GshContext)Args(argv[]string){ for i,v := range os.Args { fmt.Printf("[%v] %v\n",i,v) } } func (gshCtx *GshContext) showVersion(argv[]string){ if isin("-l",argv) { fmt.Printf("%v/%v (%v)",NAME,VERSION,DATE); }else{ fmt.Printf("%v",VERSION); } if isin("-a",argv) { fmt.Printf(" %s",AUTHOR) } if !isin("-n",argv) { fmt.Printf("\n") } } // Scanf // string decomposer // scanf [format] [input] func scanv(sstr string)(strv[]string){ strv = strings.Split(sstr," ") return strv } func scanUntil(src,end string)(rstr string,leng int){ idx := strings.Index(src,end) if 0 <= idx { rstr = src[0:idx] return rstr,idx+len(end) } return src,0 } // -bn -- display base-name part only // can be in some %fmt, for sed rewriting func (gsh*GshContext)printVal(fmts string, vstr string, optv[]string){ //vint,err := strconv.Atoi(vstr) var ival int64 = 0 n := 0 err := error(nil) if strBegins(vstr,"_") { vx,_ := strconv.Atoi(vstr[1:]) if vx < len(gsh.iValues) { vstr = gsh.iValues[vx] }else{ } } // should use Eval() if strBegins(vstr,"0x") { n,err = fmt.Sscanf(vstr[2:],"%x",&ival) }else{ n,err = fmt.Sscanf(vstr,"%d",&ival) //fmt.Printf("--D-- n=%d err=(%v) {%s}=%v\n",n,err,vstr, ival) } if n == 1 && err == nil { //fmt.Printf("--D-- formatn(%v) ival(%v)\n",fmts,ival) fmt.Printf("%"+fmts,ival) }else{ if isin("-bn",optv){ fmt.Printf("%"+fmts,filepath.Base(vstr)) }else{ fmt.Printf("%"+fmts,vstr) } } } func (gsh*GshContext)printfv(fmts,div string,argv[]string,optv[]string,list[]string){ //fmt.Printf("{%d}",len(list)) //curfmt := "v" outlen := 0 curfmt := gsh.iFormat if 0 < len(fmts) { for xi := 0; xi < len(fmts); xi++ { fch := fmts[xi] if fch == '%' { if xi+1 < len(fmts) { curfmt = string(fmts[xi+1]) gsh.iFormat = curfmt xi += 1 if xi+1 < len(fmts) && fmts[xi+1] == '(' { vals,leng := scanUntil(fmts[xi+2:],")") //fmt.Printf("--D-- show fmt(%v) val(%v) next(%v)\n",curfmt,vals,leng) gsh.printVal(curfmt,vals,optv) xi += 2+leng-1 outlen += 1 } continue } } if fch == '_' { hi,leng := scanInt(fmts[xi+1:]) if 0 < leng { if hi < len(gsh.iValues) { gsh.printVal(curfmt,gsh.iValues[hi],optv) outlen += 1 // should be the real length }else{ fmt.Printf("((out-range))") } xi += leng continue; } } fmt.Printf("%c",fch) outlen += 1 } }else{ //fmt.Printf("--D-- print {%s}\n") for i,v := range list { if 0 < i { fmt.Printf(div) } gsh.printVal(curfmt,v,optv) outlen += 1 } } if 0 < outlen { fmt.Printf("\n") } } func (gsh*GshContext)Scanv(argv[]string){ //fmt.Printf("--D-- Scanv(%v)\n",argv) if len(argv) == 1 { return } argv = argv[1:] fmts := "" if strBegins(argv[0],"-F") { fmts = argv[0] gsh.iDelimiter = fmts argv = argv[1:] } input := strings.Join(argv," ") if fmts == "" { // simple decomposition v := scanv(input) gsh.iValues = v //fmt.Printf("%v\n",strings.Join(v,",")) }else{ v := make([]string,8) n,err := fmt.Sscanf(input,fmts,&v[0],&v[1],&v[2],&v[3]) fmt.Printf("--D-- Scanf ->(%v) n=%d err=(%v)\n",v,n,err) gsh.iValues = v } } func (gsh*GshContext)Printv(argv[]string){ if false { //@@U fmt.Printf("%v\n",strings.Join(argv[1:]," ")) return } //fmt.Printf("--D-- Printv(%v)\n",argv) //fmt.Printf("%v\n",strings.Join(gsh.iValues,",")) div := gsh.iDelimiter fmts := "" argv = argv[1:] if 0 < len(argv) { if strBegins(argv[0],"-F") { div = argv[0][2:] argv = argv[1:] } } optv := []string{} for _,v := range argv { if strBegins(v,"-"){ optv = append(optv,v) argv = argv[1:] }else{ break; } } if 0 < len(argv) { fmts = strings.Join(argv," ") } gsh.printfv(fmts,div,argv,optv,gsh.iValues) } func (gsh*GshContext)Basename(argv[]string){ for i,v := range gsh.iValues { gsh.iValues[i] = filepath.Base(v) } } func (gsh*GshContext)Sortv(argv[]string){ sv := gsh.iValues sort.Slice(sv , func(i,j int) bool { return sv[i] < sv[j] }) } func (gsh*GshContext)Shiftv(argv[]string){ vi := len(gsh.iValues) if 0 < vi { if isin("-r",argv) { top := gsh.iValues[0] gsh.iValues = append(gsh.iValues[1:],top) }else{ gsh.iValues = gsh.iValues[1:] } } } func (gsh*GshContext)Enq(argv[]string){ } func (gsh*GshContext)Deq(argv[]string){ } func (gsh*GshContext)Push(argv[]string){ gsh.iValStack = append(gsh.iValStack,argv[1:]) fmt.Printf("depth=%d\n",len(gsh.iValStack)) } func (gsh*GshContext)Dump(argv[]string){ for i,v := range gsh.iValStack { fmt.Printf("%d %v\n",i,v) } } func (gsh*GshContext)Pop(argv[]string){ depth := len(gsh.iValStack) if 0 < depth { v := gsh.iValStack[depth-1] if isin("-cat",argv){ gsh.iValues = append(gsh.iValues,v...) }else{ gsh.iValues = v } gsh.iValStack = gsh.iValStack[0:depth-1] fmt.Printf("depth=%d %s\n",len(gsh.iValStack),gsh.iValues) }else{ fmt.Printf("depth=%d\n",depth) } } // Command Interpreter func (gshCtx*GshContext)gshellv(argv []string) (fin bool) { fin = false if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv((%d))\n",len(argv)) } if len(argv) <= 0 { return false } xargv := []string{} for ai := 0; ai < len(argv); ai++ { xargv = append(xargv,strsubst(gshCtx,argv[ai],false)) } argv = xargv if false { for ai := 0; ai < len(argv); ai++ { fmt.Printf("[%d] %s [%d]%T\n", ai,argv[ai],len(argv[ai]),argv[ai]) } } cmd := argv[0] if gshCtx.CmdTrace { fmt.Fprintf(os.Stderr,"--I-- gshellv(%d)%v\n",len(argv),argv) } switch { // https://tour.golang.org/flowcontrol/11 case cmd == "": gshCtx.xPwd([]string{}); // emtpy command case cmd == "-x": gshCtx.CmdTrace = ! gshCtx.CmdTrace case cmd == "-xt": gshCtx.CmdTime = ! gshCtx.CmdTime case cmd == "-ot": gshCtx.sconnect(true, argv) case cmd == "-ou": gshCtx.sconnect(false, argv) case cmd == "-it": gshCtx.saccept(true , argv) case cmd == "-iu": gshCtx.saccept(false, argv) case cmd == "-i" || cmd == "<" || cmd == "-o" || cmd == ">" || cmd == "-a" || cmd == ">>" || cmd == "-s" || cmd == "><": gshCtx.redirect(argv) case cmd == "|": gshCtx.fromPipe(argv) case cmd == "args": gshCtx.Args(argv) case cmd == "bg" || cmd == "-bg": rfin := gshCtx.inBackground(argv[1:]) return rfin case cmd == "-bn": gshCtx.Basename(argv) case cmd == "call": _,_ = gshCtx.excommand(false,argv[1:]) case cmd == "cd" || cmd == "chdir": gshCtx.xChdir(argv); case cmd == "-cksum": gshCtx.xFind(argv) case cmd == "-sum": gshCtx.xFind(argv) case cmd == "-sumtest": str := "" if 1 < len(argv) { str = argv[1] } crc := strCRC32(str,uint64(len(str))) fprintf(stderr,"%v %v\n",crc,len(str)) case cmd == "close": gshCtx.xClose(argv) case cmd == "gcp": gshCtx.FileCopy(argv) case cmd == "dec" || cmd == "decode": gshCtx.Dec(argv) case cmd == "#define": case cmd == "dic" || cmd == "d": xDic(argv) case cmd == "dump": gshCtx.Dump(argv) case cmd == "echo" || cmd == "e": echo(argv,true) case cmd == "enc" || cmd == "encode": gshCtx.Enc(argv) case cmd == "env": env(argv) case cmd == "eval": xEval(argv[1:],true) case cmd == "ev" || cmd == "events": dumpEvents(argv) case cmd == "exec": _,_ = gshCtx.excommand(true,argv[1:]) // should not return here case cmd == "exit" || cmd == "quit": // write Result code EXIT to 3> return true case cmd == "fdls": // dump the attributes of fds (of other process) case cmd == "-find" || cmd == "fin" || cmd == "ufind" || cmd == "uf": gshCtx.xFind(argv[1:]) case cmd == "fu": gshCtx.xFind(argv[1:]) case cmd == "fork": // mainly for a server case cmd == "-gen": gshCtx.gen(argv) case cmd == "-go": gshCtx.xGo(argv) case cmd == "-grep": gshCtx.xFind(argv) case cmd == "gdeq": gshCtx.Deq(argv) case cmd == "genq": gshCtx.Enq(argv) case cmd == "gpop": gshCtx.Pop(argv) case cmd == "gpush": gshCtx.Push(argv) case cmd == "history" || cmd == "hi": // hi should be alias gshCtx.xHistory(argv) case cmd == "jobs": gshCtx.xJobs(argv) case cmd == "lnsp" || cmd == "nlsp": gshCtx.SplitLine(argv) case cmd == "-ls": gshCtx.xFind(argv) case cmd == "nop": // do nothing case cmd == "pipe": gshCtx.xOpen(argv) case cmd == "plug" || cmd == "plugin" || cmd == "pin": gshCtx.xPlugin(argv[1:]) case cmd == "print" || cmd == "-pr": // output internal slice // also sprintf should be gshCtx.Printv(argv) case cmd == "ps": gshCtx.xPs(argv) case cmd == "pstitle": // to be gsh.title case cmd == "rexecd" || cmd == "rexd": gshCtx.RexecServer(argv) case cmd == "rexec" || cmd == "rex": gshCtx.RexecClient(argv) case cmd == "repeat" || cmd == "rep": // repeat cond command gshCtx.repeat(argv) case cmd == "replay": gshCtx.xReplay(argv) case cmd == "scan": // scan input (or so in fscanf) to internal slice (like Files or map) gshCtx.Scanv(argv) case cmd == "set": // set name ... case cmd == "serv": gshCtx.httpServer(argv) case cmd == "shift": gshCtx.Shiftv(argv) case cmd == "sleep": gshCtx.sleep(argv) case cmd == "-sort": gshCtx.Sortv(argv) case cmd == "j" || cmd == "join": gshCtx.Rjoin(argv) case cmd == "a" || cmd == "alpa": gshCtx.Rexec(argv) case cmd == "jcd" || cmd == "jchdir": gshCtx.Rchdir(argv) case cmd == "jget": gshCtx.Rget(argv) case cmd == "jls": gshCtx.Rls(argv) case cmd == "jput": gshCtx.Rput(argv) case cmd == "jpwd": gshCtx.Rpwd(argv) case cmd == "time": fin = gshCtx.xTime(argv) case cmd == "ungets": if 1 < len(argv) { ungets(argv[1]+"\n") }else{ } case cmd == "pwd": gshCtx.xPwd(argv); case cmd == "ver" || cmd == "-ver" || cmd == "version": gshCtx.showVersion(argv) case cmd == "where": // data file or so? case cmd == "which": which("PATH",argv); case cmd == "gj" && 1 < len(argv) && argv[1] == "listen": go gj_server(argv[1:]); case cmd == "gj" && 1 < len(argv) && argv[1] == "join": go gj_client(argv[1:]); case cmd == "gj": jsend(argv); case cmd == "jsend": jsend(argv); default: if gshCtx.whichPlugin(cmd,[]string{"-s"}) != nil { gshCtx.xPlugin(argv) }else{ notfound,_ := gshCtx.excommand(false,argv) if notfound { fmt.Printf("--E-- command not found (%v)\n",cmd) } } } return fin } func (gsh*GshContext)gshelll(gline string) (rfin bool) { argv := strings.Split(string(gline)," ") fin := gsh.gshellv(argv) return fin } func (gsh*GshContext)tgshelll(gline string)(xfin bool){ start := time.Now() fin := gsh.gshelll(gline) end := time.Now() elps := end.Sub(start); if gsh.CmdTime { fmt.Printf("--T-- " + time.Now().Format(time.Stamp) + "(%d.%09ds)\n", elps/1000000000,elps%1000000000) } return fin } func Ttyid() (int) { fi, err := os.Stdin.Stat() if err != nil { return 0; } //fmt.Printf("Stdin: %v Dev=%d\n", // fi.Mode(),fi.Mode()&os.ModeDevice) if (fi.Mode() & os.ModeDevice) != 0 { stat := syscall.Stat_t{}; err := syscall.Fstat(0,&stat) if err != nil { //fmt.Printf("--I-- Stdin: (%v)\n",err) }else{ //fmt.Printf("--I-- Stdin: rdev=%d %d\n", // stat.Rdev&0xFF,stat.Rdev); //fmt.Printf("--I-- Stdin: tty%d\n",stat.Rdev&0xFF); return int(stat.Rdev & 0xFF) } } return 0 } func (gshCtx *GshContext) ttyfile() string { //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) ttyfile := gshCtx.GshHomeDir + "/" + "gsh-tty" + fmt.Sprintf("%02d",gshCtx.TerminalId) //strconv.Itoa(gshCtx.TerminalId) //fmt.Printf("--I-- ttyfile=%s\n",ttyfile) return ttyfile } func (gshCtx *GshContext) ttyline()(*os.File){ file, err := os.OpenFile(gshCtx.ttyfile(),os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if err != nil { fmt.Printf("--F-- cannot open %s (%s)\n",gshCtx.ttyfile(),err) return file; } return file } func (gshCtx *GshContext)getline(hix int, skipping bool, prevline string) (string) { if( skipping ){ reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) }else if true { return xgetline(hix,prevline,gshCtx) } /* else if( with_exgetline && gshCtx.GetLine != "" ){ //var xhix int64 = int64(hix); // cast newenv := os.Environ() newenv = append(newenv, "GSH_LINENO="+strconv.FormatInt(int64(hix),10) ) tty := gshCtx.ttyline() tty.WriteString(prevline) Pa := os.ProcAttr { "", // start dir newenv, //os.Environ(), []*os.File{os.Stdin,os.Stdout,os.Stderr,tty}, nil, } //fmt.Printf("--I-- getline=%s // %s\n",gsh_getlinev[0],gshCtx.GetLine) proc, err := os.StartProcess(gsh_getlinev[0],[]string{"getline","getline"},&Pa) if err != nil { fmt.Printf("--F-- getline process error (%v)\n",err) // for ; ; { } return "exit (getline program failed)" } //stat, err := proc.Wait() proc.Wait() buff := make([]byte,LINESIZE) count, err := tty.Read(buff) //_, err = tty.Read(buff) //fmt.Printf("--D-- getline (%d)\n",count) if err != nil { if ! (count == 0) { // && err.String() == "EOF" ) { fmt.Printf("--E-- getline error (%s)\n",err) } }else{ //fmt.Printf("--I-- getline OK \"%s\"\n",buff) } tty.Close() gline := string(buff[0:count]) return gline }else */ { // if isatty { fmt.Printf("!%d",hix) fmt.Print(PROMPT) // } reader := bufio.NewReaderSize(os.Stdin,LINESIZE) line, _, _ := reader.ReadLine() return string(line) } } //== begin ======================================================= getline /* * getline.c * 2020-0819 extracted from dog.c * getline.go * 2020-0822 ported to Go */ /* package main // getline main import ( "fmt" // fmt "strings" // strings "os" // os "syscall" // syscall //"bytes" // os //"os/exec" // os ) */ // C language compatibility functions var errno = 0 var stdin *os.File = os.Stdin var stdout *os.File = os.Stdout var stderr *os.File = os.Stderr var EOF = -1 var NULL = 0 type FILE os.File type StrBuff []byte var NULL_FP *os.File = nil var NULLSP = 0 //var LINESIZE = 1024 func system(cmdstr string)(int){ PA := syscall.ProcAttr { "", // the starting directory os.Environ(), []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, } argv := strings.Split(cmdstr," ") pid,err := syscall.ForkExec(argv[0],argv,&PA) if( err != nil ){ fmt.Printf("--E-- syscall(%v) err(%v)\n",cmdstr,err) } syscall.Wait4(pid,nil,0,nil) /* argv := strings.Split(cmdstr," ") fmt.Fprintf(os.Stderr,"--I-- system(%v)\n",argv) //cmd := exec.Command(argv[0:]...) cmd := exec.Command(argv[0],argv[1],argv[2]) cmd.Stdin = strings.NewReader("output of system") var out bytes.Buffer cmd.Stdout = &out var serr bytes.Buffer cmd.Stderr = &serr err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr,"--E-- system(%v)err(%v)\n",argv,err) fmt.Printf("ERR:%s\n",serr.String()) }else{ fmt.Printf("%s",out.String()) } */ return 0 } func atoi(str string)(ret int){ ret,err := fmt.Sscanf(str,"%d",ret) if err == nil { return ret }else{ // should set errno return 0 } } func getenv(name string)(string){ val,got := os.LookupEnv(name) if got { return val }else{ return "?" } } func strcpy(dst StrBuff, src string){ var i int srcb := []byte(src) for i = 0; i < len(src) && srcb[i] != 0; i++ { dst[i] = srcb[i] } dst[i] = 0 } func xstrcpy(dst StrBuff, src StrBuff){ dst = src } func strcat(dst StrBuff, src StrBuff){ dst = append(dst,src...) } func strdup(str StrBuff)(string){ return string(str[0:strlen(str)]) } func sstrlen(str string)(int){ return len(str) } func strlen(str StrBuff)(int){ var i int for i = 0; i < len(str) && str[i] != 0; i++ { } return i } func sizeof(data StrBuff)(int){ return len(data) } func isatty(fd int)(ret int){ return 1 } func fopen(file string,mode string)(fp*os.File){ if mode == "r" { fp,err := os.Open(file) if( err != nil ){ fmt.Printf("--E-- fopen(%s,%s)=(%v)\n",file,mode,err) return NULL_FP; } return fp; }else{ fp,err := os.OpenFile(file,os.O_RDWR|os.O_CREATE|os.O_TRUNC,0600) if( err != nil ){ return NULL_FP; } return fp; } } func fclose(fp*os.File){ fp.Close() } func fflush(fp *os.File)(int){ return 0 } func fgetc(fp*os.File)(int){ var buf [1]byte _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func sfgets(str*string, size int, fp*os.File)(int){ buf := make(StrBuff,size) var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fgets(buf StrBuff, size int, fp*os.File)(int){ var ch int var i int for i = 0; i < len(buf)-1; i++ { ch = fgetc(fp) //fprintf(stderr,"--fgets %d/%d %X\n",i,len(buf),ch) if( ch == EOF ){ break; } buf[i] = byte(ch); if( ch == '\n' ){ break; } } buf[i] = 0 //fprintf(stderr,"--fgets %d/%d (%s)\n",i,len(buf),buf[0:i]) return i } func fputc(ch int , fp*os.File)(int){ var buf [1]byte buf[0] = byte(ch) fp.Write(buf[0:1]) return 0 } func fputs(buf StrBuff, fp*os.File)(int){ fp.Write(buf) return 0 } func xfputss(str string, fp*os.File)(int){ return fputs([]byte(str),fp) } func sscanf(str StrBuff,fmts string, params ...interface{})(int){ fmt.Sscanf(string(str[0:strlen(str)]),fmts,params...) return 0 } func fprintf(fp*os.File,fmts string, params ...interface{})(int){ fmt.Fprintf(fp,fmts,params...) return 0 } // Command Line IME //----------------------------------------------------------------------- MyIME var MyIMEVER = "MyIME/0.0.2"; type RomKana struct { dic string // dictionaly ID pat string // input pattern out string // output pattern hit int64 // count of hit and used } var dicents = 0 var romkana [1024]RomKana var Romkan []RomKana func isinDic(str string)(int){ for i,v := range Romkan { if v.pat == str { return i } } return -1 } const ( DIC_COM_LOAD = "im" DIC_COM_DUMP = "s" DIC_COM_LIST = "ls" DIC_COM_ENA = "en" DIC_COM_DIS = "di" ) func helpDic(argv []string){ out := stderr cmd := "" if 0 < len(argv) { cmd = argv[0] } fprintf(out,"--- %v Usage\n",cmd) fprintf(out,"... Commands\n") fprintf(out,"... %v %-3v [dicName] [dicURL ] -- Import dictionary\n",cmd,DIC_COM_LOAD) fprintf(out,"... %v %-3v [pattern] -- Search in dictionary\n",cmd,DIC_COM_DUMP) fprintf(out,"... %v %-3v [dicName] -- List dictionaries\n",cmd,DIC_COM_LIST) fprintf(out,"... %v %-3v [dicName] -- Disable dictionaries\n",cmd,DIC_COM_DIS) fprintf(out,"... %v %-3v [dicName] -- Enable dictionaries\n",cmd,DIC_COM_ENA) fprintf(out,"... Keys ... %v\n","ESC can be used for '\\'") fprintf(out,"... \\c -- Reverse the case of the last character\n",) fprintf(out,"... \\i -- Replace input with translated text\n",) fprintf(out,"... \\j -- On/Off translation mode\n",) fprintf(out,"... \\l -- Force Lower Case\n",) fprintf(out,"... \\u -- Force Upper Case (software CapsLock)\n",) fprintf(out,"... \\v -- Show translation actions\n",) fprintf(out,"... \\x -- Replace the last input character with it Hexa-Decimal\n",) } func xDic(argv[]string){ if len(argv) <= 1 { helpDic(argv) return } argv = argv[1:] var debug = false var info = false var silent = false var dump = false var builtin = false cmd := argv[0] argv = argv[1:] opt := "" arg := "" if 0 < len(argv) { arg1 := argv[0] if arg1[0] == '-' { switch arg1 { default: fmt.Printf("--Ed-- Unknown option(%v)\n",arg1) return case "-b": builtin = true case "-d": debug = true case "-s": silent = true case "-v": info = true } opt = arg1 argv = argv[1:] } } dicName := "" dicURL := "" if 0 < len(argv) { arg = argv[0] dicName = arg argv = argv[1:] } if 0 < len(argv) { dicURL = argv[0] argv = argv[1:] } if false { fprintf(stderr,"--Dd-- com(%v) opt(%v) arg(%v)\n",cmd,opt,arg) } if cmd == DIC_COM_LOAD { //dicType := "" dicBody := "" if !builtin && dicName != "" && dicURL == "" { f,err := os.Open(dicName) if err == nil { dicURL = dicName }else{ f,err = os.Open(dicName+".html") if err == nil { dicURL = dicName+".html" }else{ f,err = os.Open("gshdic-"+dicName+".html") if err == nil { dicURL = "gshdic-"+dicName+".html" } } } if err == nil { var buf = make([]byte,128*1024) count,err := f.Read(buf) f.Close() if info { fprintf(stderr,"--Id-- ReadDic(%v,%v)\n",count,err) } dicBody = string(buf[0:count]) } } if dicBody == "" { switch arg { default: dicName = "WorldDic" dicURL = WorldDic if info { fprintf(stderr,"--Id-- default dictionary \"%v\"\n", dicName); } case "wnn": dicName = "WnnDic" dicURL = WnnDic case "sumomo": dicName = "SumomoDic" dicURL = SumomoDic case "sijimi": dicName = "SijimiDic" dicURL = SijimiDic case "jkl": dicName = "JKLJaDic" dicURL = JA_JKLDic } if debug { fprintf(stderr,"--Id-- %v URL=%v\n\n",dicName,dicURL); } dicv := strings.Split(dicURL,",") if debug { fprintf(stderr,"--Id-- %v encoded data...\n",dicName) fprintf(stderr,"Type: %v\n",dicv[0]) fprintf(stderr,"Body: %v\n",dicv[1]) fprintf(stderr,"\n") } body,_ := base64.StdEncoding.DecodeString(dicv[1]) dicBody = string(body) } if info { fmt.Printf("--Id-- %v %v\n",dicName,dicURL) fmt.Printf("%s\n",dicBody) } if debug { fprintf(stderr,"--Id-- dicName %v text...\n",dicName) fprintf(stderr,"%v\n",string(dicBody)) } entv := strings.Split(dicBody,"\n"); if info { fprintf(stderr,"--Id-- %v scan...\n",dicName); } var added int = 0 var dup int = 0 for i,v := range entv { var pat string var out string fmt.Sscanf(v,"%s %s",&pat,&out) if len(pat) <= 0 { }else{ if 0 <= isinDic(pat) { dup += 1 continue } romkana[dicents] = RomKana{dicName,pat,out,0} dicents += 1 added += 1 Romkan = append(Romkan,RomKana{dicName,pat,out,0}) if debug { fmt.Printf("[%3v]:[%2v]%-8v [%2v]%v\n", i,len(pat),pat,len(out),out) } } } if !silent { url := dicURL if strBegins(url,"data:") { url = "builtin" } fprintf(stderr,"--Id-- %v scan... %v added, %v dup. / %v total (%v)\n", dicName,added,dup,len(Romkan),url); } // should sort by pattern length for conclete match, for performance if debug { arg = "" // search pattern dump = true } } if cmd == DIC_COM_DUMP || dump { fprintf(stderr,"--Id-- %v dump... %v entries:\n",dicName,len(Romkan)); var match = 0 for i := 0; i < len(Romkan); i++ { dic := Romkan[i].dic pat := Romkan[i].pat out := Romkan[i].out if arg == "" || 0 <= strings.Index(pat,arg)||0 <= strings.Index(out,arg) { fmt.Printf("\\\\%v\t%v [%2v]%-8v [%2v]%v\n", i,dic,len(pat),pat,len(out),out) match += 1 } } fprintf(stderr,"--Id-- %v matched %v / %v entries:\n",arg,match,len(Romkan)); } } func loadDefaultDic(dic int){ if( 0 < len(Romkan) ){ return } //fprintf(stderr,"\r\n") xDic([]string{"dic",DIC_COM_LOAD}); var info = false if info { fprintf(stderr,"--Id-- Conguraturations!! WorldDic is now activated.\r\n") fprintf(stderr,"--Id-- enter \"dic\" command for help.\r\n") } } func readDic()(int){ /* var rk *os.File; var dic = "MyIME-dic.txt"; //rk = fopen("romkana.txt","r"); //rk = fopen("JK-JA-morse-dic.txt","r"); rk = fopen(dic,"r"); if( rk == NULL_FP ){ if( true ){ fprintf(stderr,"--%s-- Could not load %s\n",MyIMEVER,dic); } return -1; } if( true ){ var di int; var line = make(StrBuff,1024); var pat string var out string for di = 0; di < 1024; di++ { if( fgets(line,sizeof(line),rk) == NULLSP ){ break; } fmt.Sscanf(string(line[0:strlen(line)]),"%s %s",&pat,&out); //sscanf(line,"%s %[^\r\n]",&pat,&out); romkana[di].pat = pat; romkana[di].out = out; //fprintf(stderr,"--Dd- %-10s %s\n",pat,out) } dicents += di if( false ){ fprintf(stderr,"--%s-- loaded romkana.txt [%d]\n",MyIMEVER,di); for di = 0; di < dicents; di++ { fprintf(stderr, "%s %s\n",romkana[di].pat,romkana[di].out); } } } fclose(rk); //romkana[dicents].pat = "//ddump" //romkana[dicents].pat = "//ddump" // dump the dic. and clean the command input */ return 0; } func matchlen(stri string, pati string)(int){ if strBegins(stri,pati) { return len(pati) }else{ return 0 } } func convs(src string)(string){ var si int; var sx = len(src); var di int; var mi int; var dstb []byte for si = 0; si < sx; { // search max. match from the position if strBegins(src[si:],"%x/") { // %x/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 //fmt.Sscanf(src[si+3:si+3+ix],"%d",&iv) fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%x",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%d/") { // %d/integer/ // s/a/b/ ix := strings.Index(src[si+3:],"/") if 0 < ix { var iv int = 0 fmt.Sscanf(src[si+3:si+3+ix],"%v",&iv) sval := fmt.Sprintf("%d",iv) bval := []byte(sval) dstb = append(dstb,bval...) si = si+3+ix+1 continue } } if strBegins(src[si:],"%t") { now := time.Now() if true { date := now.Format(time.Stamp) dstb = append(dstb,[]byte(date)...) si = si+3 } continue } var maxlen int = 0; var len int; mi = -1; for di = 0; di < dicents; di++ { len = matchlen(src[si:],romkana[di].pat); if( maxlen < len ){ maxlen = len; mi = di; } } if( 0 < maxlen ){ out := romkana[mi].out; dstb = append(dstb,[]byte(out)...); si += maxlen; }else{ dstb = append(dstb,src[si]) si += 1; } } return string(dstb) } func trans(src string)(int){ dst := convs(src); xfputss(dst,stderr); return 0; } //------------------------------------------------------------- LINEEDIT // "?" at the top of the line means searching history // should be compatilbe with Telnet const ( EV_MODE = 255 EV_IDLE = 254 EV_TIMEOUT = 253 GO_UP = 252 // k GO_DOWN = 251 // j GO_RIGHT = 250 // l GO_LEFT = 249 // h DEL_RIGHT = 248 // x GO_TOPL = 'A'-0x40 // 0 GO_ENDL = 'E'-0x40 // $ GO_TOPW = 239 // b GO_ENDW = 238 // e GO_NEXTW = 237 // w GO_FORWCH = 229 // f GO_PAIRCH = 228 // % GO_DEL = 219 // d HI_SRCH_FW = 209 // / HI_SRCH_BK = 208 // ? HI_SRCH_RFW = 207 // n HI_SRCH_RBK = 206 // N ) // should return number of octets ready to be read immediately //fprintf(stderr,"\n--Select(%v %v)\n",err,r.Bits[0]) var EventRecvFd = -1 // file descriptor var EventSendFd = -1 const EventFdOffset = 1000000 const NormalFdOffset = 100 func putEvent(event int, evarg int){ if true { if EventRecvFd < 0 { var pv = []int{-1,-1} syscall.Pipe(pv) EventRecvFd = pv[0] EventSendFd = pv[1] //fmt.Printf("--De-- EventPipe created[%v,%v]\n",EventRecvFd,EventSendFd) } }else{ if EventRecvFd < 0 { // the document differs from this spec // https://golang.org/src/syscall/syscall_unix.go?s=8096:8158#L340 sv,err := syscall.Socketpair(syscall.AF_UNIX,syscall.SOCK_STREAM,0) EventRecvFd = sv[0] EventSendFd = sv[1] if err != nil { fmt.Printf("--De-- EventSock created[%v,%v](%v)\n", EventRecvFd,EventSendFd,err) } } } var buf = []byte{ byte(event)} n,err := syscall.Write(EventSendFd,buf) if err != nil { fmt.Printf("--De-- putEvent[%v](%3v)(%v %v)\n",EventSendFd,event,n,err) } } func ungets(str string){ for _,ch := range str { putEvent(int(ch),0) } } func (gsh*GshContext)xReplay(argv[]string){ hix := 0 tempo := 1.0 xtempo := 1.0 repeat := 1 for _,a := range argv { // tempo if strBegins(a,"x") { fmt.Sscanf(a[1:],"%f",&xtempo) tempo = 1 / xtempo //fprintf(stderr,"--Dr-- tempo=[%v]%v\n",a[2:],tempo); }else if strBegins(a,"r") { // repeat fmt.Sscanf(a[1:],"%v",&repeat) }else if strBegins(a,"!") { fmt.Sscanf(a[1:],"%d",&hix) }else{ fmt.Sscanf(a,"%d",&hix) } } if hix == 0 || len(argv) <= 1 { hix = len(gsh.CommandHistory)-1 } fmt.Printf("--Ir-- Replay(!%v x%v r%v)\n",hix,xtempo,repeat) //dumpEvents(hix) //gsh.xScanReplay(hix,false,repeat,tempo,argv) go gsh.xScanReplay(hix,true,repeat,tempo,argv) } // syscall.Select // 2020-0827 GShell-0.2.3 /* func FpollIn1(fp *os.File,usec int)(uintptr){ nfd := 1 rdv := syscall.FdSet {} fd1 := fp.Fd() bank1 := fd1/32 mask1 := int32(1 << fd1) rdv.Bits[bank1] = mask1 fd2 := -1 bank2 := -1 var mask2 int32 = 0 if 0 <= EventRecvFd { fd2 = EventRecvFd nfd = fd2 + 1 bank2 = fd2/32 mask2 = int32(1 << fd2) rdv.Bits[bank2] |= mask2 //fmt.Printf("--De-- EventPoll mask added [%d][%v][%v]\n",fd2,bank2,mask2) } tout := syscall.NsecToTimeval(int64(usec*1000)) //n,err := syscall.Select(nfd,&rdv,nil,nil,&tout) // spec. mismatch err := syscall.Select(nfd,&rdv,nil,nil,&tout) if err != nil { //fmt.Printf("--De-- select() err(%v)\n",err) } if err == nil { if 0 <= fd2 && (rdv.Bits[bank2] & mask2) != 0 { if false { fmt.Printf("--De-- got Event\n") } return uintptr(EventFdOffset + fd2) }else if (rdv.Bits[bank1] & mask1) != 0 { return uintptr(NormalFdOffset + fd1) }else{ return 1 } }else{ return 0 } } */ func fgetcTimeout1(fp *os.File,usec int)(int){ READ1: //readyFd := FpollIn1(fp,usec) readyFd := CFpollIn1(fp,usec) if readyFd < 100 { return EV_TIMEOUT } var buf [1]byte if EventFdOffset <= readyFd { fd := int(readyFd-EventFdOffset) _,err := syscall.Read(fd,buf[0:1]) if( err != nil ){ return EOF; }else{ if buf[0] == EV_MODE { recvEvent(fd) goto READ1 } return int(buf[0]) } } _,err := fp.Read(buf[0:1]) if( err != nil ){ return EOF; }else{ return int(buf[0]) } } func visibleChar(ch int)(string){ switch { case '!' <= ch && ch <= '~': return string(ch) } switch ch { case ' ': return "\\s" case '\n': return "\\n" case '\r': return "\\r" case '\t': return "\\t" } switch ch { case 0x00: return "NUL" case 0x07: return "BEL" case 0x08: return "BS" case 0x0E: return "SO" case 0x0F: return "SI" case 0x1B: return "ESC" case 0x7F: return "DEL" } switch ch { case EV_IDLE: return fmt.Sprintf("IDLE") case EV_MODE: return fmt.Sprintf("MODE") } return fmt.Sprintf("%X",ch) } func recvEvent(fd int){ var buf = make([]byte,1) _,_ = syscall.Read(fd,buf[0:1]) if( buf[0] != 0 ){ romkanmode = true }else{ romkanmode = false } } func (gsh*GshContext)xScanReplay(hix int,replay bool,repeat int,tempo float64,argv[]string){ var Start time.Time var events = []Event{} for _,e := range Events { if hix == 0 || e.CmdIndex == hix { events = append(events,e) } } elen := len(events) if 0 < elen { if events[elen-1].event == EV_IDLE { events = events[0:elen-1] } } for r := 0; r < repeat; r++ { for i,e := range events { nano := e.when.Nanosecond() micro := nano / 1000 if Start.Second() == 0 { Start = time.Now() } diff := time.Now().Sub(Start) if replay { if e.event != EV_IDLE { putEvent(e.event,0) if e.event == EV_MODE { // event with arg putEvent(int(e.evarg),0) } } }else{ fmt.Printf("%7.3fms #%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n", float64(diff)/1000000.0, i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event), float64(e.evarg)/1000000.0) } if e.event == EV_IDLE { d := time.Duration(float64(time.Duration(e.evarg)) * tempo) //nsleep(time.Duration(e.evarg)) nsleep(d) } } } } func dumpEvents(arg[]string){ hix := 0 if 1 < len(arg) { fmt.Sscanf(arg[1],"%d",&hix) } for i,e := range Events { nano := e.when.Nanosecond() micro := nano / 1000 //if e.event != EV_TIMEOUT { if hix == 0 || e.CmdIndex == hix { fmt.Printf("#%-3v !%-3v [%v.%06d] %3v %02X %-4v %10.3fms\n",i, e.CmdIndex, e.when.Format(time.Stamp),micro, e.event,e.event,visibleChar(e.event),float64(e.evarg)/1000000.0) } //} } } func fgetcTimeout(fp *os.File,usec int)(int){ ch := fgetcTimeout1(fp,usec) if ch != EV_TIMEOUT { now := time.Now() if 0 < len(Events) { last := Events[len(Events)-1] dura := int64(now.Sub(last.when)) Events = append(Events,Event{last.when,EV_IDLE,dura,last.CmdIndex}) } Events = append(Events,Event{time.Now(),ch,0,CmdIndex}) } return ch } var TtyMaxCol = 72 // to be obtained by ioctl? var EscTimeout = (100*1000) var ( MODE_VicMode bool // vi compatible command mode MODE_ShowMode bool romkanmode bool // shown translation mode, the mode to be retained MODE_Recursive bool // recursive translation MODE_CapsLock bool // software CapsLock MODE_LowerLock bool // force lower-case character lock MODE_ViInsert int // visible insert mode, should be like "I" icon in X Window MODE_ViTrace bool // output newline before translation ) type IInput struct { lno int lastlno int pch []int // input queue prompt string line string right string inJmode bool pinJmode bool waitingMeta string // waiting meta character LastCmd string } func (iin*IInput)Getc(timeoutUs int)(int){ ch1 := EOF ch2 := EOF ch3 := EOF if( 0 < len(iin.pch) ){ // deQ ch1 = iin.pch[0] iin.pch = iin.pch[1:] }else{ ch1 = fgetcTimeout(stdin,timeoutUs); } if( ch1 == 033 ){ /// escape sequence ch2 = fgetcTimeout(stdin,EscTimeout); if( ch2 == EV_TIMEOUT ){ }else{ ch3 = fgetcTimeout(stdin,EscTimeout); if( ch3 == EV_TIMEOUT ){ iin.pch = append(iin.pch,ch2) // enQ }else{ switch( ch2 ){ default: iin.pch = append(iin.pch,ch2) // enQ iin.pch = append(iin.pch,ch3) // enQ case '[': switch( ch3 ){ case 'A': ch1 = GO_UP; // ^ case 'B': ch1 = GO_DOWN; // v case 'C': ch1 = GO_RIGHT; // > case 'D': ch1 = GO_LEFT; // < case '3': ch4 := fgetcTimeout(stdin,EscTimeout); if( ch4 == '~' ){ //fprintf(stderr,"x[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); ch1 = DEL_RIGHT } } case '\\': //ch4 := fgetcTimeout(stdin,EscTimeout); //fprintf(stderr,"y[%02X %02X %02X %02X]\n",ch1,ch2,ch3,ch4); switch( ch3 ){ case '~': ch1 = DEL_RIGHT } } } } } return ch1 } func (inn*IInput)clearline(){ var i int fprintf(stderr,"\r"); // should be ANSI ESC sequence for i = 0; i < TtyMaxCol; i++ { // to the max. position in this input action fputc(' ',os.Stderr); } fprintf(stderr,"\r"); } func (iin*IInput)Redraw(){ redraw(iin,iin.lno,iin.line,iin.right) } func redraw(iin *IInput,lno int,line string,right string){ inMeta := false showMode := "" showMeta := "" // visible Meta mode on the cursor position showLino := fmt.Sprintf("!%d! ",lno) InsertMark := "" // in visible insert mode if MODE_VicMode { }else if 0 < len(iin.right) { InsertMark = " " } if( 0 < len(iin.waitingMeta) ){ inMeta = true if iin.waitingMeta[0] != 033 { showMeta = iin.waitingMeta } } if( romkanmode ){ //romkanmark = " *"; }else{ //romkanmark = ""; } if MODE_ShowMode { romkan := "--" inmeta := "-" inveri := "" if MODE_CapsLock { inmeta = "A" } if MODE_LowerLock { inmeta = "a" } if MODE_ViTrace { inveri = "v" } if MODE_VicMode { inveri = ":" } if romkanmode { romkan = "\343\201\202" if MODE_CapsLock { inmeta = "R" }else{ inmeta = "r" } } if inMeta { inmeta = "\\" } showMode = "["+romkan+inmeta+inveri+"]"; } Pre := "\r" + showMode + showLino Output := "" Left := "" Right := "" if romkanmode { Left = convs(line) Right = InsertMark+convs(right) }else{ Left = line Right = InsertMark+right } Output = Pre+Left if MODE_ViTrace { Output += iin.LastCmd } Output += showMeta+Right for len(Output) < TtyMaxCol { // to the max. position that may be dirty Output += " " // should be ANSI ESC sequence // not necessary just after newline } Output += Pre+Left+showMeta // to set the cursor to the current input position fprintf(stderr,"%s",Output) if MODE_ViTrace { if 0 < len(iin.LastCmd) { iin.LastCmd = "" fprintf(stderr,"\r\n") } } } // utf8 func delHeadChar(str string)(rline string,head string){ _,clen := utf8.DecodeRune([]byte(str)) head = string(str[0:clen]) return str[clen:],head } func delTailChar(str string)(rline string, last string){ var i = 0 var clen = 0 for { _,siz := utf8.DecodeRune([]byte(str)[i:]) if siz <= 0 { break } clen = siz i += siz } last = str[len(str)-clen:] return str[0:len(str)-clen],last } // 3> for output and history // 4> for keylog? // Command Line Editor func xgetline(lno int, prevline string, gsh*GshContext)(string){ var iin IInput iin.lastlno = lno iin.lno = lno CmdIndex = len(gsh.CommandHistory) if( isatty(0) == 0 ){ if( sfgets(&iin.line,LINESIZE,stdin) == NULL ){ iin.line = "exit\n"; }else{ } return iin.line } if( true ){ //var pts string; //pts = ptsname(0); //pts = ttyname(0); //fprintf(stderr,"--pts[0] = %s\n",pts?pts:"?"); } if( false ){ fprintf(stderr,"! "); fflush(stderr); sfgets(&iin.line,LINESIZE,stdin); return iin.line } system("/bin/stty -echo -icanon"); xline := iin.xgetline1(prevline,gsh) system("/bin/stty echo sane"); return xline } func (iin*IInput)Translate(cmdch int){ romkanmode = !romkanmode; if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); }else if( cmdch == 'J' ){ fprintf(stderr,"J\r\n"); iin.inJmode = true } iin.Redraw(); loadDefaultDic(cmdch); iin.Redraw(); } func (iin*IInput)Replace(cmdch int){ iin.LastCmd = fmt.Sprintf("\\%v",string(cmdch)) iin.Redraw(); loadDefaultDic(cmdch); dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" if( cmdch == 'I' ){ fprintf(stderr,"I\r\n"); iin.inJmode = true } iin.Redraw(); } // aa 12 a1a1 func isAlpha(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } return false } func isAlnum(ch rune)(bool){ if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' { return true } if '0' <= ch && ch <= '9' { return true } return false } // 0.2.8 2020-0901 created // DecodeRuneInString func (iin*IInput)GotoTOPW(){ str := iin.line i := len(str) if i <= 0 { return } //i0 := i i -= 1 lastSize := 0 var lastRune rune var found = -1 for 0 < i { // skip preamble spaces lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if !isAlnum(lastRune) { // character, type, or string to be searched i -= lastSize continue } break } for 0 < i { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { continue } // not the character top if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i -= lastSize } if found < 0 && i == 0 { found = 0 } if 0 <= found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word i += lastSize } iin.right = str[i:] + iin.right if 0 < i { iin.line = str[0:i] }else{ iin.line = "" } } //fmt.Printf("\n(%d,%d,%d)[%s][%s]\n",i0,i,found,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoENDW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var lastW = 0 i := 0 inWord := false lastRune,lastSize = utf8.DecodeRuneInString(str[0:]) if isAlnum(lastRune) { r,z := utf8.DecodeRuneInString(str[lastSize:]) if 0 < z && isAlnum(r) { inWord = true } } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i // the last alnum if in alnum word i += lastSize } if inWord { goto DISP } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if isAlnum(lastRune) { // character, type, or string to be searched break } i += lastSize } for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched break } lastW = i i += lastSize } DISP: if 0 < lastW { iin.line = iin.line + str[0:lastW] iin.right = str[lastW:] } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0901 created func (iin*IInput)GotoNEXTW(){ str := iin.right if len(str) <= 0 { return } lastSize := 0 var lastRune rune var found = -1 i := 1 for i < len(str) { lastRune,lastSize = utf8.DecodeRuneInString(str[i:]) if lastSize <= 0 { break } // broken data? if !isAlnum(lastRune) { // character, type, or string to be searched found = i break } i += lastSize } if 0 < found { if isAlnum(lastRune) { // or non-kana character }else{ // when positioning to the top o the word found += lastSize } iin.line = iin.line + str[0:found] if 0 < found { iin.right = str[found:] }else{ iin.right = "" } } //fmt.Printf("\n(%d)[%s][%s]\n",i,iin.line,iin.right) //fmt.Printf("") // set debug messae at the end of line } // 0.2.8 2020-0902 created func (iin*IInput)GotoPAIRCH(){ str := iin.right if len(str) <= 0 { return } lastRune,lastSize := utf8.DecodeRuneInString(str[0:]) if lastSize <= 0 { return } forw := false back := false pair := "" switch string(lastRune){ case "{": pair = "}"; forw = true case "}": pair = "{"; back = true case "(": pair = ")"; forw = true case ")": pair = "("; back = true case "[": pair = "]"; forw = true case "]": pair = "["; back = true case "<": pair = ">"; forw = true case ">": pair = "<"; back = true case "\"": pair = "\""; // context depednet, can be f" or back-double quote case "'": pair = "'"; // context depednet, can be f' or back-quote // case Japanese Kakkos } if forw { iin.SearchForward(pair) } if back { iin.SearchBackward(pair) } } // 0.2.8 2020-0902 created func (iin*IInput)SearchForward(pat string)(bool){ right := iin.right found := -1 i := 0 if strBegins(right,pat) { _,z := utf8.DecodeRuneInString(right[i:]) if 0 < z { i += z } } for i < len(right) { if strBegins(right[i:],pat) { found = i break } _,z := utf8.DecodeRuneInString(right[i:]) if z <= 0 { break } i += z } if 0 <= found { iin.line = iin.line + right[0:found] iin.right = iin.right[found:] return true }else{ return false } } // 0.2.8 2020-0902 created func (iin*IInput)SearchBackward(pat string)(bool){ line := iin.line found := -1 i := len(line)-1 for i = i; 0 <= i; i-- { _,z := utf8.DecodeRuneInString(line[i:]) if z <= 0 { continue } //fprintf(stderr,"-- %v %v\n",pat,line[i:]) if strBegins(line[i:],pat) { found = i break } } //fprintf(stderr,"--%d\n",found) if 0 <= found { iin.right = line[found:] + iin.right iin.line = line[0:found] return true }else{ return false } } // 0.2.8 2020-0902 created // search from top, end, or current position func (gsh*GshContext)SearchHistory(pat string, forw bool)(bool,string){ if forw { for _,v := range gsh.CommandHistory { if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } }else{ hlen := len(gsh.CommandHistory) for i := hlen-1; 0 < i ; i-- { v := gsh.CommandHistory[i] if 0 <= strings.Index(v.CmdLine,pat) { //fprintf(stderr,"\n--De-- found !%v [%v]%v\n",i,pat,v.CmdLine) return true,v.CmdLine } } } //fprintf(stderr,"\n--De-- not-found(%v)\n",pat) return false,"(Not Found in History)" } // 0.2.8 2020-0902 created func (iin*IInput)GotoFORWSTR(pat string,gsh*GshContext){ found := false if 0 < len(iin.right) { found = iin.SearchForward(pat) } if !found { found,line := gsh.SearchHistory(pat,true) if found { iin.line = line iin.right = "" } } } func (iin*IInput)GotoBACKSTR(pat string, gsh*GshContext){ found := false if 0 < len(iin.line) { found = iin.SearchBackward(pat) } if !found { found,line := gsh.SearchHistory(pat,false) if found { iin.line = line iin.right = "" } } } func (iin*IInput)getstring1(prompt string)(string){ // should be editable iin.clearline(); fprintf(stderr,"\r%v",prompt) str := "" for { ch := iin.Getc(10*1000*1000) if ch == '\n' || ch == '\r' { break } sch := string(ch) str += sch fprintf(stderr,"%s",sch) } return str } // search pattern must be an array and selectable with ^N/^P var SearchPat = "" var SearchForw = true func (iin*IInput)xgetline1(prevline string, gsh*GshContext)(string){ var ch int; MODE_ShowMode = false MODE_VicMode = false iin.Redraw(); first := true for cix := 0; ; cix++ { iin.pinJmode = iin.inJmode iin.inJmode = false ch = iin.Getc(1000*1000) if ch != EV_TIMEOUT && first { first = false mode := 0 if romkanmode { mode = 1 } now := time.Now() Events = append(Events,Event{now,EV_MODE,int64(mode),CmdIndex}) } if ch == 033 { MODE_ShowMode = true MODE_VicMode = !MODE_VicMode iin.Redraw(); continue } if MODE_VicMode { switch ch { case '0': ch = GO_TOPL case '$': ch = GO_ENDL case 'b': ch = GO_TOPW case 'e': ch = GO_ENDW case 'w': ch = GO_NEXTW case '%': ch = GO_PAIRCH case 'j': ch = GO_DOWN case 'k': ch = GO_UP case 'h': ch = GO_LEFT case 'l': ch = GO_RIGHT case 'x': ch = DEL_RIGHT case 'a': MODE_VicMode = !MODE_VicMode ch = GO_RIGHT case 'i': MODE_VicMode = !MODE_VicMode iin.Redraw(); continue case '~': right,head := delHeadChar(iin.right) if len([]byte(head)) == 1 { ch = int(head[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.right = string(ch) + right } iin.Redraw(); continue case 'f': // GO_FORWCH iin.Redraw(); ch = iin.Getc(3*1000*1000) if ch == EV_TIMEOUT { iin.Redraw(); continue } SearchPat = string(ch) SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '/': SearchPat = iin.getstring1("/") // should be editable SearchForw = true iin.GotoFORWSTR(SearchPat,gsh) iin.Redraw(); continue case '?': SearchPat = iin.getstring1("?") // should be editable SearchForw = false iin.GotoBACKSTR(SearchPat,gsh) iin.Redraw(); continue case 'n': if SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue case 'N': if !SearchForw { iin.GotoFORWSTR(SearchPat,gsh) }else{ iin.GotoBACKSTR(SearchPat,gsh) } iin.Redraw(); continue } } switch ch { case GO_TOPW: iin.GotoTOPW() iin.Redraw(); continue case GO_ENDW: iin.GotoENDW() iin.Redraw(); continue case GO_NEXTW: // to next space then iin.GotoNEXTW() iin.Redraw(); continue case GO_PAIRCH: iin.GotoPAIRCH() iin.Redraw(); continue } //fprintf(stderr,"A[%02X]\n",ch); if( ch == '\\' || ch == 033 ){ MODE_ShowMode = true metach := ch iin.waitingMeta = string(ch) iin.Redraw(); // set cursor //fprintf(stderr,"???\b\b\b") ch = fgetcTimeout(stdin,2000*1000) // reset cursor iin.waitingMeta = "" cmdch := ch if( ch == EV_TIMEOUT ){ if metach == 033 { continue } ch = metach }else /* if( ch == 'm' || ch == 'M' ){ mch := fgetcTimeout(stdin,1000*1000) if mch == 'r' { romkanmode = true }else{ romkanmode = false } continue }else */ if( ch == 'k' || ch == 'K' ){ MODE_Recursive = !MODE_Recursive iin.Translate(cmdch); continue }else if( ch == 'j' || ch == 'J' ){ iin.Translate(cmdch); continue }else if( ch == 'i' || ch == 'I' ){ iin.Replace(cmdch); continue }else if( ch == 'l' || ch == 'L' ){ MODE_LowerLock = !MODE_LowerLock MODE_CapsLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'u' || ch == 'U' ){ MODE_CapsLock = !MODE_CapsLock MODE_LowerLock = false if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'v' || ch == 'V' ){ MODE_ViTrace = !MODE_ViTrace if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else if( ch == 'c' || ch == 'C' ){ if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) if len([]byte(tail)) == 1 { ch = int(tail[0]) if( 'a' <= ch && ch <= 'z' ){ ch = ch + 'A'-'a' }else if( 'A' <= ch && ch <= 'Z' ){ ch = ch + 'a'-'A' } iin.line = xline + string(ch) } } if MODE_ViTrace { fprintf(stderr,"%v\r\n",string(cmdch)); } iin.Redraw(); continue }else{ iin.pch = append(iin.pch,ch) // push ch = '\\' } } switch( ch ){ case 'P'-0x40: ch = GO_UP case 'N'-0x40: ch = GO_DOWN case 'B'-0x40: ch = GO_LEFT case 'F'-0x40: ch = GO_RIGHT } //fprintf(stderr,"B[%02X]\n",ch); switch( ch ){ case 0: continue; case '\t': iin.Replace('j'); continue case 'X'-0x40: iin.Replace('j'); continue case EV_TIMEOUT: iin.Redraw(); if iin.pinJmode { fprintf(stderr,"\\J\r\n") iin.inJmode = true } continue case GO_UP: if iin.lno == 1 { continue } cmd,ok := gsh.cmdStringInHistory(iin.lno-1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno - 1 } iin.Redraw(); continue case GO_DOWN: cmd,ok := gsh.cmdStringInHistory(iin.lno+1) if ok { iin.line = cmd iin.right = "" iin.lno = iin.lno + 1 }else{ iin.line = "" iin.right = "" if iin.lno == iin.lastlno-1 { iin.lno = iin.lno + 1 } } iin.Redraw(); continue case GO_LEFT: if 0 < len(iin.line) { xline,tail := delTailChar(iin.line) iin.line = xline iin.right = tail + iin.right } iin.Redraw(); continue; case GO_RIGHT: if( 0 < len(iin.right) && iin.right[0] != 0 ){ xright,head := delHeadChar(iin.right) iin.right = xright iin.line += head } iin.Redraw(); continue; case EOF: goto EXIT; case 'R'-0x40: // replace dst := convs(iin.line+iin.right); iin.line = dst iin.right = "" iin.Redraw(); continue; case 'T'-0x40: // just show the result readDic(); romkanmode = !romkanmode; iin.Redraw(); continue; case 'L'-0x40: iin.Redraw(); continue case 'K'-0x40: iin.right = "" iin.Redraw(); continue case 'E'-0x40: iin.line += iin.right iin.right = "" iin.Redraw(); continue case 'A'-0x40: iin.right = iin.line + iin.right iin.line = "" iin.Redraw(); continue case 'U'-0x40: iin.line = "" iin.right = "" iin.clearline(); iin.Redraw(); continue; case DEL_RIGHT: if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } continue; case 0x7F: // BS? not DEL if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } /* else if( 0 < len(iin.right) ){ iin.right,_ = delHeadChar(iin.right) iin.Redraw(); } */ continue; case 'H'-0x40: if( 0 < len(iin.line) ){ iin.line,_ = delTailChar(iin.line) iin.Redraw(); } continue; } if( ch == '\n' || ch == '\r' ){ iin.line += iin.right; iin.right = "" iin.Redraw(); fputc(ch,stderr); break; } if MODE_CapsLock { if 'a' <= ch && ch <= 'z' { ch = ch+'A'-'a' } } if MODE_LowerLock { if 'A' <= ch && ch <= 'Z' { ch = ch+'a'-'A' } } iin.line += string(ch); iin.Redraw(); } EXIT: return iin.line + iin.right; } func getline_main(){ line := xgetline(0,"",nil) fprintf(stderr,"%s\n",line); /* dp = strpbrk(line,"\r\n"); if( dp != NULL ){ *dp = 0; } if( 0 ){ fprintf(stderr,"\n(%d)\n",int(strlen(line))); } if( lseek(3,0,0) == 0 ){ if( romkanmode ){ var buf [8*1024]byte; convs(line,buff); strcpy(line,buff); } write(3,line,strlen(line)); ftruncate(3,lseek(3,0,SEEK_CUR)); //fprintf(stderr,"outsize=%d\n",(int)lseek(3,0,SEEK_END)); lseek(3,0,SEEK_SET); close(3); }else{ fprintf(stderr,"\r\ngotline: "); trans(line); //printf("%s\n",line); printf("\n"); } */ } //== end ========================================================= getline // // $USERHOME/.gsh/ // gsh-rc.txt, or gsh-configure.txt // gsh-history.txt // gsh-aliases.txt // should be conditional? // func (gshCtx *GshContext)gshSetupHomedir()(bool) { homedir,found := userHomeDir() if !found { fmt.Printf("--E-- You have no UserHomeDir\n") return true } gshhome := homedir + "/" + GSH_HOME _, err2 := os.Stat(gshhome) if err2 != nil { err3 := os.Mkdir(gshhome,0700) if err3 != nil { fmt.Printf("--E-- Could not Create %s (%s)\n", gshhome,err3) return true } fmt.Printf("--I-- Created %s\n",gshhome) } gshCtx.GshHomeDir = gshhome return false } func setupGshContext()(GshContext,bool){ gshPA := syscall.ProcAttr { "", // the staring directory os.Environ(), // environ[] []uintptr{os.Stdin.Fd(),os.Stdout.Fd(),os.Stderr.Fd()}, nil, // OS specific } cwd, _ := os.Getwd() gshCtx := GshContext { cwd, // StartDir "", // GetLine []GChdirHistory { {cwd,time.Now(),0} }, // ChdirHistory gshPA, []GCommandHistory{}, //something for invokation? GCommandHistory{}, // CmdCurrent false, []int{}, syscall.Rusage{}, "", // GshHomeDir Ttyid(), false, false, []PluginInfo{}, []string{}, " ", "v", ValueStack{}, GServer{"",""}, // LastServer "", // RSERV cwd, // RWD CheckSum{}, } err := gshCtx.gshSetupHomedir() return gshCtx, err } func (gsh*GshContext)gshelllh(gline string)(bool){ ghist := gsh.CmdCurrent ghist.WorkDir,_ = os.Getwd() ghist.WorkDirX = len(gsh.ChdirHistory)-1 //fmt.Printf("--D--ChdirHistory(@%d)\n",len(gsh.ChdirHistory)) ghist.StartAt = time.Now() rusagev1 := Getrusagev() gsh.CmdCurrent.FoundFile = []string{} fin := gsh.tgshelll(gline) rusagev2 := Getrusagev() ghist.Rusagev = RusageSubv(rusagev2,rusagev1) ghist.EndAt = time.Now() ghist.CmdLine = gline ghist.FoundFile = gsh.CmdCurrent.FoundFile /* record it but not show in list by default if len(gline) == 0 { continue } if gline == "hi" || gline == "history" { // don't record it continue } */ gsh.CommandHistory = append(gsh.CommandHistory, ghist) return fin } // Main loop func script(gshCtxGiven *GshContext) (_ GshContext) { gshCtxBuf,err0 := setupGshContext() if err0 { return gshCtxBuf; } gshCtx := &gshCtxBuf //fmt.Printf("--I-- GSH_HOME=%s\n",gshCtx.GshHomeDir) //resmap() /* if false { gsh_getlinev, with_exgetline := which("PATH",[]string{"which","gsh-getline","-s"}) if with_exgetline { gsh_getlinev[0] = toFullpath(gsh_getlinev[0]) gshCtx.GetLine = toFullpath(gsh_getlinev[0]) }else{ fmt.Printf("--W-- No gsh-getline found. Using internal getline.\n"); } } */ ghist0 := gshCtx.CmdCurrent // something special, or gshrc script, or permanent history gshCtx.CommandHistory = append(gshCtx.CommandHistory,ghist0) prevline := "" skipping := false for hix := len(gshCtx.CommandHistory); ; { gline := gshCtx.getline(hix,skipping,prevline) if skipping { if strings.Index(gline,"fi") == 0 { fmt.Printf("fi\n"); skipping = false; }else{ //fmt.Printf("%s\n",gline); } continue } if strings.Index(gline,"if") == 0 { //fmt.Printf("--D-- if start: %s\n",gline); skipping = true; continue } if false { os.Stdout.Write([]byte("gotline:")) os.Stdout.Write([]byte(gline)) os.Stdout.Write([]byte("\n")) } gline = strsubst(gshCtx,gline,true) if false { fmt.Printf("fmt.Printf %%v - %v\n",gline) fmt.Printf("fmt.Printf %%s - %s\n",gline) fmt.Printf("fmt.Printf %%x - %s\n",gline) fmt.Printf("fmt.Printf %%U - %s\n",gline) fmt.Printf("Stouut.Write -") os.Stdout.Write([]byte(gline)) fmt.Printf("\n") } /* // should be cared in substitution ? if 0 < len(gline) && gline[0] == '!' { xgline, set, err := searchHistory(gshCtx,gline) if err { continue } if set { // set the line in command line editor } gline = xgline } */ fin := gshCtx.gshelllh(gline) if fin { break; } prevline = gline; hix++; } return *gshCtx } func main() { gshCtxBuf := GshContext{} gsh := &gshCtxBuf argv := os.Args if( isin("wss",argv) ){ gj_server(argv[1:]); return; } if( isin("wsc",argv) ){ gj_client(argv[1:]); return; } if 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } if argv[1] == "gj" { if argv[2] == "listen" { go gj_server(argv[2:]); } if argv[2] == "join" { go gj_client(argv[2:]); } } comx := isinX("-c",argv) if 0 < comx { gshCtxBuf,err := setupGshContext() gsh := &gshCtxBuf if !err { gsh.gshellv(argv[comx+1:]) } return } } if 1 < len(argv) && isin("-s",argv) { }else{ gsh.showVersion(append(argv,[]string{"-l","-a"}...)) } script(nil) //gshCtx := script(nil) //gshelll(gshCtx,"time") } //
//
Considerations
// - inter gsh communication, possibly running in remote hosts -- to be remote shell // - merged histories of multiple parallel gsh sessions // - alias as a function or macro // - instant alias end environ export to the permanent > ~/.gsh/gsh-alias and gsh-environ // - retrieval PATH of files by its type // - gsh as an IME with completion using history and file names as dictionaies // - gsh a scheduler in precise time of within a millisecond // - all commands have its subucomand after "---" symbol // - filename expansion by "-find" command // - history of ext code and output of each commoand // - "script" output for each command by pty-tee or telnet-tee // - $BUILTIN command in PATH to show the priority // - "?" symbol in the command (not as in arguments) shows help request // - searching command with wild card like: which ssh-* // - longformat prompt after long idle time (should dismiss by BS) // - customizing by building plugin and dynamically linking it // - generating syntactic element like "if" by macro expansion (like CPP) >> alias // - "!" symbol should be used for negation, don't wast it just for job control // - don't put too long output to tty, record it into GSH_HOME/session-id/comand-id.log // - making canonical form of command at the start adding quatation or white spaces // - name(a,b,c) ... use "(" and ")" to show both delimiter and realm // - name? or name! might be useful // - htar format - packing directory contents into a single html file using data scheme // - filepath substitution shold be done by each command, expecially in case of builtins // - @N substition for the history of working directory, and @spec for more generic ones // - @dir prefix to do the command at there, that means like (chdir @dir; command) // - GSH_PATH for plugins // - standard command output: list of data with name, size, resouce usage, modified time // - generic sort key option -nm name, -sz size, -ru rusage, -ts start-time, -tm mod-time // -wc word-count, grep match line count, ... // - standard command execution result: a list of string, -tm, -ts, -ru, -sz, ... // - -tailf-filename like tail -f filename, repeat close and open before read // - max. size and max. duration and timeout of (generated) data transfer // - auto. numbering, aliasing, IME completion of file name (especially rm of quieer name) // - IME "?" at the top of the command line means searching history // - IME %d/0x10000/ %x/ffff/ // - IME ESC to go the edit mode like in vi, and use :command as :s/x/y/g to edit history // - gsh in WebAssembly // - gsh as a HTTP server of online-manual //---END--- (^-^)//ITS more
// var WorldDic = // "data:text/dic;base64,"+ "Ly8gTXlJTUUvMC4wLjEg6L6e5pu4ICgyMDIwLTA4MTlhKQpzZWthaSDkuJbnlYwKa28g44GT"+ "Cm5uIOOCkwpuaSDjgasKY2hpIOOBoQp0aSDjgaEKaGEg44GvCnNlIOOBmwprYSDjgYsKaSDj"+ "gYQK"; // var WnnDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL2Rp"+ "Y3ZlcglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNXbm5ccy8vXHMyMDIwLTA4MzAK"+ "R1NoZWxsCUdTaGVsbArjgo/jgZ/jgZcJ56eBCndhdGFzaGkJ56eBCndhdGFzaQnnp4EK44Gq"+ "44G+44GICeWQjeWJjQpuYW1hZQnlkI3liY0K44Gq44GL44GuCeS4remHjgpuYWthbm8J5Lit"+ "6YeOCndhCeOCjwp0YQnjgZ8Kc2kJ44GXCnNoaQnjgZcKbm8J44GuCm5hCeOBqgptYQnjgb4K"+ "ZQnjgYgKaGEJ44GvCm5hCeOBqgprYQnjgYsKbm8J44GuCmRlCeOBpwpzdQnjgZkKZVxzCWVj"+ "aG8KZGljCWRpYwplY2hvCWVjaG8KcmVwbGF5CXJlcGxheQpyZXBlYXQJcmVwZWF0CmR0CWRh"+ "dGVccysnJVklbSVkLSVIOiVNOiVTJwp0aW9uCXRpb24KJXQJJXQJLy8gdG8gYmUgYW4gYWN0"+ "aW9uCjwvdGV4dGFyZWE+Cg==" // var SumomoDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTdW1vbW9ccy8vXHMyMDIwLTA4MzAK"+ "c3UJ44GZCm1vCeOCggpubwnjga4KdQnjgYYKY2hpCeOBoQp0aQnjgaEKdWNoaQnlhoUKdXRp"+ "CeWGhQpzdW1vbW8J44GZ44KC44KCCnN1bW9tb21vCeOBmeOCguOCguOCggptb21vCeahgwpt"+ "b21vbW8J5qGD44KCCiwsCeOAgQouLgnjgIIKPC90ZXh0YXJlYT4K" // var SijimiDic = // "data:text/dic;base64,"+ "PG1ldGEgY2hhcnNldD0iVVRGLTgiPgo8dGV4dGFyZWEgY29scz04MCByb3dzPTQwPgovL3Zl"+ "cglHU2hlbGxcc0lNRVxzZGljdGlvbmFyeVxzZm9yXHNTaGlqaW1pXHMvL1xzMjAyMC0wODMw"+ "CnNpCeOBlwpzaGkJ44GXCmppCeOBmAptaQnjgb8KbmEJ44GqCmp1CeOBmOOChQp4eXUJ44KF"+ "CnUJ44GGCm5pCeOBqwprbwnjgZMKYnUJ44G2Cm5uCeOCkwpubwnjga4KY2hpCeOBoQp0aQnj"+ "gaEKa2EJ44GLCnJhCeOCiQosLAnjgIEKLi4J44CCCnhuYW5hCeS4gwp4anV1CeWNgQp4bmkJ"+ "5LqMCmtveAnlgIsKa29xCeWAiwprb3gJ5YCLCm5hbmFqdXVuaXgJNzIKbmFuYWp1dW5peHgJ"+ "77yX77ySCm5hbmFqdXVuaVgJ77yX77ySCuS4g+WNgeS6jHgJNzIKa29idW5uCeWAi+WIhgp0"+ "aWthcmFxCeOBoeOBi+OCiQp0aWthcmEJ5YqbCmNoaWthcmEJ5YqbCjwvdGV4dGFyZWE+Cg=" // var JA_JKLDic = // "data:text/dic;base64,"+ "Ly92ZXJsCU15SU1FamRpY2ptb3JzZWpKQWpKS0woMjAyMGowODE5KSheLV4pL1NhdG94SVRT"+ "CmtqamprbGtqa2tsa2psIOS4lueVjApqamtqamwJ44GCCmtqbAnjgYQKa2tqbAnjgYYKamtq"+ "amwJ44GICmtqa2trbAnjgYoKa2pra2wJ44GLCmpramtrbAnjgY0Ka2tramwJ44GPCmpramps"+ "CeOBkQpqampqbAnjgZMKamtqa2psCeOBlQpqamtqa2wJ44GXCmpqamtqbAnjgZkKa2pqamts"+ "CeOBmwpqamprbAnjgZ0KamtsCeOBnwpra2prbAnjgaEKa2pqa2wJ44GkCmtqa2pqbAnjgaYK"+ "a2tqa2tsCeOBqApramtsCeOBqgpqa2prbAnjgasKa2tra2wJ44GsCmpqa2psCeOBrQpra2pq"+ "bAnjga4Kamtra2wJ44GvCmpqa2tqbAnjgbIKampra2wJ44G1CmtsCeOBuApqa2tsCeOBuwpq"+ "a2tqbAnjgb4Ka2tqa2psCeOBvwpqbAnjgoAKamtra2psCeOCgQpqa2tqa2wJ44KCCmtqamwJ"+ "44KECmpra2pqbAnjgoYKampsCeOCiApra2tsCeOCiQpqamtsCeOCigpqa2pqa2wJ44KLCmpq"+ "amwJ44KMCmtqa2psCeOCjQpqa2psCeOCjwpramtramwJ44KQCmtqamtrbAnjgpEKa2pqamwJ"+ "44KSCmtqa2prbAnjgpMKa2pqa2psCeODvApra2wJ44KbCmtramprbAnjgpwKa2pramtqbAnj"+ "gIEK"; // // /*
References
*/ /*
Raw Source
Whole file
CSS part
JavaScript part
Builtin data part
*/ /*
GJScript
 function gjtest1(){ alert('Hello GJScript!'); }
  gjtest1()
*/ /* (^_^)//{Hit j k l h}
CLOSE

*/ /*
class BlinderText // https://w3c.github.io/uievents/#event-type-keydown // // 2020-09-21 class BlinderText - textarea element not to be readable // // BlinderText attributes // bl_plainText - null // bl_hideChecksum - [false] // bl_showLength - [false] // bl_visible - [false] // data-bl_config - [] // - min. length // - max. length // - acceptable charset in generete text // function BlinderChecksum(text){ plain = text.bl_plainText; return strCRC32(plain,plain.length).toFixed(0); } function BlinderKeydown(ev){ pass = ev.target if( ev.code == 'Enter' ){ ev.preventDefault(); } ev.stopPropagation() } function BlinderKeyup1(ev){ blind = ev.target if( ev.code == 'Backspace'){ blind.bl_plainText = blind.bl_plainText.slice(0,blind.bl_plainText.length-1) }else if( and(ev.code == 'KeyV', ev.ctrlKey) ){ blind.bl_visible = !blind.bl_visible; }else if( and(ev.code == 'KeyL', ev.ctrlKey) ){ blind.bl_showLength = !blind.bl_showLength; }else if( and(ev.code == 'KeyU', ev.ctrlKey) ){ blind.bl_plainText = ""; }else if( and(ev.code == 'KeyR', ev.ctrlKey) ){ checksum = BlinderChecksum(blind); blind.bl_plainText = checksum; //.toString(32); }else if( ev.code == 'Enter' ){ ev.stopPropagation(); ev.preventDefault(); return; }else if( ev.key.length != 1 ){ console.log('KeyUp: '+ev.code+'/'+ev.key); return; }else{ blind.bl_plainText += ev.key; } leng = blind.bl_plainText.length; //console.log('KeyUp: '+ev.code+'/'+blind.bl_plainText); checksum = BlinderChecksum(blind) % 10; // show last one digit only visual = ''; if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '['; } if( !blind.bl_hideCheckSum ){ visual += '#'+checksum.toString(10); } if( blind.bl_showLength ){ visual += '/' + leng; } if( !blind.bl_hideCheckSum || blind.bl_showLength ){ visual += '] '; } if( blind.bl_visible ){ visual += blind.bl_plainText; }else{ visual += '*'.repeat(leng); } blind.value = visual; } function BlinderKeyup(ev){ BlinderKeyup1(ev); ev.stopPropagation(); } // https://w3c.github.io/uievents/#keyboardevent // https://w3c.github.io/uievents/#uievent // https://dom.spec.whatwg.org/#event function BlinderTextEvent(){ ev = event; blind = ev.target; console.log('Event '+ev.type+'@'+blind.nodeName+'#'+blind.id) if( ev.type == 'keyup' ){ BlinderKeyup(ev); }else if( ev.type == 'keydown' ){ BlinderKeydown(ev); }else{ console.log('thru-event '+ev.type+'@'+blind.nodeName+'#'+blind.id) } } //< textarea hidden id="BlinderTextClassDef" class="textField"" // onkeydown="BlinderTextEvent()" onkeyup="BlinderTextEvent()" // spellcheck="false">< /textarea> //< textarea hidden id="gj_pass1" // class="textField BlinderText" // placeholder="PassWord1" // onkeydown="BlinderTextEvent()" // onkeyup="BlinderTextEvent()" // spellcheck="false"< /textarea> function SetupBlinderText(parent,txa,phold){ if( txa == null ){ txa = document.createElement('textarea'); //txa.id = id; } txa.setAttribute('class','textField BlinderText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','BlinderTextEvent()'); txa.setAttribute('onkeyup','BlinderTextEvent()'); txa.setAttribute('spellcheck','false'); //txa.setAttribute('bl_plainText','false'); txa.bl_plainText = ''; //parent.appendChild(txa); } function DestroyBlinderText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); txa.bl_plainText = ''; } // // visible textarea like Username // function VisibleTextEvent(){ if( event.code == 'Enter' ){ if( event.target.NoEnter ){ event.preventDefault(); } } event.stopPropagation(); } function SetupVisibleText(parent,txa,phold){ txa.setAttribute('class','textField VisibleText'); txa.setAttribute('placeholder',phold); txa.setAttribute('onkeydown','VisibleTextEvent()'); txa.setAttribute('onkeyup', 'VisibleTextEvent()'); txa.setAttribute('spellcheck','false'); cols = txa.getAttribute('cols'); if( cols != null ){ txa.style.width = '580px'; console.log(txa.id+' cols='+cols) }else{ console.log(txa.id+' NO cols') } rows = txa.getAttribute('rows'); if( rows != null ){ txa.style.height = '40px'; txa.style.resize = 'both'; txa.NoEnter = false; }else{ txa.NoEnter = true; } } function DestroyVisibleText(txa){ txa.removeAttribute('class'); txa.removeAttribute('placeholder'); txa.removeAttribute('onkeydown'); txa.removeAttribute('onkeyup'); txa.removeAttribute('spellcheck'); cols = txa.removeAttribute('cols'); }
*/ /* */ // //
Golang / JavaScript Link // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // INSTALL: sudo {apt,yum} install git (if git is not instlled yet) // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_port = "localhost:9999" const gshws_path = "gshws" const gshws_url = "ws://"+gshws_port+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) return "["+now.Format(time.Stamp)+"."+us+"] --WS-" + what + ": " } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } var WSV = []*websocket.Conn{} func jsend(argv []string){ if len(argv) <= 1 { fmt.Printf("--Ij %v [-m] command arguments\n",argv[0]) return } argv = argv[1:] if( len(WSV) == 0 ){ fmt.Printf("--Ej-- No link now\n") return } if( 1 < len(WSV) ){ fmt.Printf("--Ij-- multiple links (%v)\n",len(WSV)) } multicast := false // should be filtered with regexp if( 0 < len(argv) && argv[0] == "-m" ){ multicast = true argv = argv[1:] } args := strings.Join(argv," ") now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) msg := fmtstring("%v SEND gshell|* %v",tstamp,args) if( multicast ){ for i,ws := range WSV { wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } }else{ i := 0 ws := WSV[i] wn,werr := ws.Write([]byte(msg)) if( werr != nil ){ fmt.Printf("[%v] wn=%v, werr=%v\n",i,wn,werr) } glog("SQ",fmtstring("(%v) %v",wn,msg)) } } func serv1(ws *websocket.Conn) { WSV = append(WSV,ws) fmt.Print("\n") fmt.Printf("-- accepted connections[%v]\n",len(WSV)) //remoteAddr := ws.RemoteAddr //fmt.Printf("-- accepted %v\n",remoteAddr) //fmt.Printf("-- accepted %v\n",ws.Config()) //fmt.Printf("-- accepted %v\n",ws.Config().Header) //fmt.Printf("-- accepted %v // %v\n",ws,serv1) var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) margv := strings.Split(req," "); margv = margv[1:] if( 0 < len(margv) ){ if( margv[0] == "RESP" ){ // should forward to the destination continue; } } now := time.Now() msec := now.UnixNano() / 1000000; tstamp := fmtstring("%.3f",float64(msec)/1000.0) res := fmtstring("%v "+"CAST"+" %v",tstamp,req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") wsv := []*websocket.Conn{} for _,v := range WSV { if( v != ws ){ wsv = append(wsv,v) } } WSV = wsv fmt.Printf("-- closed %v\n",ws) ws.Close() } func gj_server(argv []string) { port := gshws_port glog("LS",fmtstring("listening at %v",gshws_url)) http.Handle("/"+gshws_path,websocket.Handler(serv1)) err := http.ListenAndServe(port,nil) gchk("LE",err) } func gj_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/*

Execute command "gsh gj listen" on the localhost and push the Join button:


Username: Password:
*/ /* *///