GShell 0.4.9 − ブラウザユニオン

社長:今日はもうお昼近いのに、妙に静かな日ですね。

開発:お正月みたいですね。交通音も生活音もして来ない。カラスが鳴いてるし。

基盤:そういえばイツモは何の音が聞こえてるんでしょうね。今聞こえるのはレノボがわずかにモーっていってる音くらいです。

社長:静かだと耳鳴りに気づきます (^-^;

社長:おなかがすいたので食事して来ます。今日は久しぶりにしらす丼かな。

* * *

社長:案の定、今日もよい空がが出ていました。

開発:日常生活の中にこんな風景があったというのがちょっと驚きですね。

基盤:スラムの上にも美しい空が平等に出ているのですね。

開発:ここはスラムではないですけどね。ただ、ともかく電線がうざい。

社長:最近は歩く時にも眼鏡をかけるようになったせいか、空を見るのが楽しいです。地球の空の素晴らしさを再認識させられているところです。

開発:空の再発見。ディスカバースカイですね。

社長:それで帰りに寄ったゥエルシァでは目の大きなロボットお姉さんがレジをしてました。

基盤:目のパッチリした人がマスクしてると、いつも驚いているように見えますよねw

開発:本当にロボットなんですかね。

基盤:直接聞いてみては。

開発:まあ「私はロボットではありません」と答えるのではないでしょうかw

社長:でも最近は学習が進んだみたいで、レジ袋はどうなさいますかとか、スプーンはお付けしますかとか聞いてくるようになりました。

経理:それはそうと、レノボ機はほとんど使っていないようですので、スリープさせておいてはいかがでしょう?

基盤:確かに。では1分でスリープ…

開発:どうやって起こすんでしょう?

基盤:ネットで特定ポートを監視して起きるとかできるはずですね。RemoteDesktopで起きてくれるのではないかと。

社長:いや、手を伸ばせば電源スイッチがありますから、それでも良いです。

* * *

社長:おや、目が醒めたら外はもう暗いです。

開発:日が短くなりましたね。

基盤:そういえば、今日は秋分の日ってやつです。

開発:さすがに昨日は朝から深夜までやったので疲れました。

基盤:gsh.go.html も500行近く大きくなりましたね。

HTMLでウェブサイト魚拓

社長:実質的に一番大きな進歩があった日なのかも知れません。@media screen の記念写真をSafariでパシャ。

開発:コンテンツだけの印刷とはまた違った壊れ方をします。

社長:サイトの印刷ではDOMの現状を吐いて印刷するみたいですから、DOMの状態を印刷用の状態にする、というかスクリーン表示モードを、GShellで作ってやると良いのでしょうね。

開発:そう思います。といいますか、そもそもサイト全体のスクリーン表示の現状を吐き出してPDFにしてくれる機能って、ひょっとすると Safari の File > Export as PDF しかなかったりしないですかね。

基盤:Opera にも、File > Save Page as PDF はありますね。ただ、ページの前半が無くなってたり、details が化けたりと、悲惨ですが。

社長:なぜ details のような基本中の基本の印刷が壊れるんですかね。Chromium系に共通のようですが。

開発:Opera にあるなら、Vivaldi にもあるんじゃないですかね。

基盤:見当たらないですね。メニューのその付近には、得意のセッション保存機能が表示されてます。

社長:ブラウザの黎明期には、Mozaic とか Netscape とか、画面に表示されてる状態をそのまま印刷してましたよね。だから、画面の表示幅を変えて調整してから印刷とかしてました。

開発:画面表示の完璧な再現というか魚拓を期待するなら、それが正しい方法ですね。

社長:つまり、表示する前のDOMではなくて、表示された後のビットマップが手に入れば良いと。

開発:どの x, y 座標に表示されてるのはどのエレメントだと逆探知できるくらいですから、イメージを全部作ってるんじゃないですかね。

基盤:それを頂いて、PNGにしちゃうとかw

開発:非常にひょうきんなサイズの画像ができると思いますが、それが正しい道の一つかなと思います。

基盤:とうかPNGじゃなくて、スゴーク縦長の1ページのPDFに吐いてくれるブラウザがあったような記憶もあるんですが。割と最近に見た記憶が。

社長:バイナリのダンプとしては正しいと思いますが、ソースレベルでのダンプも欲しい。GShellでやってる、DOMの現状を、なるべくオリジナルに近いHTMLで吐くというのも、ひとつの有るべき道だと思います。

開発:まあ「har / はあ」形式のアーカイブですね。生きたDOMの状態というか値がHTMLのインラインで展開されてしまうのが嫌ですが、あそこは、その値を別途JavaScriptで生成するようなscriptに分離すればよいのかなと思います。

社長:人間が見るのに耐える、というのはかなり難しいと思いますが、なんでもいいからダンプして保存しよう、ありのままを保存して後でそのとおりに表示しよう、実行を継続しよう、という意味では、単にDOMのダンプというか、outerHTMLを履けば良いのだと思います。

開発:DOMと、style のバイナリであるCSSOMもですね。あれをCSSのごとくか、設定script形式にして吐く。生成された script コードについてはどうか、不明ですが。

基盤:WebAssembly の逆コンパイルみたいなことになるんですかね。

社長:なんにしても、これは、バイナリの実行状態をソース形式でダンプするという、普通のコンピュータ・プログラムでは難しいことが、ウェブ処理系ではできる可能性があるわけです。面白いですね。

開発:ほんとうの実行状態という意味では、JavaScript のスタックとかヒープのダンプも必要じゃないかとは思いますけど。

社長:まあ、動いてる途中というのまで望まないないですが。少なくともDOMに紐付けられた静的なデータ構造については。

開発:まあ、Lispなら出来るって言う人もいるでしょうけどね。

基盤:WebAssembly って、Lisp みたいなやつじゃなかったでしたっけ。

GShellでブラウザ間パスワード移転

社長:さて、今日もまだ数時間はありますので、何かやりますかね。

開発:テーマとしては、パスワードの移転が良いと思います。

基盤:iMacへの移行が完了しない理由もそれですね。

社長:といいますか、6ブラウザそれぞれに良いところはありますので、どれも並行して使いたいのです。なので、移転というよりは、理想的には常に共有していたい。そもそも、ブラウザにパスワードを記憶させるのも嫌ですし。なので、自前の秘密記憶に置いておいて、それをブラウザから取りに来れるようにしたい。

開発:任意のサイトへのログイン用には、ブラウザに手を入れるか、少なくとも extention は必要でしょうけどね。

社長:clinet-side CSSみたいに、client-side スクリプトを添加できると良いのですが。

基盤:プロキシで突っ込んでやるとか。

開発:HTTPSだとそれが結構むずかしいのです。SSL的には復号・再暗号化で通せますが、エンドポイントがMITMを検査している可能性が高い。昔はDeleGateでやってましたけど、最近は通用しないんじゃないですかね?それができてしまうなら、ネットバンクなんて使う気にならなくなります。

社長:まあ、エンドポイントと同じ秘密鍵を持ってて再暗号化できれば、ってくらいじゃないですかね。

基盤:通信じゃなくてコンテンツが部品単位に署名されてれば良いのでは。自分の署名のある添加スクリプトなら信頼するというのは普通だと思います。

開発:そのへんは逆に、HTTPじゃなくてS/MIMEならできるという話でしょうね。

社長:利用者側クライアント側エージェントとしては許しても、サーバ側、提供者が、スクリプトでの改変を許さない可能性は高いですね。

基盤:利用者ファーストじゃないんですね。まあ、確かに利用者自身は信頼できないですがw

開発:そもそもHTML/HTTPSの通信はGoでやって、表示は既存のブラウザに任せる、という構造がが有力なのかなと思います。これならば、ブラウザのコードとかextensionに手を染める必要は鳴い。ただし、開発にはかなりコストがかかりそう。

社長:一点突破的に、ログイン時のパスワードの移転にだけ使えるエンジンならなんとかなるんじゃないですかね。

開発:そうかも知れません。

現在のページのユーザ名を知る

開発:それではまず、気合を入れるためにこのブログ記事にGShellを貼り付けます。で、昨日寝る前にMDNで読んだLocation API。 location.username を eval。これだけでいけるのではないかと。

基盤:残念!

開発:時代錯誤だった模様。

開発:思うに、それはもうobsoleteだから、今後はこれを使えっていう指示があって欲しいですよね。ワンストップサービスでない。

社長:規格系の情報源は正確な一次情報だけの記載に留めるのは仕方がないかなと思います。でも実際昨年 table 関係の col だったかが思ったように効かなくて探したら「HTML5ではこれは削除された」とだけあって、なぜ、いつそうなったのか、がわからない。代替は何かも検索するのに時間がかかって。あの時は非常に、HTML5とかいうものに腹がたったものです。

開発:ですが一方でMDNは、ブラウザでの実装状況という非常にライブな情報もサービスしているわけです。それがMDNを便りにしている理由でもあるんですが。

社長:そのブラウザの版は何年のものだ、っていう情報がないのも残念です。ドキュメントの更新の時間軸もわからない。

開発:機械的に、昔の記載を残して、そこへのリンクがはってあれば良いと思うのですが。

基盤:WikiPediaはその点が完璧ですね。どういう議論があってそうなったかもちゃんと残されている。

パスワードマネジャー召喚の術

開発:というわけで仕切り直しです。

社長:まあ、JavaScriptからダイレクトに現在のパスワードが見えちゃうなんて、のどかすぎますよね。あー、長閑って書くんですか。でも、自分では書けない漢字とか使いたくないですね。

開発:それで思い出したんですが、昨日 BlinderText を作った時に、「このエレメントにはパスワードを入れてね」っていう、autofil 機能がCSSにあったなって。HTTPS文脈じゃないとブラウザの警告がうるさかったか何かでやめましたが。

レンジでチンできる丼

基盤:ちょっとお腹が空きました。

開発:我が社ではカップラーメンブームが下火でレトルトブームがきていますが、レンジで具を乗せてチンしても熱くならない丼があると良いと思います。

経理:アマゾンで検索。安いのでは500円、3000円あたりが多数、高めだと5000円、10000円なんていうのもありますね。

社長:軽くて断熱性のあるのがいいです。

基盤:陶器だといとぢ理… macOSのデフォルトIMEってやっぱりダメじゃないですか?糸尻が熱くなったりするのがあります。

開発:見た目にこれいいなと思うと、5000円くらいしますね。

社長:これ、食材の入った写真が無いとスケール感がわからないですね。寸だインチだと書かれましても…

基盤:これって、≡GShellとかロゴの入ったボウルだとかわいいでしょうね。

開発:この美濃焼ラーメン丼はいいですね。1800円だし。

社長:内側がかっこいいけど、外側がいまいちのような。19.5cm てデカすぎませんかね。

経理:食材の入った利用例をみるとそうでも無いですね。

社長:じゃこれをひとつ。ちょっとこのメーカーで探しましょう。

基盤:この皿がすごく良いと思います。24.2cmですけど。

開発:カレーを食べるのに良さそうです。ああ、こっちのも良いですね。やっぱり日本製。

基盤:あ、この小さめの3点セット、いい感じだしすごくお手頃価格。

社長:それも追加しましょう。そろそろ探し疲れました。

経理:ただいま、カートの総計、10点で33,661円です(笑)

社長:3000円以上のと、プライムじゃないのを全削除。

経理:ぷちぷちぷちっ… 3件で5090円になりました。

社長:それでGo。

経理:Operaにアマゾンbusinessのパスワードを教えていないようです。

社長:かちゃかちゃ。でも、これは経費ですかね?

基盤:ちょっと待った。その安い3点セット、電子レンジ対応ではないようです。

開発:何から何までチンするわけでもないですけどね。

社長:もっと小さめのも欲しいかな。このまっしろ無地でふにゃっと形状のが良いです。お値打ちだし。

経理:11cm, 14cm, 18cm、追加しました。

社長:じゃあそれで。

基盤:コーヒーカップも欲しいですね。この何十年ものの安物しかなくて。

社長:割れたら考えましょう。

経理:ではこれで。あ、いちど見積書をダウンロードして見ます。

開発:一点だけ法人価格ってありますね。

基盤:この会社は税込みでジャストになるようになってます。

経理:まあ普通の会社だとこれを上に上げて承認をとるわけですね。

社長:承認しました。

経理:ではぷちっ。明後日午前中着です。対面配達選択可能ですが。

社長:置き配で。

経理:注文を確定する。ぷちっ。

基盤:そういえば今回は、注文IDの設定を聞かれませんでした。なんか入り口が違ったんですしょうかね?あの機能をやめちゃったとか。

社長:まあうちではとりあえず必要ないですから。

開発:値段とデザインで選んだら、結果的に日本の3社になりました。

基盤:コンピュータの部品とか、海外製が嘘みたいに安いですけどね。食器なんて原価は大したことないと思うのですが、センスの壁みたいなのがあるんでしょうか。

開発:食洗機っていうのはどうなんでしょうね?

社長:洗い物作業自体というより、腰が痛くなるのが問題だと思いますから、それの対策としては良いかもですね。

開発:昔、洗い物を一ヶ月くらい放置してたら、下積みになってた皿からメロン種がぞぞぞーっと芽を出して、さわったらさわさわっとして気持ちよかったことがあります。

基盤:メロン版のかいわれですね。食べてみましたか?

開発:気が回りませんでした。

社長:大根なんかも幼少のみぎりから大根らしさがありますからね。メロンの香りがするのかもしれません。

基盤:子供のころはただのきゅーちゃんかもしれないですが。

開発:ところでこのレトルトカレー、封を切らずにレンジにかけろと。蒸気を逃す弁がついてるんですね。

基盤:温まってから開けると中身が残らなくて無駄がないですね。

社長:まあ問題は味ですが… はふはふ… 合格です。

開発:ハフハフ。これって、醤油のパックの密閉口以来のヒットだと思います。

基盤:もぐもぐ。まあ有効性のインパクト的にはあれにまさる発明はなかなか無いと思いますが。

社長:醤油ってすぐに酸化して悲惨でしたからね。あれは醤油の歴史上みんながそういうものだと諦めていた問題を解決してしまった素晴らしい技術だと思います。もぐもぐ。

開発:技術が素晴らしいというか、今なら技術で安価に解決できる問題だと思い得たという点ですね。ごちそうさまでした。

社長:まあ、あれをあの形で店に出したらかっこ悪いから、体裁を気にする店では使わない。なので、高い店のほうが逆に醤油がまずかったりするわけです。

開発:次のフェーズは、見た目にカッコ良くて、使い捨てではない醤油さしの実現技術かと思います。

基盤:使い捨てでも大したことない洗剤の容器とかでも詰め替えパックが普通なので、そういう形でも抵抗感は少ないですね。

社長:小さく軽く作らないといけないのが技術的な困難かと思います。

社長:それで、思い出したのですが、そろそろ鍋の季節ですね。

開発:白菜が安くなると良いですが。

社長:そろそろ吉田拓郎も飽きたので、昔 iPhone にためたのを色々つくったプレイリストで聴きたいです。iMacで操作して、iMacのスピーカーで聴きたい。

基盤:iPhoneをiMacに接続… こういう事に。

開発:他のライブラリってなんですかね?

社長:自分で買ったCDとiTunesから買った曲しか入ってないですが、それも削除されちゃうて意味ですかね。

基盤:そういう事みたいですね。「このiPhoneをこのコンピュータと同期すると、昔のMacBookAir上の別のライブラリからのメディアは置き換えられます。iPhoneは一度に1台のコンピュータとしか同期できません」ときました。

開発:「置き換えられる」って「削除される」って意味ですよね。

社長:別に同期したいわけでなくて、単にiMacをiPhoneのリモコンにできれば良いのですが。いや、オーディオの出力先としても使いたいですが… ん?まてよ、昔のMacBookAirはキーボードがバーボン漬けになって死んでますが、他は生きてるんです。あれを復活させてはどうでしょう。画面共有で制御すれば十分かと。

基盤:オーディオは画面共有でiMacには持ってこれないような気もしますが。

開発:持ってこれたとしても、通信が混んでると雑音が入るとか悲しいですね。

社長:引き続き検討しましょう。

そうこうしているうちに、もう終業時間が近づいてきたようです。

認証情報のJavaScirptでの読み出し

開発:今日はまだ、パスワード送信テスト用のHTMLを数行書いただけですw

社長:ところで思ったのですが、ここで考えているユーザ名やパスワードって、HTTPで送られるbasicとかdigestのauthorizationじゃなくて、あくまでHTMLの中のform dataかURLの中の等価なそれの事じゃないかって。

開発:そう言えば。HTMLのコンテクストの中で語られてますしね。まあ、ブラウザに記憶する認証情報として、その2つを区別しているのかどうかわかりませんが。

開発:まあ調べるのも手間がかかりますから、実際に動かして確かめます。本日の GShell work product はこの6行です。で、これをブラウザで表示。

開発:あー、これですね昨日うざいと思ったのは。

開発:あ、でもパスワードの入力ではちょっと期待を持たせるような何かが。

開発:パスワーを入れてsubmit。

基盤:「開示」って、本人以外に教えちゃう事ですよね?

開発:保存する。で、もう一度フォームを見に行く…

社長:自動で fill はされてないですね。

開発:たぶん、”on” では指示が弱いのではないかと。ともかく「aaa このサイトから」をクリッ。

開発:ユーザのすべき操作としては、どうでもいい認証以外でなければ、このほうが良いかもですね。

開発:で、問題はこのUNとPWをevalで読めるかですが。

社長:問題なしですね。

開発:あれー、Firefox は autocomplete=current-password をサポートしてないんですかね。MDNを見る… おっと、こういうおすすめがポップアップされました。

社長:それをビューティフルワールドって言いますかね。

開発:きちんとしてる事が好きな人でしょうね。まあそういうウェブ開発者もいるでしょう。

社長:Aですからね。

基盤:でも内容は、まさにうちが欲しがってる情報の総まとめみたいな感じですね。

開発:autocompleteは、トラブルメーカーの第4位にランクインしています。

社長:しかしこのPDF、著者も日付もページ番号もないって、どうしたものですかね?

開発:まあ、著者は A Web Developer じゃないですかね。URLは以下。

https://mdn-web-dna.s3-us-west-2.amazonaws.com/MDN-Browser-Compatibility-Report-2020.pdf

基盤:まさかこれ、ライトセールですかね?

開発:別に困窮はしてないんじゃないかと思いますけどね。Wikipediaと違って。

開発:それはともかく、「保存されたログイン情報を表示」をクリックして、Firefoxのパスワードマネージャはあんかなかわかりやすく出来ていることもわかりました。

基盤:さすがに「開示」では無い模様ですが。

開発:この非表示のまま、通常のコピーで、隠されたテキストがクリップできる一般的な規約があると良いと思うんですけどね。BlinderTextとしても。

社長:まとめると、認証情報というかformのフィールドのfillには色々規約があるけれど、いったんfillされたら単にエレメントの値だと言う事ですね。

開発:だと思います。まあ、formの中、inputでなくても、autocomplete=password は使えるはずなんですけどね。 textareaでも。でないと、普通にtextareaであるBlinderText的にはちょっと残念なことです。

社長:で核心は、任意のサイトのログイン情報を召喚できるかという点ですが。

開発:もし action に他のサイトを書いて呼び出せたら可能ですね。

基盤:恐ろしすぎる。

開発:まあダメでしょうけど。何か特別の確認手順を経れば出来る可能性はなきにしもあらずです。

社長:やってみましょう。

開発:実験にしても恐ろしいので… まああのタコルータで試しましょう。認証画面のソースを見る… フィールド名は user と pass ですね。ではこれで、ぷちっ!

基盤:処理中ってなんですかね?

開発:例によってリブート準備中とか。

社長:どうやって自分じゃないサイトから飛ばされて来たかを識別してるかですね。

開発:Referer か Cookieですかね。

基盤:でもなりすましサイトから飛ばされて来たのを知ったら、すぐにそのアカウントを凍結すべきですよね。

社長:ただ、Firefoxのパスワードマネージャは、action が、そのサイト自体を向いてない時にも、ダマでautofillしてくれちゃうということですね。自分で他のサイトにつないで、それを送ってしまう。

開発:まあフィールドに値をフィルしてあげるところと、リクエストを送り出すところはアトミックではないですからね。その2つの操作の間の関連は規程されてないんじゃないですかね。

/* */ /* GShell-0.4.9 by SatoxITS
GShell version 0.4.9 // 2020-09-22 // SatoxITS

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

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

0 Fork Stop Unfold Digest Source */ /*
Statement

Fun to create a shell

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

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

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

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

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

Vi compatible command line editor

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

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

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

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


Username: Password:
*/ /* *///

GShell 0.4.8 − 自分認証方式

社長:ずいぶん早く目が覚めました。

開発:今日は間に合いましたね。後半に入ったところです。

基盤:みなさん朝から応援してますねw

社長:そろそろ終わるというのにいつもの人はどうしてるんでしょう?

開発:しかたが無い、緊急出動で。コピペしてぷちっ。

基盤:やはり便利な20000chが必要なのではw

社長:これは不便が文化を作るという面白い世界ですからね。顔文字もしかり。AAもしかり。

基盤:Vivaldiの広告ブロック機能、ばっちりですね。

開発:まあ、大抵は指定した class とか id とかのエレメントを表示しなきゃいいだけの話ですけどね。URLでもいいし。いつも見ているサイトなら簡単ですよね。client-side CSS が使えれば。

基盤:インスペクタで要素を調査して条件を決めてブロックできると良いですね。

社長:data URI とか JavaScript で埋め込んできたりして。

開発:悪いイメージを付けてほしくないですね。

基盤:URLでブロックするならプロキシでもやればよいのではないかと。実際にプロキシは無くても、このサイトはプロキシ経由にするという指示にして。フォールバックしなければ切れますよね。

開発:そもそも、このサイト、このURLにはアクセスしないっていう設定をブラウザでできれば良いんですよね。

社長:複数ブラウザ使ってますからねぇ。

開発:まあそのへんをGShell経由で共有するのもよいかと。

基盤:これは、新着投稿を読み上げソフトで読ませとくと良いかもですねw

社長:ぜひ吉田くんの声で。

基盤:このブログも読ませて確認して校正すると良いかも。

開発:終了しました。

社長:復調めでたし。

自分認証方式

社長:さて、JavaScript から Golang の GShellに接続する時の認証方法ですが。

基盤:これは今ブラウザ上でクリックした自分からの接続だということを確認する事ですね。

社長:故に我有りみたいな。

開発:ユーザによるインタラクティブな承認、パスワード方式、スクリプト側のURLによる承認、スクリプトの署名による承認、スクリプトに埋め込んだ証明書での承認、あたりかと。

社長:まずスクリプトを含むURLを閲覧した時に、Golangサーバ側でユーザが承認してクレデンシャルを生成してブラウザのローカルストレージに保存する、その後変更がなければそのクレデンシャルが有効、っていうような流れが良いかなと思います。

基盤:ユーザがインタラクティブにOKする時って何かのポップアップですよね。ブラウザだと起動するのが重いことがあって嫌なんですが。

開発:ターミナルとか、あるいは専用の簡易窓とかが良いかも知れないですね。これもGoサーバで開いてやれば良いと思います。

基盤:承認を要求したブラウザに、Go側からポップアップを作れると良いのでは無いかと思うのですが。

社長:Golang側で一時パスワードを生成して表示、クリップボードに保存、ブラウザでペーストするとOKみたいのが良いかも。

基盤:クリップボードは魅力ですが、時々妙に重いことがあるのがちょっとですね。

開発:IMEの辞書に一時パスワードを登録して、特別な読みを入れるとそれがペーストされるとかが良いかも。

基盤:でも、10秒以内に yes / no ボタンを押す、だけというのが一番かなと思います。

社長:reCAPTCHAでは無いですが、サーバで画像をPNGで生成してクリッカブルマップで「そこ」をクリックさせると良いかも知れません。

開発:それ、それで行きましょう。「そこ」を何にするかが面白そうです。

基盤:物理的にキーが押されたというイベントが偽造できないなら、それでも良いですよね。「右シフトキーを押してください」みたいなのを画像で返す。

開発:それも良いですね。モールスで打てとか。

社長:キーが押されたことを確認するのはGolang側のサーバですよね。

開発:JavaScriptクライアントとGolangサーバの両方で確認して、押された時刻をマイクロ秒単位で確認するとか。

基盤:確かセキュリティ上の制限で、JavaScriptの時刻の精度はミリ秒に抑えられてますね。

開発:いずれにしても、合意したクレデンシャルで継続的に承認というのは、Go-GShell/HTTP とクライアントとの間の仮想セッションですから、一般化するならCookieと同じようなものになるか、そもそもCookieを使えば良いということになりそうです。

社長:でも結局、普通にパスワードでいいんじゃないかって気もしますけどね。長いランダムなのを生成して、ブラウザとGShellサーバに記録させる。

開発:まあそうかも知れません…

Goからブラウザを制御する

開発:表示系の調整が未完ですが、だいたいできました。

社長:結局、ブラウザからGoを使うより、Goからブラウザを使うほうが面白いですね。

開発:まあそっちのほうが安全ということもあるんですが。

社長:長い間やりたいと思っていたことが、ようやく実現しました。

開発:やってみたらあっさりでしたけどね。WebSocketのおかげです。

社長:自前プロトコルはVIABUSを参考にして設計しましょう。

開発:まだ標準命に毒されてなかった頃の発想に戻るというわけですね。

社長:この、パスワードを入力する時にチェックディジットを表示する機能は、単体でも使えますね。

開発:まあ、そのようにBlinderTextというクラスを作りました。特にパスワード専用ということではなく、その場の入力を隠すだけでもなく、読めないようにテキストを表示しつつ編集したり、暗号化とかをその場でできるIMEとして拡充していきたいと思います。

基盤:バックエンドのGoでパスワード管理して、ブラウザのJavaScriptから参照できると便利ですよね。

開発:暗号化、署名も、JavaScriptでやると制約がかったるいので、Goに送ってやりたいと思います。

社長 :さっきから、Vivaldiの入力の応答が遅くてアタマがおかしくなりそうです。Google IMEでは変換して送信終了しているのに。実際にVivaldiでブロックエディタに出てくるのに何秒も掛かる…

基盤:さっきMacMiniをリブートした後に、何かの初期化がまだ終わってないんでしょうかね。CPU負荷は全然問題ないのですが。

社長:あれ?改善しました。良くなったり、悪くなったりです。

基盤:iMacのほうならサクサクですが。

開発:でもってシメはやはりこれで。

基盤:viで書いて保存したら即ブラウザで確認できるというのが良いですね。

社長:ブラウザの一つの窓でHTMLとして編集すると、他の窓でリアルタイムに表示されるというのも。しかも複数の種類のブラウザで同時に。

開発:もはや、原理的には簡単です。

— 2020-0921 SatoxITS

/* GShell-0.4.8 by SatoxITS
GShell version 0.4.8 // 2020-09-21 // SatoxITS

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

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

0 Fork Stop Unfold Digest Source */ /*
Statement

Fun to create a shell

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

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

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

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

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

Vi compatible command line editor

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

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

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

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


*/ /* *///

GShell 0.4.7 − JavaScript/Golang通信

開発:昨日の件ですが、仕掛けておいたログから原因が明らかになりました。解決法も。

社長:果報は寝て待てですね。

基盤:寝たり起きたりしてますけどね。

暴走事案解決

開発:最大の原因というか90%の負荷は、DOMの表示に関する処理で、要するに textarea の表示高さ、.scrollHeight の計算。これはたぶん、ほぼゼロにできます。残り10%は JavaScriptの処理によるもので、要するに textarea の .value に対する文字列の追加。ここは、べたの textarea でなく構造化するかどうかという判断がからみます。textarea が1MBの状態に対して、追加するのに約20msかかっています。

社長:textareaはテキストのブラウズに便利なので活用したいですね。RAM to RAM の転写速度が10GB/s くらいとすると、10MB/ms くらいは出そうな気がしますが。

開発:内部的にはtextareaの.valueのレベルでも、JavaScriptのstringのレベルでも構造化はされているのかなと思います。ブラウズだけなら1MB程度もサクサクスルスルで問題ないようです。ただ、.scrollHeightの計算だけが不自然に重い。固定ピッチフォントで画面幅が変わらないうちは、単純な差分の足し算とか、nowrapなら単純な掛け算で終わると思うのです。

社長:大きなバッファは動的に割り当てられる string じゃなくて、固定長のバイト列のバッファにしてしまうと良いのかもですね。

基盤:ターミナルのバッファとかは、1万行つまり10K行が普通ですね。1行50文字平均として、1MBで2万行はイケる計算です。

社長:まあ gsh.go.html も1万行がひとつのメドかなと思います。

基盤:すでに8000行を超えてますけどね。

開発:まあ現状、学習帳というか落書きノート状態ですから。整理すれば5000行くらいの内容かなと思います。

開発:あと、Safariでは「暴走」が起こらないように見えたのですが、勘違いで、textareaへのテキストの追加が累積していくとちゃんと重くなります。昨日、負荷原因として疑った、コンパイルされたコードの蓄積の問題は確認してませんが、これは今回の「暴走」にも見えた負荷の原因ではなかったようです。たただ、textareaへ追加と高さ再計算に関してSafariが軽いのは事実で、他のブラウザの2倍近く速いというか軽量です。独自のエンジンを持っているんだと思います。

GShell Golang部+JavaScript部の連携

開発:それで、GShellの応用例の一つとして、ホストマシンの負荷状況グラフをブラウザで描画したいと思います。

社長:会社設立直後にやってみようとしたことですが、ようやくたどり着きました。

開発:当時はこういう形で実現することになるとは想像しなかったですけどね。

開発:課題は、負荷情報の取得と、その描画の2つです。描画に関してはただDOMとJavaScriptを勉強するだけだと思います。問題は負荷情報の取得で、これをリアルタイムかつ軽量にやりたい。

社長:1秒に一度は解像度が低すぎますね。たとえば今回の暴走事件では、0.1秒単位では状況を観測したかった。そうすれば、もっと簡単に状況が理解できたはずです。

開発:で、問題は、技術的というよりセキュリティ上の制約から、JavaScript でホストコンピュータのナマ情報を知ることが出来ないという点にあります。

社長:うちの場合、ユーザが見て良いって言ってるんですけどね。自分で作ったJavaScriptで自分のマシンを見たいだけです。JavaScriptの作者認証とか実行権限をちゃんと管理すれば問題無いと思うのですが。せっかく素晴らしい処理系があるのに、もったいない話です。

基盤:いっそ、外部には全く繋げないというブラウザかモードを作って、そこではJavaScriptに何でもやらせるってすれば良いんじゃないでしょうかね。ローカルなGUIプログラムのプラットフォームとして。

開発:まあブラウザはウェブ用、ネットワークアクセス用、JavaScript は作者不詳で全く信用しないっていう前提でしょうからね。

社長:なので、我社的な解決方法は、ローカルプログラムとして実行しているGolang GSellをサーバにして、ブラウザのGShell JavaScript部にローカル環境へのアクセスを与えるというものです。

基盤:Golang部からブラウザをopenして、接続ポートとか認証コードを引数で教えると良さそうに思います。

開発:たぶん、そうなりますね。

基盤:ローカルストレージ10MBというのは辛いし、ローカルマシンだけでしか共有できないのも残念なので、Golang部からNFSサービスとかもしてあげたら良いかと思います。

開発:それもやりたいですね。

社長:Golang部のGShellはネットワーク上に分散してバックエンドの世界を作る、JavaScript部はブラウザ内からそれにアクセスすることでどこからでもそこにアクセスできる。ブラウザによらない共通の世界。怪しげなHTTPサーバとかクラウドに頼らない。

開発:そうしたいですね。

WebSocketでコンニチハ

開発:それでまず、何で通信するかですが、前から目を付けていた WebSocket で行きたいと思います。接続後はただのソケットみたいに自由に使えると期待されますが、とりあえずHTTP要求・応答でも構いません。

基盤:例を見ると、めっちゃ簡単そうですね。

社長:Go側では実装されているんでしょうか?

開発:websocket というパッケージがありますね。

基盤:例を見ると、めっちゃ簡単そうですね。

開発:なので、まず Hello応答をやりたいと思います。

基盤:というか、iMacのデスクトップがすごいことになってますが。

開発:昨日の問題を追跡した時の名残です。いずれ余裕ができたら整理しましょう。

開発:ミッションコントロールありがとう。

基盤:これ、仮想デスクトップごとに画面を変えたいですね。

社長:空の写真なら沢山あります。とりあえずこれかな。

開発:この旗がなんともいいですね。

社長:「処」だけでキレてるのが思わせぶりです。

基盤:うちのサイトの背景画像にしましょうか。

社長:そういえば昨日飲んだ帰りに、例の謎の定食屋のマスターにゥエルシァでばったり出会いました。ヒゲがーとか最近ご無沙汰とか言われちゃって。

基盤:最近また、飲みに行くのを忘れる日が多いです。

社長:一度どこかで区切りをつけますかね。

開発:我が社初の慰安旅行!

基盤:そうだ、奈良へ行こう!

社長:あ、こういうのもいいかもですね。のどかで。

基盤:白い点、飛行機飛んでますね。成田行きでしょうか。

経理:秋の空も楽しみですね。

社長:夜空を撮れるカメラが欲しいです。

開発:さて、デスクトップも整いましたので。

基盤:BGMは吉田拓郎のままでいいんでしょうかw

社長:ちょっといっぷくしましょう。

Golang版 GShell WebSocket

開発:まずGolang版のWebSocket、特に問題なく、テスト完了です。

開発:実は驚いたことに、すでにGolangの事をかなり忘れていて、最初は間違ってwriteでバッファ全体を送ってしまったのですが、それでも繰り返しの通信が出来ました。

社長:つまり、デフォルトでは無限オクテットストリームでは無いってことですかね。

基盤:デフォルトのポート番号は 9999 にするのでしょうか?

開発:仮決めですが、そうなる可能性が高いです。正式に登録されてなくて4桁で、昔からずっと使ってきた番号です。

JavaScript版 GShell WebSocket

開発:でいよいよお待ちかねの JavaScript版です。MDNから指されている仕様は、これ

基盤:更新日付がこの9/18ですね。とってもライブ感。

開発:まあ、WebSocketに関して変更があったのかどうかはわかりませんけど。

社長:JavaScriptの場合には、サーバにはなれないんでしょうか?

開発:少なくともこの仕様はクライアント側のみですね。Node.js でなら、サーバ側を作る例があちこちにありますが。ただ要するに WebSocketは、まず HTTPサーバで受信して、コネクションを Upgrade する形でできているようですから、HTTPサーバ経由でならサーバになれるのではないかと思われます。

基盤:その websocket 上の ping って最優先みたいですから、サーバの負荷の影響が少なくて、ネットワーク状況を知るのに使えると良いですね。

開発:MDNの例によると、とりあえずコードはこれだけで良いはずです。

開発:でもってGolang版GShellサーバを立ち上げておいて、ブラウザでGShellのHTMLをリロード。

基盤:パチパチパチ。

社長:めでたし。祝杯を挙げに行きましょう。とっても giant leap。

開発:応答は1ミリ秒程度のようです。

基盤:JavaScript から Golang の GShell IME を使えそうですね。

開発:問題なく。

基盤:syscall も全部呼べる。

開発:問題なく。

社長:GShellサーバがリモートにあってもOK。

開発:問題なく。

基盤:ローカルファイルも使い放題。

開発:問題なく。

基盤:とりあえず GJ Console から uptime コマンドを打ちたいです。top でも良いです。

開発:OSのコマンドを投げて、Goで実行した結果を返せばなんでもできますね。

社長:ようやく、Golang部とJavaScript部がつながりました。

開発:もっと早くにこれをやれば良かったですかね。

社長:それはどうですかね。ともかくめでたいので飲みに行きましょう。今日は謎の定食屋かな。

— 2020-0920 SatoxITS

社長:ただい…ま…く。苦しい…

経理:大丈夫ですか?

社長:食べすぎました…

基盤:謎の定食屋で食べ放題+飲み放題?

社長:あー…あそこは今日はマスターがやる気が無かったのか閉まってまして、さたぽんで。先日のツケもありましたし…

* * *

社長:ふぅ。少し落ち着きました。

開発:胃の中で食べたものの膨張が収まったですかね。

社長:それで、お店のWiFiでGoogleのニュースをブラウズしてたのですが、いくつか新情報。その1、ただいまUS女子ツアー開催中、明日というか日本時間で今日深夜スタートです。

開発:リーダーズボード開けときます。また寝ちゃう気もしますけど。

基盤:開催地はうちの支部のあるオレゴンですね。

社長:その2、藤井2冠は自作PCで将棋研究をしており、そのCPUは50万円。ライゼンスレッドリッパー3990Xとかいうもの。

基盤:それ、アマゾンで完成品が50万円で売ってますね。2.9GHzですが、64コアというのが凄いですかね。それでも消費電力280W程度の模様。

社長:あとは、ポケモンGOの記事が、Goの誤植に見える件とか。世間は4連休中であるらしいとか。

社長:ああそれで考えたんですが、これはやはりJavaScript側からGolang側に指示するより、Golang側が主導で、WebSocket経由でJavaScriptに描画指示する形のほうが自然なのかなと。性能的にも、セキュリティ的にも。接続は現状、JavaScript側からするという選択になるとしても。

開発:まあブラウザをWindowサーバとして使いたい、JavaScirpt をそのための処理系として使う、というのはもともとの考え方でした。でもこれは、どちらが主導するのが適切かは、アプリによると思いますが。

社長:まあそうでしょうね。いずれにしてもそれを念頭に置いて、明日以降を進めたいと思います。

/* */ /* GShell-0.4.7 by SatoxITS
GShell version 0.4.7 // 2020-09-20 // SatoxITS

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

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

0 Fork Stop Unfold Digest Source */ /*
Statement

Fun to create a shell

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

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

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

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

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

Vi compatible command line editor

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

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

*/ //
WebSocket // 2020-0920 created // WS // WS // INSTALL: go get golang.org/x/net/websocket // import "golang.org/x/net/websocket" const gshws_origin = "http://locahost:9999" const gshws_port = "localhost:9999" const gshws_path = "gshws" const gshws_url = "ws://"+gshws_port+"/"+gshws_path const GSHWS_MSGSIZE = (8*1024) func fmtstring(fmts string, params ...interface{})(string){ return fmt.Sprintf(fmts,params...) } func GSHWS_MARK(what string)(string){ now := time.Now() us := fmtstring("%06d",now.Nanosecond() / 1000) return "["+now.Format(time.Stamp)+"."+us+"] --WS-" + what + ": " } func gchk(what string,err error){ if( err != nil ){ panic(GSHWS_MARK(what)+err.Error()) } } func glog(what string, fmts string, params ...interface{}){ fmt.Print(GSHWS_MARK(what)) fmt.Printf(fmts+"\n",params...) } func serv1(ws *websocket.Conn) { var reqb = make([]byte,GSHWS_MSGSIZE) for { rn, rerr := ws.Read(reqb) if( rerr != nil || rn < 0 ){ glog("SQ",fmtstring("(%v,%v)",rn,rerr)) break } req := string(reqb[0:rn]) glog("SQ",fmtstring("(%v) %v",rn,req)) res := fmtstring("OK: %v",req) wn, werr := ws.Write([]byte(res)) gchk("SE",werr) glog("SR",fmtstring("(%v) %v",wn,string(res))) } glog("SF","WS response finish") } func ws_server(argv []string) { port := gshws_port glog("LS",fmtstring("listening at %v",gshws_url)) http.Handle("/"+gshws_path,websocket.Handler(serv1)) err := http.ListenAndServe(port,nil) gchk("LE",err) } func ws_client(argv []string) { glog("CS",fmtstring("connecting to %v",gshws_url)) ws, err := websocket.Dial(gshws_url,"",gshws_origin) gchk("C",err) var resb = make([]byte, GSHWS_MSGSIZE) for qi := 0; qi < 3; qi++ { req := fmtstring("Hello, GShell! (%v)",qi) wn, werr := ws.Write([]byte(req)) glog("QM",fmtstring("(%v) %v",wn,req)) gchk("QE",werr) rn, rerr := ws.Read(resb) gchk("RE",rerr) glog("RM",fmtstring("(%v) %v",rn,string(resb))) } glog("CF","WS request finish") } //
/* *///

editable で draggable なHTML

開発:最近知って驚いたHTMLの機能のひとつが contenteditable という属性です。これをつけるだけで、閲覧者が、自分の手元だけであるにしろ、ウェブページを書き換えることができます。

社長:いつのHTMLからなんでしょうかね?

開発:ちょっと試しにこの段落(この段落です)を contenteditable にしてみます。自由に編集できるようになっていると思います。

開発:これはちょっと面白すぎるので、ふつうの使い方的には「メモ欄」を用意するのかなと思います。

あなたのメモ欄:

社長:あとは、メモ欄のclassを自分のローカルストレージに保存したり検索したりする機能を、サイト提供のscriptか、ブラウザのプラグインとかで提供すれば良いわけですね。

開発:そうですね。テンプレート的なページを提供して、本文の書き換えができるようになっていると良いこともあると思います。あるいは、スプレッドシート的なもの、フォーム的なものが、より素直に作成できます。

基盤:テキストに限らないというところがミソですね。

社長:ローカルにしろ、オリジナルのページの「編集」「改ざん」を許可していると考えると、編集結果への作成者のデジタル署名が有用になるでしょうね。

基盤:そのページの「本来の」編集者には、編集結果をそのまま本式に反映させるという編集インターフェイスにもなりそうです。

社長:ユーザを認証して、何をeditableにするか決めるんでしょうね。

開発:もうひとつがdraggableという属性。これをつけるとなんでも引きずることができるようになります。

— 2020-0920 SatoxITS

GShell 0.4.6 − 暴走事案

基盤:緊急事態発生。

社長:なんじゃね朝っぱらから吉田くん。

開発:いえ、もうお昼です。

謎のギクシャク君

基盤:GShelのバナーが変だと思ったら、暴走している模様です。CPU食いまくり。

基盤:めっちゃギクシャク動いております。

社長:新しい芸風ですかね。

開発:iMacの増設メモリのせい?

基盤:いえ、これはMacMiniでの状況。でも、iMacも同様です。

基盤:メモリでパンパンなのはよく見ましたが、CPUが100%はりつきというのは初めて見ました。今回は、メモリはスカスカです。

基盤:ディスクもまったく静寂。

基盤:ロードアベレージは50前後で推移。

基盤:おかげで電力消費は100Wほど上積み状態。

経理:即座にリブートしましょう。

開発:あいや、こういうのはきっと、ブラウザでページをリロードすると直っちゃうんです。

社長:表示後すぐには発生しませんでしたから、再現にも時間がかかるでしょう。

基盤:ぱっと見、1秒毎にGShellのバナーが止まっている、というのはGJ Consoleの時刻表示のタイミングですねかね。

開発:だと思います。

社長:ガベッジコレクションか何かですかね?

基盤:同じページを表示しているのに、Safariは重くならないというのは面白いですね。

開発:とりあえずリロードしてみましょう。たぶん解消すると思います。リロードをぷちっ。

基盤:収まりましたね。

開発:もうひとつもリロードをぷちっ。ああ収まりますね。

社長:アイコン化して画面表示を止めるとどうなるでしょう?

開発:ではiMacのほうで… ぷちぷちぷちぷちぷちっ。

基盤:おもての処理が無くなったぶん、裏でアプリが食いたい放題にCPU食ってますね。

開発:メモリが肥大化はしてないですから、リークとかでは無いですね。そもそもスワップ祭りが起きてるわけでも無い。

謎のダブルクリック君

社長:ひとつ、2GBを超えてるChromeがありますが。

開発:ああ、これはうちの子じゃありません。

開発:この子もリロードしてみましょう。ぷちっ。あれ?リロードできないですね。じゃ閉じます。

社長:おお、とたんに平和な風景になりましたね。日常的なプロセス風景。

基盤:iMacのファンが唸らなくなりましたね。というか、電力消費が平常に戻りました。

社長:たたんで置いた GJ Console を開くとどうなりますかね。

開発:ぷちぷちぷちぷちぷちっ。おー、再びiMacがうなりはじめました。

開発:で、GShell のページはそのままで GJ Console だけ止めたら、バナーがスムーズに動くようになるので、GJ Console が問題であることは確実です。

基盤:Chrome のインスペクタでJavaScriptのメモリ使用状況が見れますね。たとえばさっきの、2GBに肥大してたページですが。

基盤:だいたい60kB/sで、単調に肥大しています。

開発:0.06MB x 60 x 60 x 24 … 一日で5GBを超える計算ですね。

基盤:もう一度リロードして様子を見ます…

基盤:最初はおとなしいんですが、徐々に来ます。Performanceとやらで10秒間観察…

開発:タイマーで処理し続けているということですね。

基盤:マウスイベントを出してみます。

開発:いったい何が溜まっていくんでしょう?

基盤:メモリのスナップショットを撮ってみます…

基盤:なんか配列に文字列をひたすら追加している感じですね。

社長:よくわからないけど、怖いな。

開発:まー、スクリプトを見れば意図はわかるんでしょうけど。

社長:もうしばらく観察してから、このダブルクリックはルータで塞ぎましょう。

GShellのメモリ使用状況観察

社長:で、うちのGShell君の場合は?

基盤:毎秒1.0kB、地道に太ってますね。

開発:え?何か太るような事してましたっけ?

基盤:スナップショットを比較…

社長:毎回コードを生成してるような雰囲気ですね。

開発:うーん。生成し直した跡は、昔のはもういらないんで。それは処理系が察してくれて、参照が無くなったら自動的にゴミ回収してくれるんだと思ってたんですが…

基盤:マウスイベントでも太りますね。ただ、徐々に減っていく。ゴミ回収が遅延して行われているような。

開発:なんですかね。わかりやすいところで string から…

開発:なるほど、コンソールログと、ソースを閲覧した時のテキストと、バナーにdata URLのpng を設定した時の文字列… しかしこれが重くなる原因とは思えないです。イベントリスナーを消さないで追加してるからですかね… ああでも、表示の更新は1秒ごとですが、タイマーイベントは200msごとにしてたような記憶が。textarea を広げてみる…

社長:コンソールログは別の窓にしたほうが良いような。センタリング表示はしないで。あと、メモ帳の窓がほしいですね。ローカルストレージに実体を持ってる。

開発:楽しい話の前に、この問題はクリアにして置きたいです。

自前リソース使用モニタ

開発:普通に、参照がなくなったらガベッジコレクションするってMDNには書いてますね。一方、手動で解放する手段はまだ無い模様。

開発:Chromium系にはメモリの使用量を知る手段がある模様。まずこれを使います。

漏洩箇所発見

開発:というか、漏れてた場所に思い当たったので直しました。ここです。

開発:これが200ミリ秒毎にコンパイルされてたわけです。コンパイルされたコードを解放する手段があるのかは不明。

社長:なぜこのような作りに?

開発:まあこんなこともできるかなと、面白かったからですが。要するに、このイベントに専用のハンドラーコードを作りたかった。

開発:まず刹那的な解決。というか、もとはこういう実装でした。

開発:これでリークがなくなりますが、複数のターゲットを作る時によろしくないです。なので、状態をDOMの中のターゲット自体に持たせるということも考えられます。と、思ったのですが、思えばタイマーを設定する時に関数名の他にも引数が渡せることを思い出しました。

社長:まあ、あたりまえっちゃあ当たり前ですね。

開発:あの時は、引数の順番がなんか変なので使うのに抵抗感があったような気がします。で、引数を使ってこうしました。

開発:これで、オブジェクトコードでメモリが膨らむ問題は解決です。重くなる問題と関係するかはまだ不明。でも、膨らんだコードをコンパイルするのに時間がかかっていたという可能性は大だと思います。それと、Safariで問題が発生しなかったのは、Safariでは使われなくなったコードを認識して捨てることができてるからではないだろうか、とか。

* * *

開発:その後、全く太らなくなったので、この暴走事案は解決したと思います。

社長:これ、スナップショットの時刻は表示出来ないんですかね。

経理:解決までに失われた総電力、約0.5kWh。

開発:部屋を暖める熱と消えましたね。

基盤:げ、なんか涼しいと思ったら室温26.5度になってます。急ぎエアコンをOFF。

経理:設定は28度なのに。

開発:温度センサーは室内機についてのでは。吸気の温度とか。上空は暑いのしょう。

* * *

基盤:新情報です。過去の版をいくつかを並べて動かしてみています。

基盤:バナーを動かすようになったのが 0.1.2からで、CPUを7%消費してます。タイマーで GJ Consoleを動かすようになったのが 0.4.2 から。JavaScriptの仮想マシンが使用するメモリは論理的には2MB程度のようですが、仮想マシンとしてははるかに大量のメモリを消費しています。ただ、増加し続けるかというとそうでもなく、遅延したゴミ収集が行われている様子が見えます。フラグメンテーションも溜まっていくのかなと思います。CPU時間はたしかに急激には増えていませんが、GJ Consoleにインタラクティブに変更を加えたpid14322では、より多くのCPUを消費しています。

開発:うーむ・・・

社長:でも今日はもう疲れましたから、しばらくコンピュータにまかせて、飲みに行きましょう。

— 2020-0919 SatoxITS



/* */ /* GShell-0.4.6 by SatoxITS
GShell version 0.4.6 // 2020-09-19 // SatoxITS

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

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

0 Fork Stop Unfold Digest Source */ /*
Statement

Fun to create a shell

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

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

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

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

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

Vi compatible command line editor

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

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