Programming」カテゴリーアーカイブ

os.forkpty

あけましておめでとうございます。
元旦といえば、そうプログラミングです。

ということで、Python で sshpass を再現の話。
os.forkpty というものがあるようだ。

python – Using os.forkpty() to create a pseudo-terminal to ssh to a remote server and communicate with it – Stack Overflow

これだよコレ、似たようなことを考える人っているもんだ。
がんばって sshpass と完全に同じコードにしなくてもよかった。
というか ptmx では上手くいかない、擬似端末ならこっちでもいいはず。

Python3 に書き換えしなきゃね。
色々問題はあるけどなんとかなったコードをとりあえず。

#!/usr/bin/env python3

import os, sys, signal, time

# var
HOSTNAME = 'username@hostname'
PASSWORD = '********'

# Ctrl+C
signal.signal(signal.SIGINT, signal.SIG_DFL)

pid, fd = os.forkpty()

def cmd(s):
    '''
        長い出力の場合分割される場合がある
        os.read はこの場合ループにすると値を戻さずフリーズする
        しかたがないので空打ちの場合は残りの読み込みにしている
    '''
    if s:
        os.write(fd, f'{s}\n'.encode('utf-8'))
        time.sleep(1)
        res = os.read(fd, 1024).decode()
        # 一行目を取り除いて表示
        sys.stdout.write(res[res.find('\n')+1 : ])
    else:
        res = os.read(fd, 1024).decode()
        sys.stdout.write(res)
    sys.stdout.flush()

if pid == 0:
    #os.setsid() # Error
    os.execvp('ssh',['ssh', HOSTNAME])
    # 親プロセスに切り替わるので以下は実行されない
    print('execvp Error!')

# レスポンスが遅いと表示されないけどログインは可能
output = os.read(fd, 1024)
sys.stdout.write(output.decode())
sys.stdout.flush()
# パスいワード
os.write(fd, f'{PASSWORD}\n'.encode('utf-8'))
# 只の時間稼ぎ
time.sleep(1)
res = os.read(fd, 1024).decode()
# 最初のプロンプトが表示できないので仮プロンプト
print(f'{res}First Command > ', end='')
# exit で終了
while True:
    s = input()
    if s == 'exit':
        break
    cmd(s)
os.write(fd, 'exit\n'.encode('utf-8'))
print('__DONE__')

よしこれで sshpass 不要でパスワード入力ができる。
しかし time.sleep を駆使するしかないのかな?
今のところこんな感じ。

調子こいて pysshpass みたいなものを作ろうと思ったけど…
os.forkpty では setsid できないし ptmx だと暴走するしetc…
我が Macbook Air の CPU ファン全開音なんて初めて聞いたよ。
ま、必要ないか。

しかし思っていたより Fedora と macOS では使える関数が違う。
os.fsync は macOS で問題ないけど Fedora は何をやってもエラー。
fcntl でノンブロッキングI/O は macOS ではエラー出まくり。
他の Linux や BSD では、なんて考えたくないな。

Gstreamer Aspect Rate

新規アプリも作らなきゃだけど既存アプリのメンテナンスも。

動画によっては原寸とアスペクト比が異なっている場合がある。
Totem や Celluloid はファイル中にある情報を見て適切なアスペクト比になる。
筆者の自作の奴も同じようにしたい。

ClutterGst.VideoResolution – Structures – ClutterGst 3.0

ためしに par_d と par_n の値を見てみた。
アスペクト比が原寸どおりな場合は常にどちらも 1 になる。
アスペクト比がおかしいものは 1 にならない。
なるほど、コレを使って単純計算できる。

ClutterGst 3.0 Get Media Width, Height | Paepoi Blog

ついでに昔書いた古い GstPad での書き換え方も判明。
Gst を直接使う人はコッチしかできないので併記しとく。

var Y901xWindow = GObject.registerClass({
    GTypeName: 'Y901xWindow'
}, class Y901xWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({application: app});
        //
        // etc...
        //
        this.player.connect('ready', (playbin)=> {
            this.player.set_playing(false);
            // Get Origin size
            let vsink = playbin.get_video_sink();
            // Only ClutterGst
            let frame = vsink.get_frame();
            let d = frame.resolution.par_d;
            let n = frame.resolution.par_n;
            this.src_width = frame.resolution.width * n / d;
            this.src_height = frame.resolution.height;
            /* or GstPad
            vsink.foreach_pad((sink, pad) => {
                let caps = pad.get_current_caps();
                let struct = caps.get_structure(0);
                //print(struct.to_string()); // check
                let [,w, h] = struct.get_fraction('pixel-aspect-ratio');
                this.src_width = struct.get_int('width')[1] * w / h;
                this.src_height = struct.get_int('height')[1];
            });
            */

でイケた。

後はスマホ動画では必須の回転情報を得たいんだけーが。
GstStructure に入っていると思ったけどドコにも無かった、残念。
Totem は再生開始と同時にグルッと回ってカッッチョイイんだよな。

ptmx

sftp アプリを PyObjC で作るって話。
やってます、上手くいかないだけです。
とりあえず今やっていること。

sshpass/main.c at master ? bauruine/sshpass ? GitHub

sshpass のソースってたったコレだけなんだよね。
これなら Python3 の標準モジュールだけで模写できそうだと考えた。
UNIX の中身の勉強にもなるし一石二鳥。

/dev/ptmx って何だ?

Ubuntu Manpage: ptmx, pts – 擬似端末のマスタとスレーブ

たしかに sudo や ssh は stdin やパイプからの入力はできない。
/dev/ptmx を利用すればなんとかなるってことね。

#!/usr/bin/env python3

import os, signal, fcntl

# Ctrl+C
signal.signal(signal.SIGINT, signal.SIG_DFL)

# ptmx
masterpt = os.open('/dev/ptmx', os.O_RDWR)

# not macOS
#fcntl.fcntl(masterpt, fcntl.F_SETFL, os.O_NONBLOCK)
#name = os.ttyname(masterpt)

childpid = os.fork()
if childpid == 0:
    os.setsid()
    #os.execvp('ssh', ['ssh', 'sasakima-nao@mba2.local'])
    os.execvp('python3', ['python3'])
    # Error
    print(f'{__file__}: Failed to run command')

#slavept = os.open(name, os.O_RDWR|os.O_NOCTTY)

os.close(masterpt)
#os.close(slavept)
print(f'{__file__}: EOL')

とりあえず fork して execvp することはできた。
呼び出し側のプロセスを終了して子プロセスに置き換える。
ほぼ os モジュールだけでなんとかなるのようで。

Man page of EXEC

起動すると EOL が表示され Python3 インタプリタが始まる。
Ctrl+D すると bash はもう破棄されているので gnome-terminal が終了する。
macOS では「プロセスが完了しました」と出る。

で、ssh のほうの現状。

こんなになってしまう。
sshpass.c をトレースしているつもりだけどまだ何か間違っているっぽい。
上手くいったらまた今度。

subprocess run Popen

そういえば最近このブログは subprocess.Popen を使っているけど。
subprocess.run で全部まかなえるようになったんじゃなかったっけ?
Popen を使う理由は何だろう。

subprocessでPythonからLinuxコマンド実行

あぁ戻り値を得ないならば非同期実行になるってことね。
しかしなんともなサンプルコードだ、多分 Windows なんだろうけど。
UNIX 系ならこんなに単純なサンプルコードにできる。

#!/usr/bin/env python3

import subprocess

subprocess.Popen(['sh', '-c', 'sleep 3; echo First'])
subprocess.Popen(['sh', '-c', 'echo Second'])

print('__done__')

__done__ が一番最初に表示されるね、なるほど。
ただし with as を使うと戻り値を使うのと同様になるので順番どおりになる。

#!/usr/bin/env python3

import subprocess

with subprocess.Popen(['sh', '-c', 'sleep 3; echo First']) as p:
    pass
with subprocess.Popen(['sh', '-c', 'echo Second']) as q:
    pass

print('__done__')

この特性を上手く利用すれば色々便利になりそう。

subprocess readline loop

sftp を subprocess で関連は色々詰まって全然進まない。
特に stdout.readline は検索すると同じ事で詰まっている人ばかり見つかるってどうよ。
そうです、readline でループすると値が戻らず止まってしまうんです。
俺だけじゃなかった!なんて安心してもしょーがないんですけど。

python – catching stdout in realtime from subprocess – Stack Overflow

上記で上手くいっている人と駄目な人がいるみたいだけーが。
exe ってことは Windows なら問題ないってことなのかも、知らんけど。

Pythonでサブプロセスと対話する – 西尾泰和のはてなダイアリー

上記でやっとなんとかなった。
サンプルコード、bash のエミュレーター。

#!/usr/bin/env python3

import sys, subprocess, os

with subprocess.Popen(['/bin/bash'], encoding='UTF-8', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as p:
    while True:
        try:
            s = input('test > ')
        except EOFError:
            # Ctrl+D
            break
        p.stdin.write(f'{s}\n')
        p.stdin.flush()
        if s == '':
            continue
        if s == 'exit':
            break
        r = os.read(p.stdout.fileno(), 1024)
        sys.stdout.write(r.decode())
        sys.stdout.flush()
        # not UNIX ?
        #for line in iter(p.stdout.readline, b''):
        #    print(line.rstrip())

print('__done__')

os.read はファイルオブジェクトに使わないでって書いているけど動くようだ。
os — 雑多なオペレーティングシステムインタフェース ? Python 3.8.0 ドキュメント
ls の出力が改行されてしまう理由は解らない。
subprocess ごときにこんなに苦戦するとは思わなかったぞ。