霞ヶ浦

社長:ただいま。

経理:遅かったですね。

社長:土浦に行ったついでに、霞ヶ浦を眺めてきました。iCloudに同期中ですがクソ遅いのでとりあえずNotes経由で一枚。

基盤:湖というより空ですね。

社長:湖面がらしく写ってるのがあると良いのですが。なにせファインダーがないんで、明るい所では何が写っているのか確認ができないんです。後で見てのお楽しみということで、電池切れになるまで取りまくっちゃいましたよ。ちゃんとしたカメラ欲しいなー。

経理:深いフードみたいのをかぶせるとか。

基盤:片隅にUFOとか写ってると面白いですね。

開発:肉眼では今日はそれほどスッキリした青空でもなかったですけど、バカチョンデジカメにはそのへんのニュアンスは写らないですね。

社長:この撮影で一番楽しかったのは、実は撮影する前でした。はじめは、高架道を降りて左折してヨットハーバー側に向かったのですが、夏草茫々のせいで先に進めないしその先の無粋なフェンスのせいで全然先が見えない。で、諦めて逆方向というか駅前交差点から直進し直すと、すぐに湖の堤防の内側づたいになる。住宅街と堤防の間の狭い道に圧迫感を受けながら車を走らせる。

社長:車を走らせながら、堤防に遮られて見えないけど、そこに霞ヶ浦があるに違いない、早く見たい、見たいなーという期待感。それでようやく車を停めるところを見つけて、堤防の階段をかけ登ると、ぱっと開ける地平まで続く湖の景色。この瞬間が最高でした。強めの湖風もすごく気持ちよかったです。

社長:あとは、平日の堤防を歩いたりジョギングしている自由人そうな人たち。学校帰りの自転車の中学生たち。彼らにはもうこの素敵な景色はスルーされているというか、空気になってるようなのが面白かった。

開発:つくば市内だと地平どころか遠景が筑波山くらいしか無い、かといって目印になるような高いビルも無いので、閉塞感がありますよね。

社長:ちなみにこれは午前中のつくば市内から。実際、肉眼でみたのとはかなり違います。

開発:智恵子がほんとうの空を4Kのデジカメで撮ったらどういう写真になったでしょうね。

社長:あの光るのが阿武隈川って指さされて、どれどれとデジタルズームできたりすると、なんだか興ざめですね。

開発:青空って、地上の人工物との対比で映えますよね。

社長:子供の頃、CQ誌でアマチュア無線のアンテナの広告を見てはうっとりしてましたね。それを思い出して近所の鉄塔をパシャり。

基盤:iCloudですが、ようやく8枚。まだ土浦まで行ってません。

基盤:というかこれ、普通にiPhoneをストレージとして見てUSBで直接写真ファイルを吸い出せますよね。

社長:それが、最近はiCloud保存にしちゃってるので、だめかも知れません。再び Notes でバイパス… これとかは比較的足元から遠景まで。撮った時は日常感が写ってちょっとなーと思ったんですが、デフォルメすると見えないですね。

開発:バランス的には落ち着いた感じですね。

社長:あるいはこれとか。実際の湖の色はこれが近かったです。

基盤:対岸が入っててほしかった。

社長:余計なものが映り込むのでズームして切ったんです。

社長:現場の全体的な雰囲気はこういう感じでした。

基盤:これは壁紙に良いかもですね。とろける感。

開発:下手な合成モノよりずっと良いですね。

基盤:湖面の表情と言う意味ではさっきのから切り取ればまずまずだと思います。

開発:湖面の荒涼とした表情がのどかな遠景とミスマッチしてて面白いですね。

社長:デスクトップの壁紙にするには解像度が。やはり、Retina の壁紙に耐える解像度でとれるカメラを購入しましょう。それで、筑波山を撮りに行きたいです。

基盤:ロープウェイでGo!

開発:むかし、日の出前のつくば山頂から見降ろした関東平野は絶景でしたね。

-- 2020-0904 SatoxITS

GShell 0.3.0 − 電子署名付きHTML5

社長:さて、今日は署名をやりましょうか。

開発:まずは JavaScript で出来ることが重要と思いますが、普通にRSAのライブラリとかあるみたいですね。Goにももちろんあります。

Multi Purpose な使い倒し

社長:せっかくなら、コードの正本証明以外にも使いたいですね。

開発:一粒で何度も美味しい。

社長:せっかく精神は我社の基本精神です。一つの技術、一つのコードを多目的に使い倒す。more with lessです。

基盤:フッ素ひとすじみたいなやつですね。

開発:アクセス権の制御というか、ライセンスキー的なものはやってみたいですね。キーをもってないとしかるべく実行できない。HTMLの一部のエレメントを表示できないとか。class で制御する。

基盤:それをやるには、HTMLソースでも見えないように暗号化する必要がありますよね。

社長:まあROT13みたいなもんですね。いっそdata URI にするという手もあります。href やら src やらで、URLとして自分のHTML内のエレメントIDを使えると良いのですけどね。dom:elmentId.innerHTML みたいな。

基盤:エレメント id は IPv6アドレスなんていうのが面白いですね。

開発:Goコードにしても、ソースでは配布するんだけれど、デフォでは読めないようにするには、暗号化する以外の道は無いですよね。

社長:なんか電子署名より暗号化のほうが面白そうな気がして来ました。

基盤:画像ならモザイクをかけるとか面白そうですね。

社長:プログラムなら、それが利用できるリソースとか実行性能を制御する感じでしょね。あるいは機能の一部を無効化する。

基盤:ありがちな手口ですねw

開発:チャンレンジアンドレスポンスで、簡便なワンタイムパスワード的なものもやりたいです。

基盤:クライアント側もチャレンジすると面白いんではないかと。

開発:SSLってそんな感じになってないんでしたっけ?

自前タイムスタンパー

開発:コードの電子署名では、何しろタイムスタンプを押すところがヤマだと思います。どこで押してもらうか。

社長:タイムスタンプの無い署名とか、知財保護の用途では意味が無いですね。

基盤:骨董品とかもそうですね。

開発:昔から電子署名技術があったら鑑定士も失職ですね。

社長:鑑定… って、いいキーワードのような気がします。証明とかよりいい。

経理:タイムスタンパーって月1万円くらいかかるって聞きましたが。

開発:一発乱数生成するだけの証明書発行と違って、ずっとインフラ運用にコストが掛かりますからね。

社長:あれ、鍵を生成するのが証明書屋だっていうのが、全く理解できないです。

開発:実印と同じじゃないですかね。ハンコを彫った人は同じハンコを作れますから。

基盤:昔なら、機密兵器を作った技術者は、作り終えたら生き埋めにされちゃったりしてましたね。

社長:まあ、印鑑証明ってのは今の時代全く意味が不明なものの一つです。

基盤:でも社長がいつも使ってるタイムスタンパーは無償ですよね。

社長:なもので、QOSとかは無保証だと思うんです。それに、機械生成で1日に何百回も何万回を押したら怒られるか、サーバがダウンするか、ではないかと。

基盤:まあ、ファイアウォールでフィルタされておしまいでしょうけど。

開発:そういう使い方をしたら、有償のサーバでも落ちそうです。

経理:といいますか、タイムスタンプ代で一日に数万円飛びそうですね。

開発:なもので、タイムスタンパーも自前で立てる、何かの形で、1時間に1度くらいで、公式のタイムスタンパーに証明してもらう、というのが良い線だと思います。

社長:毎時間、自前タイムスタンプをするための鍵を作って、それを証明してもらう感じですかね。

基盤:有効期限が1時間の証明書みたいな感じですかね。

かぼちゃでGo

開発:おっと、吉田拓郎の夏休みがYouTubeから。

社長:麦わら帽子っていったら吉田拓郎ですよね。

基盤:それにしてもしょぼい演奏と録音ですね。

開発:なんせ1971年だそうですから。

社長:これは、聴かなかったほうが良かったかな (^-^; 想い出の中にしまって。

経理:そろそろまた血圧のサンプリングを。

社長:そうですね。24時間の変動のグラフを作って明日お医者さんに見せたい。ていうか、最近どうもクスリが効かないように思ってたんですが、一粒追加投与したらテキメンでした。

開発:仕事中、特に盛り上がってる時に血圧が下がるのは不自然じゃないかと思いますが。

基盤:めっちゃ血圧が下がったら死にますかね?

開発:思考する葦としては死んだも同然ですね。

社長:血圧が下がったせいか、イマイチ仕事する気にならないです。お腹もすいてきました。でも天気がぱっとしないので外出する気にもならない。

開発:カットかぼちゃがありますね。チンしましょう。

レンジ:ゴー… ぱぺぽ、

開発:どれどれ。あー、さすがにかぼちゃは強靭ですね。延長。

基盤:チューリップのデビュー当時のも悲惨ですが。

社長:財津さんは年食ってからのほうが圧倒的に良いですね。

基盤:ヒジョーに、きびしーいっ。

レンジ:ぴぺぱ、

開発:どれどれ。あーもう少しかな。延長。

レンジ:ぱぺぽ、

開発:どれどれ。あーいい感じですね。90度で15分。もぐもぐ。うん、旨い。

基盤:はぐはぐ…。あまーい。

社長:うーん、もぐもぐ。

基盤:電子レンジって最高ですね。もぐもぐ。

経理:これ、業務用ですか?もぐもぐ。

社長:そうだったかも。回らないで大きなガラス鍋がまるごと入るという基準で選んだんですが。

開発:震災の時に足のゴムがひとつどっかに飛んじゃって不安定なんですよねw もぐもぐ。

経理:そんなに古い機械には見えないですね。もぐもぐ。

開発:栄養価からして、かぼちゃは完全食みたいなもんですね。あなたは万能ですかって。

基礎:馬車も作れるくらいですからね。

社長:multi purpose 過ぎる。

開発:わたしはこれは「カロチン」だと思うですよ。「カロティン」ならまだしも「カロテン」て何だそれ?って。

基盤:でもWikiでは「カロテン」ですね。綴りは carotene(英)、carotin(独)。

開発:あそう。もぐもぐ。完食。

社長:あーおなかいっぱい。これで222円とは!

開発:まるごと買ってきてチンしたら爆発しますかね。

基盤:生状態だと自力ではまずカットできないですよね。

社長:ドリルで空気抜き開ければいいかも。

開発:ハロウィンのかぼちゃてそういういう起源だったのかも知れませんね。

経理:電子レンジはまだなかったんじゃないでしょうか?

基盤:ようやく洗濯物が少したまりました。60度温水泡洗浄でGo!

フルスクリーンShellの夢

開発:ふあぁ… よく寝ました。

基盤:午睡をしたのは久しぶりですね。

社長:今日は夢に ESC [ が出てきました。部分スクロールとか。cosmosの画面とか。

開発:そういえばもうずっと、ステータス行のあるshellを使ってないですね。

社長:macOSのターミナルでステータス行って出せるんですかね?画面の一番下に。

開発:特定のターミナルソフトに期待したり依存せずに、自前でステータス行を作ったほうが良いのではないかと思います。部分スクロールで良いのでは。

基盤:Goでcurses ライブラリパッケージを作っている人は居ますね。goncurses とか gocurses とか。

開発:あれ?これって単に import "C" すればCのライブラリが使えるという事のようですが。。。pkg/C ... おや、golang.org/cmd/cgo に飛ばされました。

社長:そういえばそれ、Cgo ってものがあるって事は覚えています。

開発:げげ、普通に Go の中に C が書けるんですね。どれどれ、Hello World…

社長:感動した。

開発:つまり、GoはCコンパイラとしても使えると。

社長:さらばVC!

基盤:Cの部分をコメント扱いにしちゃってるのが悲しいような。

開発:で、バイナリサイズは…

社長:Goのでっかいしっぽは付いてくるんですね。

開発:で、逆は… なるほど、Goの関数はこうやってCにexportするのか…

基盤:どんどんCに戻っていきそうですがw

開発:全然構いません。実際、可変長データ、スライスを扱うのにGoは便利ですけど、あとは普通にC風言語としてしか使ってないですからね。型宣言とforの構文がちょっと違うだけみたいな。IMEの入力部分は最初Cで書いた版を取り込むのに、自分で fprintf とか互換関数を書いたんですが、あれ必要なかったのか ( ; _ ; )。

社長:curses を使ってたのは30年前あたり… というか主に学生時代です。最初は termcap だけで、いずれ curses を使うようになったような。あの頃のコードから骨を拾ってきますかね。

開発:まあ、我々のCUIの環境は当時より明らかに退化してますよね。

社長:といいますか、たとえばブラウザの窓とかフォームとかでテキストを入力する時、あれはGUI世界の中に分散したミクロなCUI世界だと思うのですが、とても貧しいCUIを使わされている感じがします。

開発:GUIでもテキスト入力をするところ全般にGShellを突っ込みたいですね。

基盤:ブラウザのURL窓とかフォームでshellコマンドが打てたらびっくりです。

開発:実際、ターミナルでコマンドを実行した結果を張ることはよくありますよね。

社長:要はIMEですね。分散したCUI世界を、裏で一つに繋ぐ。

基盤:CUI vs GUI だと、C vs Go みたいですw

開発:CとGoのIntegrationでCGIかなw

社長:ところで、Gshellはずっと1ファイルでいけますかね。

開発:今6,000行くらいですが、10万行くらいはへっちゃらな感じがします。

開発:ただ、ユーザが特定の関数をカスタマイズしやすいように、主だった関数はプラグインで置き換えられるようにはするべきかと思います。

社長:実行時に生成したプログラムをプラグインできると良いですね。

開発:そこはちょっと… たぶん、動的リンクは一回しかできないので。でも exec しちゃえばいいのかな。実行状態は継承して。

* * *

基盤:それはそうと今日は、HTML署名の作業は何かしないんでしょうか?

開発:ん、まー、一日一善、何かやりますかね。

社長:善かどうかはわかりませんけど、コードレベルでも前進はしたいですね。

経理:アマゾンから請求書が… ああ、ライトセール代ですね。

基盤:これ以上無い明朗会計です。

基盤:Tokyoぶんは使わなくなったインスタンス2つ、40円/日削れるんですが、インスタンスを捨てる前の確認がまだできていません。

経理:水道代も来てますね。PayB でぴっ。快感〜。パスコードを。

社長:ぷちぷちっと。

携帯:ぶーっ。

Mac:ぽーん。

経理:納付完了しました。

開発:これ、よく出来てますよね。

基盤:しかしこれ、2ヶ月で4,851円。上水道代3,300円、下水道代1,551円て、サービスとしてどうなんでしょうね?1日80円。こちらにはサービス業者の選択肢が無い。

社長:一日50円で霞ヶ浦のおいしい水が飲み放題みたいな。

開発:ここで飲んだことないですけどね。

社長:昔はブリタでよく飲んでました。

基盤:げっ、もうこんな時間に…

社長:血圧を測定… よた話を書いてる時は上がらないみたいですね。

JavaScriptでRSA

開発:それでは電子署名の件をやりましょう。まずはやはり、JavaScriptにて。この SubtleCrypto.encrypto() ってやつですかね。

社長:HTTPSのコンテクストでしか使えないってありますね。オフラインでは使えないですかね。ローカルファイルに使えれば。

開発:そもそもJavaScriptではローカルファイルへのアクセスはできないんじゃないですかね。GShellをHTTPSサーバにすれば良いのではないかと。

社長:extension 使ってもだめなのかなぁ。

開発:で、これがデモページ

開発:View Page Source…

社長:簡単過ぎて意味がわかりません。

開発:まあここはHTMLの箱を定義しているだけですからね。JavaScriptはこちら

社長:ああ、ほっとしますね。でもやっぱり簡単。

開発:ちょっとこのブログサイトにコピーしてテストしてみましょう。というか、まずgsh.html に組み込んじゃって…

社長:HTTPSじゃないので、きっちり怒られますね。

開発:で、これをこのブログ記事に貼り付ける…

基盤:おお!表示がきちゃないけど、ばっちり機能してますね。

開発:このCSS書いた人も、こんなふうに埋め込まれるとは想像しなかったでしょうねw

社長:ちょっと興奮したので血圧を測定… ありゃりゃ、大台を突破してます。

開発:行儀正しいCSSなんだと思いますが、スマホ時代のモノではないなと思います。

基盤:なんか、こんな副作用も出てますね。

開発:このCSSは全削除します。

* * *

社長:思うに、ブログには載せたいけど、この部分は検索サイトにキャッシュされたくないなという部分はあると思うんですよね。そういう場合に、一部を暗号化して置くのは便利だと思います。

開発:すごい昔に、通信を暗号化しちゃいかんみたいな法があったような記憶もありますけどね。

社長:あるいは匿名で書きたいけど、後で誰が書いたのか証明する必要が出てきた時には出来るとか。

基盤:タイガーマスクのタカハシナオトみたいな。

開発:普通、平文の全文をRSAすることは無いと思いますが、ショートメッセージならRSAで暗号化が管理がシンプルで良いかもですね。暗号化して公開側の鍵も添付しちゃう。

社長:クリアテキストで署名はダイジェストに適用して別添ていうのが普通だとは思いますけどね。ただ、クリアテキストの部分が伝達経路で一部機械的に変わってしまう可能性は、現代でも考慮しておく必要はあるのかなと思います。

* * *

社長:明日は定期検診なのでそろそろ終わりましょう。血を抜かれる予定でもあり。

開発:そうですね。今こんな状況です。

社長:これを見て思ったんですが。GShell をやめて GShe!! にしようかなって。あと、この日本語入力がGShell IMEで実現できたらって。

基盤:暗号文が256バイトブロックに収まらない場合には何が起こるんでしょうか。

開発:・・・たぶん対処してないです。明日検討します。

開発:ともかく、これをやってて色々びっくりしました。まず、CSSで上書きをする !important を何時の頃からか !import だと思ってしまっていたんです。なんだか効かないなーと思ってましたw。それと、e.innerHTML=xxx を e.innnerHTML=xxx とか 打ち間違えてしまったんですが、通っちゃうんですね。

社長:クォートしなくていいんで、タグの属性名って予約語的なものかと思ってました。

開発:つまり、一つエレメントを作っておけば、その属性値として何でもぶちこめる。コーディングの見た目もごちゃごちゃしなくていいかなと思います。

基盤:ということは、e.digest とか e.esign なんて属性を決めて、そこにダイジェストや電子署名を入れておけばよいということでしょうか?

開発:・・・そういうことになりますね。明日もう一度確認します・・・

社長:・・・思い違いでないとすると、ちょっと劇的な展開ですね・・・

開発:style とかは出来たと思いますが、 onclick とかも全部 JavaScript で押し込めるとすると、書き方が劇的に変わりますね・・・

社長:あとは document.getElementById("name") なんていう長たらしいのを書かずにすんだら、もっとスッキリするのにね。

開発:そこは我慢して一度だけ var name = document.getElementById("name") をするとします。var は参照されるたびに評価されるみたいなんで、いろんな処理が名前だけで参照できて、"." でつなげるんだと思います。

社長:ダイナミックなマクロみたいですね。

開発:処理の結果を次の処理に渡す記法という意味でも便利ですよね。GShell もこういうふうにすると良いのかなとも思います。scanf().printf() みたいな。

社長:逆にshellのパイプで数珠つなぎにする的なのが JavaScript で書ければ良いと思うのですが。

開発:あと、今日はWordPressの拡張HTMLブロックの中でJavaScriptコードを開発するという妙な実験をしたわけです。ちょこちょこっといじる分にはまあ良いのですが、長いのになるとつらい。特に行数が12行程度で固定されているのが。タブも入れられない。もちろん、vi でも無く。

基盤:vi 互換のブロックエディタがあると良いですね。

開発:あと、そのブロックを独立なURLとしてダウンロードできると良いです。まあ、NFSでファイルとして見えたら最高かも。

開発:それと、ブラウザ内蔵のデベロッパーツールでは、Edgeが一番見やすいかなと思っています。

社長:MSのブラウザが最高とは。時代が変わりましたね。

-- 2020-0903 SatoxITS

/* GShell-0.3.0 by SatoxITS
GShell version 0.3.0 // 2020-09-03 // 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 | Cksum | */ /*
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 ) const ( NAME = "gsh" VERSION = "0.3.0" DATE = "2020-09-03" 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){ 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) 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 { //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } line = line[olen:] off += olen remlen -= olen } } 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); 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) 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 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } 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
*/ /*
WebCrypto Reference: https://mdn.github.io/dom-examples/web-crypto/encrypt-decrypt/index.html

Web Crypto - RSA-OAEP

Plain text:


Cipher text:


Decrypted text:


PublicKey...

*///

GShell 0.2.9 − 電子署名付きHTML

社長:今日はこの件に着手しましょう。

開発:ようやく (^-^)/

基盤:夢のお告げでしょうか?

社長:何度か、クライアントからサーバにつないで何かの機能を設定したり制御するみたいな夢を見るのですが、夢の中ではよしこれでOKって納得しているのに、目が覚めて考えると何の事かわからないんです。

HTMLへの電子署名の方式

開発:実現方法の候補はざっくりこういうところかと思います。

  1. 署名の方法
    1. gsh.html にブラウザからJavaScriptで署名する
    2. gsh.go を実行して署名する
    3. 外部ツールで署名する( OpenSSL | PGP )
  2. 検証の方法
    1. gsh.htmlをブラウザからJavaScriptで検証する
    2. gsh.go を実行して検証する
    3. 外部ツールで検証する( OpenSSL | PGP )
  3. 署名情報の付加形式
    1. 独自形式( HTMLタグ | JavaScriptデータ | QRコードPNG | Goデータ )
    2. S/MIME
    3. PGP
  4. 検証情報の内容
    1. 公開鍵署名
    2. ダイジェスト
    3. タイムスタンプ

社長:検討しましょう。

開発:まず、この方式は gsh.go, gsh.html に限らず、一般の HTML に適用できるのが良いと思います。できれば、Go が無くても使えるようにしたい。そういう意味で、1.1、2.1 が最優先と思います。3 に関しても、既存の表現形式に合わせるための手間がかかるので 3.1 にしたい。まず原理的なフィージビリティを調べてから、標準の形式にはめ込めるか確認したい。

基盤:PNGファイル自体にも署名して内蔵させたいということですが、あれとかそもそも既存の標準形式というのは無いんじゃないですか。

開発:HTMLのタグでやる場合、署名情報を通常は display:none かなんかで非表示にして必要に応じて表示する、各入れ子の部品に対して署名できるとかもできるのが良いと思います。

社長:どこに入れますかね。

開発:候補がありすぎて何なんですが、innerHTMLにする、タグの属性にする、background-imageのdata URIにする、特定の形式のclass名にする、何でもアリかともいます。

基盤:透かしみたいのがカッコいいですね。

対応できるブラウザ

社長:JavaScriptでDOMから自身のHTMLのテキストをダンプするのって、確実にオリジナルを再現できるんでしたっけ?ブラウザ依存性とかは?

開発:gsh.go.html は現在 6071行あります。ブラウザでこれを開いて、Raw Source > Whole file で textarea に表示したテキストをコピーしてファイルに書き込み、オリジナルファイルと比較。通常 Firefox でやっていますが、完全に再現・一致します。もっとも、open を open="" にしちゃう妙な癖に引っかからないようにしています。これって、Firefox のバグじゃないかと思うんですが…

開発:他でもやってみます。Chrome ... 完全一致。

基盤:その、ファイルへのコピペをターミナルにcatするってくっそ遅いですね。15秒くらいかかる。

開発:これが一番わかり易い方法なんで。クリップボードをファイルに吐くってどうやるんですかね… ともかく、そういう環境依存な方法ではなくて、GShell自体をHTTPサーバにして、http://xxx/yyy/gsh.goを要求されたらブラウザがダウンロード・保存できる形式で返そうと思っているのです。それはそうと、Opera では…

開発:Operaでも完全一致。Edgeでは…完全一致。問題児のSafariは…完全一致。

基盤:Chromium系4ブラウザはまあ、共通なんでしょうね。独立系の Firefox と Safari もOKと。あれ? Vivaldi は?

開発:Vivaldiはタブの過積載で圧死寸前でして、社長がブログ書くの以外に使う気になれずw。でもやってみます。たぶん大丈夫でしょう… 

基盤:案の定、コピーで固まりましたね。

社長:案の定たけ坊ってありましたよね。小学校の国語の教科書?

基盤:ググっても出てこないですね。

開発:やはりVivaldi はブックマークが良いので、調べ物とかは Vivaldi でやってずーっと開きっぱなしなんですよ。だから溜まりまくってしまって。

基盤:タスクマネージャで見ると、だいぶサブフレームをぶら下げてるタブがありますね。

開発:一発でサブフレームプロセスを全部殺すっていう機能があると良いのですが。ぷち。ぷち。ぷち。… 少しは軽くなったような?

社長:ウィンドウとかタブごとに動作とか更新を止めるっていう機能があったように思いますが。Vivaldiだったか忘れましたが。

開発:なにはともあれ、コピペ成功。完全一致です。

基盤:全ブラウザ問題無しですね。

社長:前提になる基盤技術に問題なしということですね。

開発:あとはDOMダンプした文字列をダイジェスト関数に食わせるだけでintegrityは検証できます。class=digest みたいなタブにして追加する。このクラスのタブはダイジェストの対象から外す。まあ id 付けて DOM から消しちゃうか、検証対象の span の外に置く。ダイジェストに署名して同様に保存する。それを取り出して検証できれば完成。

基盤:span id=xxx へのダイジェストが class=digest id=xxx-digest とかだとわかりやすいですね。

開発:あとは、検証作業を実行する部分は、検証部分のソースコードを見て信頼してもらうか、そこだけは別経路でアルゴリズムを取ってインストールしといてもらうかということでしょうね。

社長:いい感じです。お昼休みにしましょう。今日は久しぶりにちかくのそば屋かな。

外は雨

社長:帰りました。外は雨です。

基盤:台風の影響ですかね。

社長:考えたら、今月はほとんど雨に出会ったことが無いです。

基盤:野菜が異常に高いのはそのせいですかね。

社長:それで、ゥエルシァでノリと煎りごまを買ってきました。ところてんのトッピングにいいかなって。

基盤:刻み海苔 vs 青ノリ対決ですね。

開発:おやつの楽しみにしましょう。

メールでのgshコード送信

社長:それで、食事しながら考えたんですが、PDF署名だ、S/MIMEだって考えてると、やりたいことの前の敷居が高すぎるなと。WordPress のプラグインだ、ブラウザの extension だってのも回りくどい。本当にやりたいことはめっちゃシンプルなわけですから。だからもう、HTML署名でいいかなって。

開発:そもそも、部品、エレメントごとに署名する、それをさらにまとめて署名する、かつそのまま表示可能、可視・不可視選択思いのママていう使い方を考えると、現状、HTMLしか選択肢が無いように思います。

社長:それで思ったんですが、メールにこの、署名付きのHTMLは貼り付けられるだろうか?って。いやもちろん、添付ファイルとしてはできるでしょうけど。本文として。

基盤:macOS の Mail ですが… HTMLで書いて送るツールは売られてますね。Mail自体がどうサポートしているかはちょっとわかりません。

社長:Mailのインラインで動かせるとうれしいな。

開発:原理的にはまず送れないわけがないですよね。ただの封筒なんで。

基盤:規格外とか。

開発:定形だとは思いますよ。角型4号みたいな。

基盤:まあ、gsh.html とか gsh.go は問題なく添付できます。バイナリの gsh とか gsh.zip はGmailの検閲にかかる模様。gsh.go の場合には、go を app 形式にして準備しておく必要はありそうです。

社長:でもそれ簡単ですよね。Automatorの時にやってみましたけど。

開発:ただ、gsh.html で添付して、ブラウザで開く。ブラウザ用にダウロードボタンを用意してダウンロードして、実行してもらうというのが、一番簡単だとは思いますが。

社長:うーん。.ghtml とかいう拡張子にしてですね、gsh.app で実行する、てな感じがうれしいですね。

基盤:gsh.img とかで送るとか。

社長:毎回それやるのは嫌ですね。一回 gsh.app をインストールしたら、あとはそれに gsh.ghtml を食わせればOKというのがうれいしい。.ghtml は gsh.app で実行するってデフォルトで設定して。

ユーザデータとしての署名付き情報コンテナ

開発:メールの話は一旦置いて、話を巻き戻しますと、要するにHTMLに電子署名を内包させられれば、それは普通ユーザレベルのデータとして転送したり保存したり表示したりできる。特に gsh.html みたいなオールインワンの形式では。

社長:昔で言えばPGPですね。ただあれは、見た目に異物感が否めなかった。

開発:表示を自分で制御する機能は想定されてないですからね。

社長:ある意味無敵のべたテキスト前提。まあそういう時代でした。PEM形式にしたって。

開発:その点、HTMLだと、自身で表示されかたを定義できるし、CSSも JavaScript も内包できるから、リッチだしダイナミック。かといってオールインワンなら外部サイトにもアクセスしないから安全。

基盤:なんだか話がうますぎると言うか簡単過ぎる感じはしますが。なぜこれまでそうなっていなかったのか。

開発:まあ、なんか大きな考え落としをしてるかも知れないですね。よくあることです。

社長:別に頓挫しても、わが社的には痛くも痒くもないです。それに、失敗はかならず貴重な資産になるものです。

とりあえずチェックサム

社長:どうもこの、飲んで食べるとしばらく仕事する気になれませんね。

開発:それが若い頃との大きな違いかなと思います。

社長:腰が重くなるんですよね。でも、一旦腰を上げればあとは結構続く。

開発:どっこいしょっと。では、とりあえずチェックサムを付けてみます。とびきり処理が遅いけど一番使いやすい社長自作のCRC32で…

社長:こんなところでお役に立てようとは。うぅぅ…

開発:あ、これは Go プログラムだからダメですねw。HTML からやるなら、JavaScript で無いと。

社長:40行程度のプログラムですから、簡単にJavaScript にもなると思いますが。

開発:なるほど。では型宣言とキャストを全部はずしてと… おっとイキナリコンパイル通りました。HTMLから配線して。動くかな…

基盤:残念。

開発:デベロッパコンソールで見る… あれ?引数が足りない。追加。

* * *

社長:で、どうなりましたかね。

開発:いやあ、今まで JavaScript の文字列とか、+で結合するくらいしか処理したことが無かったので、その辺でつまづいてしまいました。Moz://a MDN でゼロから勉強です。

開発:ブレークスルーは、文字列の中の指定した位置のバイトの値を取り出すのが、charAt() だと思ってスタックしてたところから、ようやく charCodeAt() が正しいと気がついて脱出した時でしたw。あとは、CRCを作るビット演算をするのに整数が必要なのですが、なぜか32ビット以下には整数配列しか無い。不思議な言語です。

開発:CRCが済んだ後の課題は、自分のソースをダンプするところですが、これはHTMLエンティティがエンコードされているのを戻さないといけない。いや、エンコードされた状態でサインするというのも、別の用途でアリかなとは思います。でも、textarea 要素を作って innerHTMLに html で書いた後に、その .value プロパティでデコードされたテキストを読み出すというシャレたテクニックを Stackoverflow で引っ掛けたので、それでサクッと終わりました。

開発:で完成です。自分で自分自身の CRC32 を作れています。

基盤:おー。パチパチパチ。

社長:めでたし。

ブロッコリー

社長:ああそれで、一昨日スーパーに行った時にふと、ブロッコリーが目に入ったのです。で、これもレンジでチンしたら旨いかなと。

開発:やってみましょう。これも90度で6分くらいですかね… ぽちっと。

レンジ:ゴー…

開発:いやはや今日は、JavaScript の整数に疲れてしまいましたが、ソーステキストのダイジェストを取るとこは通過しました。普通のハッシュアルゴリズムは Go にパッケージが完備してますから、まあ繋ぐだけですね。

レンジ:パペポ、…

開発:どうかな?ガブっ。もうちょっとですかね。でも加熱しすぎて味が壊れるのもいやだし。

基盤:マヨネーズを付けていただきましょう。もぐもぐ。うん、うまい。

社長:もぐもぐ。ブロッコリーとカリフラワーには恐ろしくマヨネーズが合うんですよね。なぜなのか不思議です。

基盤:レタス系もですが、もぐもぐ、葉や花の部分以上にこの太い芯がうまいですよね。ごちそうさまでした。

WordPress拡張HTMLの障害回避

社長:きょうは早めに和やかに終了しようと思ったのですが、WordPressに gsh.html を貼り付けたら JavaScript が動かないようになってました。昨日の版までは動いてます。

開発:今日の追加分の関係ですよね。デベロッパモードで追跡。ああ、JavaScript で構文エラーだと。まさに今日追加したCRC32の部分ですね。あー、& が &#038; にされちゃってますね。<script> の中までそれをやるかねえ?

基盤:「拡張HTML」ブロックで見ると & に見えますから確信的ですね。

社長:というかこれ、普通に signed int32 と int8 にして、正負判定でいいんじゃないでしょうかね。

開発:社長のオリジナルコードですが。

社長:・・・

開発:& 演算子を使わないように変更しました。

社長:WordPressにアップロード。ああ動きました。

開発:WordPressの中で動くGShellはひょうきんで良いですね。

基盤:あれ?でもチェックサムが違う、というかサイズがそもそも違いますね。166バイト小さくなってます。

開発:あれま。

社長:チェックサムの効用が早速出ましたねw 明日対処しましょう。

基盤:checksum の情報として、行数と語数も出てると人間にはうれしいですね。

開発:そうしまふ。

-- 2020-0902 SatoxITS

/* GShell-0.2.9 by SatoxITS
GShell version 0.2.9 // 2020-09-02 // 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 | Cksum | */ /*
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 ) const ( NAME = "gsh" VERSION = "0.2.9" DATE = "2020-09-02" 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){ 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) 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 { //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } line = line[olen:] off += olen remlen -= olen } } 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); 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) 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 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } 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
*/ /* --> *///

GShell 0.2.8 − vi互換コマンドIMe

社長:vi互換のコマンド行編集機能をGShellの一丁目一番地に掲げたいと思います。Featuresの章を作って書いてみました。

基盤:あっさりしてますね。

開発:まあ「ESCでコマンドモードに入る」とだけ伝われば、あとは言わずもがなですからね。

社長:GShell 改め「ViShell」にしようかな…

開発:で、これはそのコマンド入力用IMEという位置づけなんですか?

社長:Comprehensive IMEで CMIE とかどうですかね…

モードのありがたみ

基盤:なぜそんなにviが好きなんですか。

社長:まあ生まれた時からずっとviで育ったというのはあるんですけどね。なにしろ「モード」があるという事に尽きると思います。

開発:文章でもプログラムでも何度も何度も、長期に何年にも渡って読み返しては時々加筆修正するみたいな作業をしていると、ほとんどの時間帯は閲覧モードというか、readonly なんですよ。他人の書いたものとかの場合も多い。

社長:まあ何年か前の自分は他人に近いですからね。

開発:ずっと検索したりブラウズしたりして、よし、ここを変更しようと思った時に、入力モードに入るわけです。入力状態に居るということも明示的に意識される。

開発:これがモードレスだと、間違ってキーを触って文章とかプログラムを壊してしまうという恐ろしさというかストレスを感じるのです。特に文章の場合、コンパイラがチェックしてくれないですからね。

社長:なにしろ生まれつき粗忽ですからね。

社長:だから「繰り返し眺めて直すテキスト編集の作業モデル」と、「入力/コマンドモードというツールのモデル」は、とてもマッチしていると思うのです。人間に課す負担が少ない。

開発:まあGUIのIDEだと、入力する時以外はテキスト画面内でキーを打たないことが多いですけどね。これは vi の時代にはなかったモードです。でも、やっぱり間違って何かキーを触って壊すことはあるわけです。

社長:Wordとかの文章入力ツールの「閲覧モード」とはか全然ハズレてるんですよね。単に改行マークとか外して readonly にしてくれるモードがあればよいのですが。

開発:ブラウザの画面内で / とか n とか g:/.../p なんて出来ると良いのですけどね。

社長:それはぜひ、gsh.go.html でやりましょう。

キーの押し易さ

社長:それから、コマンドモードが入力モードと別れていると、普通は入力にされてしまうキー、打ち易い普通のアルファベットキーたちですね、をコマンドに使えるというメリットがあります。

社長:とにかく私はもう、2つのキーを同時に押すということ自体をストレスに感じているのです。Shiftキーだけは仕方が無いかなと思いますが。ほかのキーは、理想的には、全部やめたい。

開発:Alt だ Command だとかもう、キートップを引っこ抜きたいですね。昔はよく、CapsLock を引っこ抜いてましたけどw

基盤:入力モードとコマンドモードの他にはモードはあるんですか?

開発:・・・「:コマンド」が相当しますかね。行コマンドモードというか。

社長:そのへんは、シェルの入力部分としては、別のモードがあって良いと思いますね。

非英数字の入力

開発:本気でIMEをやるなら、IMEとしてのモードは導入せざるを得ないでしょうね。

基盤:モードと言えば、ローマ字入力じゃなくてひらがなの直接入力はやらないんですか?

開発:・・・それはありかも知れません。ローマ字辞書で統一計画が・・・w

基盤:かなで入力してローマ字にマッピングしてから辞書で変換するとか。

社長:日本語入力ではほとんどの人が、直接その語の文字(ひらがな)を入力するよりも、ローマ字かなでの変換入力を好んで選択してるんですよね。少なくともキーボード入力では。だから、他の言語の人たちもそうすればよいのではないかと思うことが多いです。

開発:そうすればハードウェア的にやたら沢山種類のある各国語キーボードもいらなくなりますね。

基盤:英字数字以外のキー、「:」と「;」の位置すら一定でないのにはめまいがします。

開発:案外「記号の入力」というのが、このIMEの生きる道かも知れませんね。

第3のモード

社長:「: コマンド」は設定画面への遷移にも使われるわけで、これは GShell でも設定モードということになるでしょうかね。

開発:GShell としてそういうコマンドがあれば良いのかなと思います。このへんが、テキストエディタが本業である vi とは違うのではないかと。それに、設定はやはり HTTP でブラウザからやりたいですね。GShell 自体が HTTP サーバになって。

社長:でも、キーボードだけでやりたいことも多いですよね。特に簡単な設定なら。だから、設定用の仮想的なファイルとか、コマンドヒストリとか、:g/.../pの結果とか、コマンドの実行結果を表す仮想的なファイル名を :e できれると良いのかなと思います。設定ファイルでは、フォームみたいのが開く。

開発:松の第0ページでしたっけ、あれは原体験です。モードじゃなくて、あくまでページという。

基盤:モードは入力モードとコマンドモードだけ、あとは操作対象で操作の意味が変わるというのはスッキリしていると思います。

開発:プラス、非ASCII文字列用のIMEのモードというのは、避けられないと思います。onew はどうなってましたっけ?

社長:あれは、Wnn / Canna の提供する入力バッファの編集状態的なモードだったと思いますね。自前のモードというより。

社長:ともかく、簡単な編集機能だけで良いので、フルスクリーンのエディタができると良いですね。フォーム入力対応の。

開発:さすがにそれは、curses 的なライブラリがないとキツイですねぇ。

* * *

基盤:夢は終わり無く始まる〜♪

開発:この作詞者天才ですかね。

社長:でもこの人的な表現というか文体を作る翻訳機はできそうな気もします。

開発:アニメソングの面白いのはこういう直球メッセージを臆面も無く書けるとこですよね。

社長:でもプリキュアを見るような世代にこれが通じてるんですかね?

* * *

基盤:あれ?これ昨日買ってきた蒸しダコ、食べ忘れて室温で一日放置されてましたね。

社長:しまった!

開発:ちょっと食べてみる。うーん、匂いも味もヤバい感じはしませんが…

基盤:レンジでチンしては?

開発:ナイスアイデア。では70度で1分加熱殺菌・・・

レンジ:ゴー… パピペ、パピペ、…

開発:どれどれ。もぐもぐ。うん、うまい。あーでもちょっと不安だからもう1分。

レンジ:ゴー… パピペ、パピペ、…

開発:あーあ、タコから旨い汁が出ちゃってもったいない… もぐもぐ。味が薄くなっちゃった。

基盤:もぐもぐ。悲しい… もぐもぐ。

社長:もぐもぐ。これ、バター炒めにしたら良かったですね。出た汁が粘って絡みつくまで。

開発:お好み焼き屋でやるあれは最高ですよね。

基盤:次回はそれで対処しましょう。

単語単位の移動と編集

開発:vi モードでの単語単位のカーソル移動、超刹那的プログラミングにて完成しました。

社長:分かち書きの言語は単純ですが、日本語文字列の中での単語単位の移動っていうのは、深淵そうな課題ですね。

基盤:近似的には、次の非かな文字へのジャンプってことになりますかね。ああでも、かなだけの単語もありますしね。

社長:本気IMEでは形態素解析とかやるんでしょうね。でも、とりあえず漢字・ひらかな・カタカナの区切りを目印で良いと思います。

開発:といいますか、そもそも内部データがローマ字状態の文字列で、変換表示しているモードではどうするか、なんですが。まあ単語検索は変換された結果のほうでやって、ローマ字表記での位置合わせはそこから逆算する式ですかね。

社長:cw とか dw とか yw がハイライトになりそうな気がします。

複数行対応

社長:vi でめっちゃ使うコマンドと言えば % ですよね。

開発:複数行でやりたいところですが…

社長:本式のスクリーンエディタじゃなくて良いので、数行のエディタをやってみてはどうですかね。上の行に移動するエスケープシーケンスだけでできそうですが。

開発:複数行のコマンドラインというのは面白いですね。継続行を理解する。

基盤:普通のshellでも括弧とかクオーテーションでの複数行入力はサポートされてますね。編集もできる。

社長:ほんとだ。編集できますね。知りませんでした…

開発:複数行に対応するとなると、j と k でヒストリを前後するというキー割当とぶつかっちゃいますね。

社長:というか、/ とか n とかもですね。これは複数行かどうかに関わらず。

開発:vi コマンドモードでの検索編集対象を現在の行の中に限るというモードが必要ということですかね。

社長:そうするしかないように思います。でも、そうめったにやることではないですから、この複雑化は受容できるんじゃないでしょうか。

基盤:b とか w でもヒストリ間を移動するというのアリでは無いでしょうか?

開発:・・・

社長:ヒストリは全部行単位で保管して、この行は何番目のコマンドの何番目の行、という形で扱うするのが良いかもしれないですね。

開発:・・・

開発:要するに、ずっとベタな一枚のベタテキストを入力している。ある行からある行までを折りたたんで表示したり扱うモードと、展開して編集するモード。という感じですかね。ヒストリはそのべたテキストへのインデックスであると。

社長:なんか vin のリスト表示の編集機能のようです。たしか、c で catenate、 i で unfold だったと思います。

開発:で、複数のコマンド行をどのようにグループ化するかは後付けで追加できる。

社長:一行のコマンドの行内を編集するときと、複数行コマンドの中の一行を編集する時は、同じ見え方で同じ操作で。これがいいなと思います。

開発:ちょっと以前作ったヒストリ管理の部分をいじる必要はありますが、まあできると思います。たぶん、より簡単になる。

連続サーチモード

開発:一般的にテキストに向かっている時に、検索モードという作業状態というか心理状態があるように思います。基本的には自分では入力しない。検索する。検索した結果をコピペしたり再実行する。シェルのユーザにおいても、これはあると思います。

基盤:矢印キーで選んでは実行、という繰り返し状態はよくありますね。

開発:矢印キーは、入力モードにおいてもコマンドとして解釈されるので良いのですが、j k はデータキーになってしまうという問題があります。要は、一旦 vi コマンドモードに入った後の状態ですね。j k で選んで RETURN で実行する。その後に、vi コマンドが継続しているべきかいなか。

開発:何が言いたいかというと、vi コマンドモードに入って、? や / でコマンドを検索して、実行する。その後に、再び ? や / で検索したいときに、vi コマンドが継続していてくれたほうが便利なことがあると思います。

社長:実際、ある程度作業してヒストリが溜まったら、過去の入力の中から検索しては、部分的に編集して、再実行することは多いですね。それがやりやすいならなおさらそうなると思います。アドホックなようなコマンドラインですが、文章書きと違って、それほどクリエイティブに入力をしているわけではない。

開発:そういうケースを考えると、デフォが vi コマンドモードであるとか、サーチモードであるっていうのはアリかなと思います。コマンドを実行し終えたら、次のコマンド入力を求めるのではなく、検索文字列を求めるという。

基盤:メニュー形式のようなものですね。

社長:検索サイトが常に、検索モードであるみたいのと似てるかもですね。

開発:・・・

開発:表示形式そのものをメニューにする、というのがあっても良いかも知れませんね。過去に実行したコマンドからメニューを作る。その中から選ぶだけのモード。

包括的検索コマンド

社長:検索文字列そのものも、過去の検索文字列から検索できるのが良いですね。

開発:それはやりたいと思っていました。たぶん、検索モードというかサブコマンド状態になるんだと思いますが、もしフラットなモードでできると、何か面白い事ができるかも知れません。

社長:検索した結果をコマンドラインに呼び出すというコマンドがあったとすれば、それぞれにヒストリに入れておいて、ヒストリ検索で検索コマンドだけ抜き出して、そこから選択するというのが良いように思います。

開発:・・・

開発:つまり、ヒストリから条件検索した結果としてサブセットを作る。さらにその結果に対して検索で絞る。絞った結果からメニューで選ぶこともできる、みたいな感じですかね。

基盤:ファイル検索の結果が一時的な仮想的ファイルになってたり、ヒストリ検索の結果が仮想的なヒストリのビューになっていると良いと思いますね。AND OR 検索なんかもできて。

開発:つまり、ヒストリ検索を包括できるような組み込みの汎用の検索機能があると良いということですね。その包括的な検索機能の適用例の一つとしてヒストリがある。別の情報の検索結果ともイケイケである。

基盤:IMEでの単語の候補も同じ枠組みでできると良いと思います。

開発:同じ読みの単語を検索すると・・・ まあこれは、辞書の検索コマンドは作ったので、あれと統合すれば良いのかな。ユニバーサル検索というか、コンプリヘンシブ検索というか・・・

基盤:whichでの検索、findでの検索、 ヒストリ検索、IME辞書検索、などなどですね。

社長:で、ぜんぶIMEの辞書につっこんじゃうとか。でもって、変換というユーザインタフェースで検索する。あるいは、日付とかで検索する。リソース使用量でソートするとか。

開発:・・・

社長:あと、Q&A形式のインタラクションも、shellでサポートすると良いように思います。

開発:・・・

開発:UIの深みですね。ともかく、/ では まず現在の行内を探す、無かったらヒストリの中を探す、ということにしました。仮置きですけど。

-- 2020-0901 SatoxITS

/* GShell-0.2.4 by SatoxITS
GShell version 0.2.8 // 2020-09-01 // 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 | */ /*
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 ) const ( NAME = "gsh" VERSION = "0.2.8" DATE = "2020-09-01" 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){ 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) 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 { //os.Stdout.Write([]byte("\r\n")) os.Stdout.Write([]byte("\\")) os.Stdout.Write([]byte("\n")) } line = line[olen:] off += olen remlen -= olen } } 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 i uint64 for i = 0; i < len; i++ { var oct = str[i] for bi := 0; bi < 8; bi++ { ovf1 := (crc & 0x80000000) != 0 ovf2 := (oct & 0x80) != 0 ovf := (ovf1 && !ovf2) || (!ovf1 && ovf2) oct <<= 1 crc <<= 1 if ovf { crc ^= CRC32UNIX } } } 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 byteCRC32(str[]byte,len uint64)(crc uint32){ crc = byteCRC32add(0,str,len) crc = byteCRC32end(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 == "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); 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) 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 1 < len(argv) { if isin("version",argv){ gsh.showVersion(argv) return } 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
*/ /* --> *///

株式会社ITS more設立6ヶ月目突入

開発:今月で6ヶ月目に入りました。

経理:当社決算の月です。なんで9月に決めたんでしょうか?

社長:主には竹内まりあの影響ですかね。セプテンバー そして9月は♪

開発:さよならの月♪って不吉じゃないですか。

社長:国際基準かもカッコイーかもみたいな気分で。電子定款を確認する係の人は、設立月に合わせないと消費税免除期間措置で損しますけど良いですかなんてメールで確認をしてくれましたけどね。

経理:ゼロ以上には減免できないですからね。

基盤:だいたい決算報告ったって株主は社長一人なのに、他の誰に知らせる必要があるんでしょう。

経理:税務署ですね。

基盤:税務署の一人だかの人に見てもらうためだけに年4000円もするファイル置き場を借りたんですか。

社長:会社設立支援サイトでの入力フォームで、ラジオをポチッと押したらそうなって、法人登記事項にそのURLが(全角で)乗っちゃってるわけです。なにせこっちは定款を生成するのに必要な情報だと思ってフォームに入力します。で、生成される定款本体にはそういう情報は書かれてないから、何も考えずにスルーしてしまう。法人名のフリガナにしたって、定款本体に記載されるわけでは無いから、あまり考えずに入力したわけです。でもって、あとで法人検索したり登記事項を見ておよよという次第です。まあ、フリガナが検索で公開されるようになったのはわりと最近の登記からのようですし、電子公告のURLについては公証役場でもらった証明書というか登記事項を見て気づいてはいたんですが。

開発:情報入力システムとしては、そこに書いた情報がどのように使われるかは明示しないといけませんよね。

基盤:ってったって、ただのフォームですよね。

社長:いや、あれはありがちなフォームのページとは一味違いまして。見た目がやわらかいし、フェーズを反映して進展するし、マルチメディアだし、タイムアウトして消えたりしないし,よく出来てると思います。それに、わたしはあれの作りを見てはじめて、HTML の details タグの便利さを認識したのです。その点ではあれに恩義を感じていますw

社長:登記事項の変更は単純作業ですが、手間数的には面倒です。しかも、当時の感覚では軽いと思っていた手数料が今は重く感じるので。前払いしちゃったし一度くらい、あそこを使おうかなと。

基盤:なぜ自社サイトにしなかったんですか。

社長:登記の準備を始めた3月中旬の時点ではまだ、この会社用のサイトをちゃんと立ち上げてなかったというか、ふにゃふにゃしてまして。

開発:この毎日のブログを公告したら面白いかもですね。ほぼ日刊ITS公告。

経理:官報に載せてたら破算しますね。

基盤:そもそもスクリーンキャプチャとか動画とか入ってる公告って無いですよね。

社長:税務署の人が楽しんでくれれば。

基盤:電子公告って電子署名するんですかね?

開発:アクセスカウンター付けられると良いですね。

基盤:4000円/年ならクラウドドライブの常識なら500GBくらい使ってもよいのでは。

* * *

基盤:社長のブログにももうひと月分の行が加わります。

社長:では、これを投稿します。ぽちっと。

全員:おおー。

開発:4月に、4月って1行しかなかったのが懐かしいですね。

-- 2020-0901 SatoxITS