標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

DropBox が使っている代替データストリーム

会社PCにアクセスすると、なんか見知らぬフォルダーが出来てた。

f:id:zetamatta:20210104124941p:plain

電源入れっぱなしだったので、多分、他の人が UP したのはよいが、取り消したとか、そんな感じで出来上がったもんだろう。dropbox.com の方を見ても、別段サーバーには該当フォルダーは出来ていないようだ。

とはいえ、消してよいもんだか、ちょっと気になるので、DropBox のヘルプページを開いてみた。比較的よく検索されるトピックなのか、「DropBox マイナス…」とまでタイプすると、「DropBox マイナスマーク」まで Chrome が補完してくれた。

2番目のリンク先の『コマンドラインでファイルやフォルダを「無視」に設定する』というセクションを見ると、

Set-Content -Path 'C:\Users\yourname\Dropbox(Personal)\YourFileName.pdf' -Stream com.dropbox.ignored -Value 1

という PowerShell で無視と設定できるらしい。Set-Content って普通の組み込み関数だよな。-Stream とついているということは、代替データストリームとか何とかいう拡張属性のことかな。それなら別に PowerShell でなくもアクセスできたような気がする。

代替データストリームは dir /R で一覧が見られるようだ。

2018/10/11  17:41    <DIR>          scr (未同期アイテムの競合)
                                 83 scr (未同期アイテムの競合):com.dropbox.attributes:$DATA
                                 26 scr (未同期アイテムの競合):com.dropbox.attrs:$DATA
                                 17 scr (未同期アイテムの競合):com.dropbox.folder_policy:$DATA
                                  1 scr (未同期アイテムの競合):com.dropbox.ignored:$DATA

わお!思ったよりもたくさんあったわ。中身見られるかな?

$ type "scr (未同期アイテムの競合):com.dropbox.ignored:$DATA"
1

あ、はい(あっさりすぎて拍子抜けを隠せない)※1

この type は nyagos の内蔵 type なので、何か特別なことをしなくても「(ファイル名):com.dropbox.ignored:$DATA」というパスでオープンさえすればデータにアクセスできることを意味している。

ということは、あとは代替データストリームの「一覧」つまり dir /R と同等なことをどうにかして出せれば、ほぼ自由に読み書きができるということだ。FindFirst/Next 系の API を使えばよいのだろうか…

ググってみたら、FindFirstStream という API があって、個別のファイルに対して実行すればよいようだ。これ、100個ファイルがあったら、100回発行しなくちゃいけないということか。あまり調子に乗って使うべきものではなさそうだなぁ

まぁ、特別これを使ってなにをしようというプランがあるわけではないので、これくらいにしておくか…

※1 追記

CMD.EXE 内蔵の type ではダメっぽい

$ cmd /c type "scr (未同期アイテムの競合):com.dropbox.ignored:$DATA"
ファイル名、ディレクトリ名、またはボリューム ラベルの構文が間違っています。
exit status 1

ビルドタグは import 先にまで派生する

まぁ、当たり前の話ではあるんだけど、github.com/zetamatta/go-windows-dbg を改造するにあたって大丈夫か?とちょっと不安になったので、念のため検証。

サブパッケージ(bar)は release タグの有無で挙動が変わる。タグを指定するのはメインパッケージ(main=foo)となる。

github.com/zetamatta/bar/main.go

サブパッケージ。タグ release が設定されていない時だけ有効

// +build !release

package bar

func Run() {
    println("not release")
}

github.com/zetamatta/bar/main_release.go

サブパッケージ。タグ release が設定されている時だけ有効

// +build release

package bar

func Run() {
    println("release")
}

github.com/zetamatta/foo/main.go

メインパッケージ。ここのビルド時にタグを指定する

package main

import (
    "github.com/zetamatta/bar"
)

func main(){
    bar.Run()
}

実験

<~/go/src/github.com/zetamatta/foo>
$ go build && foo
not release
<:~/go/src/github.com/zetamatta/foo>
$ go build -tags=release && foo
release

よしよし

複数のビルドタグによる条件ビルドの構文

によると

  • AND はタグを , (カンマ)区切りでならべるか、次の行に +build 行を追加
  • OR はタグを空白区切りでならべる
  • 結合の強さは , > 空白 >改行

(例)

  • // +build !ndebug,windows
    ! defined(ndebug) && defined(windows) に相当

  • // +build !windows ndebug
    ! defined(windows) || defined(ndebug) に相当

なるほど!

Windows でポータブルな Makefile を書く(綺麗にとは言っていない)

nmake と GNU Make の非互換性が大きすぎるので、Windows でポータブルな Makefile を書くのは、簡単なものでないと無理だろうなぁと思ってた (ゆえに多くの場合は簡単なバッチファイルで用をたしていた)

が、「GNU make nmake」でググってみると…あるもんだなぁ

どうやら

  • nmake はコメント行の末尾の \ を認識しない ⇒ 次の行はコメントにならない
  • GNU Make はコメント行の末尾の \ を行継続文字として認識する ⇒ 次の行はコメントになる
  • nmake は !if ~ !else ~ !endif という条件文を認識する(GNU Make のは違う文法)

という違いを利用した技のようだ。これでマクロくらいは nmake と GNU Make で変えることが出来るようだ。ちょっと書いてみよう

# NMAKE code here \
!ifndef 0 # \
ECHO=echo #\
!else
# Make code here
ECHO=cmd /c echo
# \
!endif

all :
    $(ECHO) $(MAKE)

nmake は CMD.EXE をシェルとして呼び出すので、echo は内蔵コマンドとして呼べるが、GNU Make は sh を使おうとするので、echo.exe がパスにとっていなければ cmd.exe /c echo という形にしないといけない。実行結果は次のとおり:

$ nmake

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

        echo C:\Users\hymko\Share\bin\nmake.exe
C:\Users\hymko\Share\bin\nmake.exe
$ mingw32-make.exe
cmd /c echo C:/TDM-GCC-64/bin/mingw32-make
C:/TDM-GCC-64/bin/mingw32-make

お、おう…

非表示になっている CADのメニューを AutoLISP で強制的に表示する

メニューを menuload コマンドでロードしても、ロードはされるが表示されないという場合があって難儀した (どうも、最後にアンロードした時の表示状態がずっと記憶されているようだ)

過去の案件だと menuload ではなく、(vla-load (vla-get-menugroups (vlax-get-acad-object)) "メニュー名") なら表示されるという解決方法もあったのだが、今回はダメだった。

でも、このコマンドを実行すると、ActiveX のオブジェクトを示すIDっぽい番号がコマンドラインに表示される。これを手掛かりにオブジェクトをたどっていけば、メニューを無理やり表示させることもできるのでは?

CAD の Lisp で扱う ActiveX オブジェクトは (vlax-dump-object オブジェクト T) で仕様が表示できる。これを手掛かりに芋づる式に表示したいメニュー項目を表示状態にしてみよう。

(setq menugroups  (vla-get-menugroups (vlax-get-acad-object)))
(if (menugroup "メニュー名")
    (setq custom  (vlax-invoke-method menugroups "Item" "メニュー名"))
    (setq custom  (vla-load menugroups "メニューファイル"))
)
(setq toolbars   (vlax-get-property  custom "Toolbars")
      count      (vlax-get-property  toolbars "Count")
)
(while (> count 0)
    (setq count    (1- count)
          toolbar1 (vlax-invoke-method toolbars "Item" count))
    (vlax-put-property toolbar1 "Visible" T)
)

うまいこといきました。

VC++のDLL関数をGoから序数指定で呼び出す

WindowsAPI関数を呼ぶ場合は、DLLの名前と関数名があれば、"syscall"パッケージ、もしくは "golang.org/x/sys/windows" パッケージの NewLazyDLL・(*LazyDLL)NewProc を使えばよかった (参考:Big Sky :: golang で型付きで DLL を呼び出す方法)。

が、VC++ で作られたサードパーティDLLの場合、関数名ではなく、序数(Ordinal)で関数がエクスポートされている場合がある。この場合はどうすればよいのだろうか。VB-Net みたいに名前に "#1009" とか "@1009" としてもダメなようだ。

VC++ 側で序数を定義しているのは .def というファイルだ。

これの英語版のページを見たところ、序数を表す言葉は Ordinal という単語のようだ。これでググれるぜ

以下、うろうろした履歴(別に見なくてもよい)

  1. x/sys/windows: a way to load dll functions by ordinal (not only by name) · Issue #16507 · golang/go
    • 自分と同じ目的で issue を立てた人がいたようだ。syscall は Fix されているので、golang.org/x/windows に目的の関数を足されているようなことが書いてある
  2. windows: add GetProcAddressByOrdinal (Ib5fba756) · Gerrit Code Review
    • その足したコードのレビューっぽい
  3. …/syscall_windows.go · Gerrit Code Review
    • レビューされてるコード。最悪これを真似すればよいが、これ結果としてマージされているのでは?
  4. golang.org/x/sys/windows の GoDocの #GetProcAddressByOrdinal
    • されとる。でも、これ素の uintptr 使ってるから使いにくい。ここまでやってるなら、ラップしているクラスあるのでは?
  5. golang.org/x/sys/windows の GoDoc の#DLL.FindProcByOrdinal
    • あるじゃないか。なぜ、気づかなかったのだ。お前はいつもそうだ。誰もお前を愛さない

涙をふいて結論をいうと (*DLL)FindProcByOrdinal という関数を使えばよいようだ。

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "unsafe"

    "golang.org/x/sys/windows"
)

func mains() error {
    scriptPath := filepath.Join(os.Getenv("TEMP"), "test.scr")
    fd, err := os.Create(scriptPath)
    if err != nil {
        return err
    }
    fmt.Fprint(fd, "(alert \"TEST!\")\r\n")
    fmt.Fprint(fd, "quit\r\n")
    fmt.Fprint(fd, "y\r\n")
    fd.Close()

    os.Chdir(`~DLLのあるフォルダー~`)

    dll, err := windows.LoadDLL("DLLのファイル名")
    if err != nil {
        return err
    }
    proc, err := dll.FindProcByOrdinal(1009)
    if err != nil {
        return err
    }
    _scriptPath, err := windows.BytePtrFromString(scriptPath)
    if err != nil {
        return err
    }
    _profile, err := windows.BytePtrFromString("parameter")
    if err != nil {
        return err
    }
    proc.Call(
        uintptr(unsafe.Pointer(_scriptPath)),
        uintptr(unsafe.Pointer(_profile)),
        1)
    return nil
}

func main() {
    if err := mains(); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }
}

パラメータが ANSI文字列なので、UTF16PtrFromString ではなく、BytePtrFromString を使っているのに注意。

文字コード的に日本語が通らないが、大丈夫か?」
「大丈夫だ。(テスト用なので)問題ない」

以上

追記

関数名ではなく序数で DLL を参照するメリットとは?

参照する側にはあまりメリットはありません。

DLLの方がdefファイル内でNONAMEオプションを指定していて、関数名をエクスポートテーブルに載せていない場合があるんです(つまり関数名で参照したくとも、できない)。おそらくは、関数名を省くことでテーブルサイズを削減でき、外部から解析されにくくもなるのだろうと考えられます。

参考: EXPORTS | Microsoft Docs

分岐以降の master(main) のコミットを revert して、別に伸びてしまった第二ブランチのコミットを master(main) に取り込む

master とは別に second というブランチを作っていて、そちらが伸びてしまった。master もブランチ時点から少し commit があるが、それらの commit は廃止して、second の commit を全部 master に取り込みたい。

特に難しくなく、普通にいけた。

懸念していたのが、git rebert で打ち消したコミットで git rebase で conflict エラーになることが、幸い、そういうのなかった。 git が打ち消されたコミットをちゃんと認識して無視するようになっていたのか、たまたまうまくいったのかは不明。

git log --oneline second..master  | goawk "{ print 'git revert ' $1 }" | nyagos
git checkout second
git rebase master
git push origin second:second
git checkout master
git merge second
git branch -d second
  • second にはあるが、master にはないコミットを列挙するには git log log second..master でよい。
  • --oneline オプションで「コミットハッシュ ログの1行目だけ」という結果を出力する。
  • 文字列の加工といえば awk だが、Windows には Go製の awk で goawk というものがある
    • ダブルクォーテーションのかわりにシングルクォーテーションが使えるので、Windows環境にやさしい
    • 文字コードは UTF8 扱いだが、今回扱うのはコミット名のハッシュなので問題なし
  • goawk の system 関数は sh を呼び出そうとするので、かわりに標準出力に出して、nyagos の標準入力に食わせたが、問題なく動作

以上

「どこから得たかわからない画像」への回答例

自分の回答

#include <stdio.h>

int main(int argc,char **argv)
{
    if( argc < -1 ){
        return argv[(-argc)-1][0]-'0'+main(argc+1,argv);
    }else if( argc < 2 ){
        return 0;
    }
    printf( "%d\n",main(-argc,argv));
}
$ a 1 2 3 4 5 6 7 8
36

一回、引用RTで回答したんだけど、ネタバレよくないと思って消してしまった。でも、キャッシュには残ってるらしく、引用RT一覧を表示すると出てきてしまう。うーん、しまったね…