AWS Glueのデータカタログでの日本語カラムの話
少しだけの実験をしている。
Hive互換な名前ってなんだ?
Hive互換な名前にするには、
英数小文字とアンダースコアのみらしい。
それはGlue側のマニュアルの話。
Hiveのマニュアルも調べておく。
LanguageManual DDL - Apache Hive - Apache Software Foundation
Hive 0.12では確かにそうだ。
Hive 0.13以降はもっと緩い。
カラム名にユニコードを使えるようになった、ただしバックチックで囲む必要がある。
文章的にはテーブル名に言及してないが、バックチックで使えるはずだ。
Hive 1.2.0以降ではドットとコロンが使えなくなった。
GlueのデータカタログがどのバージョンのHiveに相当するだろうか。
Glueの元になってるEMRとHiveのバージョン関係はリリースノートから読み取れる。
GlueとEMRの関係は恐らくSparkバージョンが同じになるだろう。
Glue1.0と2.0はSpark 2.4.3だから、EMR5.25.0のHive2.3.5と考えるのが自然。
そのため、一部制限はあるものの、漢字は十分に使えると考えられる。
EMR | Spark | Hive |
6.0.0 | 2.4.5 | 3.1.2 |
5.30.0 | 2.4.4 | 2.3.6 |
5.29.0 | 2.4.4 | 2.3.6 |
5.28.1 | 2.4.4 | 2.3.6 |
5.28.0 | 2.4.4 | 2.3.6 |
5.27.0 | 2.4.4 | |
5.26.0 | ||
5.25.0 | 2.4.3 | 2.3.5 |
5.24.1 | ||
5.24.0 | 2.4.2 | |
5.23.0 | ||
5.22.0 | ||
5.21.0 | ||
5.20.0 | 2.4.0 | 2.3.4 |
5.19.0 | ||
5.18.0 | 2.3.2 | |
5.17.1 | ||
5.17.0 | ||
5.16.0 | 2.3.1 |
Glue2.0において、Spark SQLを書いてみたとき、
漢字テーブル名と漢字カラム名は、バックチックで囲んで処理できた。
ドットとコロンはまだ試してない。
S3の漢字フォルダ内に、漢字ヘッダーを含めたcsvを配置して、クローラにテーブルを作らせたら、漢字が文字数分のアンダースコアになったテーブル名になった。漢字カラムは問題ない。
AWSコンソールからテーブルを作る場合、漢字のテーブル名は使える。そのため、クローラによる既存テーブル更新の動かし方は出来た。
Athenaから漢字テーブル名、漢字カラム名を使う場合は、ダブルクォートで囲めば動いた。
リモートワークでの低価格ダイエット方法
寄る年波には勝てぬ。
また太ってきたのでダイエットを開始したのは
例年のようにGWあたりでした。
現在も継続中。
今回は財布に優しいダイエットを目指す。
めちゃくちゃズボラで誰にでも出来る。
コロナの影響でリモートワークになり、昼夜決まった食事が取れるのはダイエットに追い風です。夕飯も決まった時間に食べれる。
体重の経過
GW頃 81kg
7月中旬時(ダイエット方法確定後) 78kg
9月下旬(本ブログ執筆時) 69kg
最近は週に1kg痩せる感じ。
今回のダイエットの特徴
- 炭水化物中心
- カロリー制限
- 財布に優しい低価格志向
- 海産物
- 運動しない
主に利用したスーパー
利用する器具
- 炊飯器
- 電子レンジ
- 耐熱容器
- パスタ鍋
- 測れるパスタ容器
- レトルト絞りカッター
- 丼や皿
- ハサミ
ダイエットする目的は?
血液検査のALT(GPT)の項目がいつも異常値、要治療のレベル。
痩せたときは正常値になったことがある。
アルコールはもう飲んでないし、痩せれば下がるのでウイルス性ではなかろう。
非アルコール性脂肪肝(NAFL)か非アルコール性脂肪肝炎(NASH)だろうと個人的に解釈している。
体重を減らすのが主目的というのではなく、食習慣を見直すことで肝機能の数値を改善させると、同時に体重も減るよねって流れ。
食事は昼夜の2食派です。
カレーとパスタをメインに食べる
財布に優しくするには、米とパスタは欠かせません。
カレーはレトルトカレーを利用する。
1食100円くらいでレトルトカレーはある。
お米は好きなのを使えばいい。
米は1食で0.6〜0.7合くらいにする。
米1合のカロリーは534kcalのため、0.6〜0.7合は320〜373kcalあたりです。グラムでいうと100g前後になればいいです。
パスタは1食100gで約365kcal(これは購入した商品の栄養表示)
パスタソースも1食100円くらいのものがある。
だいたい決まった量を食べるのは経過観察も含めて意味がある。
量も決めてるので、緩めの糖質制限(1日糖質130g以下)に近い状態にある。
ビタミン類はサプリメントで補充
マルチミネラル、マルチビタミンのやつを薬局で購入。
栄養を食材によって取ろうとすると、経済的ではなくなるので、マルチビタミンに頼る。
大塚製薬 ネイチャーメイド マルチビタミン&ミネラル 200粒
- 発売日: 2007/04/23
- メディア: ヘルスケア&ケア用品
目安カロリー1400kcal
基本1200kcal+アルファ
カレーやパスタで昼夜2食を食べると、だいたい1200kcalになる。そこに煮干しなどを食べることで1400kcalくらいまでにする。
朝食を取るときはファミマでスムージー購入して追加で約200kcal。
活動時間1時間に対して100kcal程度取るなら問題ない。
カレーのこだわり
レトルトカレーのカロリーはだいたい100〜200kcalあたりです。
ご飯と合わせて600kcal以下になる。
これを食べなくちゃいけないというのはないです。自由に様々なカレーを愉しみます。
レトルトカレーの温め時間は600wで1分〜1分半と短く、昼休み向きです。
最安価格は、まいばすけっとで購入可能なTOPVALUベストプライス(PB)の「程よくスパイスをきかせた カレー 中辛」の54円(税抜)、200gです。
残念ながらコクはないですが、コスパ最強のレトルトカレーのひとつです。
程よくスパイスをきかせた カレー 中辛-イオンのプライベートブランド TOPVALU(トップバリュ) - イオンのプライベートブランド TOPVALU(トップバリュ)
まいばすけっとでは他にもレトルトカレーを扱っており、78円のものもあります。
まだダイエット始めたてのお腹の空く頃は、ローソンストア100で購入可能なバリューライン(PB)の「特盛カレー 大辛」、100円(税抜)の300gです。
特盛カレー大辛|ローソンストア100~生鮮・100円・くらし支えるストア~
ローソンストア100以外で買うなら、類似の300g系統で、Hachiの「メガ盛りカレー」約100円(税抜)の300gです。
価格的にはネット購入するより、近くのお店を探した方が良いです。キャンドゥやドンキあたりで売ってるはずです。
ローソンストア100は、けっこう100円のレトルトカレーが充実していて良いです。
もっと辛いのを求めるなら、まいばすけっとでLEE20倍が売ってることがあります。30倍は稀にファミマに置いてることがありました。
江崎グリコ ビーフカレーLEE辛さ×20倍 180g×10個
- メディア: 食品&飲料
いままで食べたなかで旨いと思った掘り出し物のは、TOPVALUセレクトの「タスマニアビーフカレー 中辛」です。値段は258円。
神田カレーグランプリと同レベルか若干上回る程度に旨いです。
TOPVALUセレクトは、最上質のものを揃えているので、確かに旨いと感じた次第です。タスマニアビーフカレーはロングセラー商品らしい、知らなかったなぁ。
パウチのカッター
キャンドゥで購入した「レトルト絞り&カッター」です。
便利 レトルト しぼり&カッター 最後まで絞れる スッと切れる 冷蔵庫に貼れるマグネット付き 便利
- メディア: ホーム&キッチン
レトルト絞りは噛み合わせがあまり良くないですが、カッターは重宝します。パウチのまま温めが必要なときだけレトルト絞りの部分を使ってました。
基本は、パウチをカッターで切って耐熱容器に移してレンチンです。
パウチの切り口を手で切って微妙に残ると、再度切ろうとしてカレーの飛び跳ねに繋がるので、この手のカッターやハサミを使う方が安全。
測れるパスタ容器
東急ハンズで購入しましたが、ロフトの方が安かったです。ロフトなら619円。
イノマタ化学の「なるほどパスタ」です。
簡単に100g相当が測れるので重宝します。
目分量というか手分量で測ってしまい、やけに量が少ない日ができてしまうのを防げます。
パスタのこだわり
とにかく安いので良いと選んだ結果、
ローソンストア100で購入可能なMARRE(マルレ)の「スパゲッティ 1.9mm 500g」の100円(税抜)です。
難点はゆで時間が11分と長い点です。
昼休みには11分は長いので昼パスタはやめて夜パスタに変更しました。
トルコ産のデュラム・セモリナ(デュラム小麦の粗挽き)。
100gあたり365kcalあります。
500gで100円はかなり安いです、これに勝てるのは業務スーパーくらいです。
1.9mmは太めです、普通のスパゲッティは1.6mmのものが多く流通してるはずです。
太いので食べ応えがある。
残念なのは、この商品は、ローソンストア100以外で見たことがない点かな。
元々はトルコ産加工食品の輸入をしていた株式会社バハールが自社ブランドMARREを立ち上げたのち、改名して株式会社マルレになってるようです。
パスタの輸入は、イタリア産の次に多いのがトルコ産らしいです。
3月パスタ供給量16%増に 国内生産量は9カ月続伸、輸入も26%増に(食品新聞) - Yahoo!ニュース
パスタソースのこだわり
最終的にメインにしたのは、まいばすけっとで売られているS&Bの「まぜるだけのスパゲッティソース 生風味からし明太子」
S&B 生風味スパゲッティソース からし明太子 53.4g×10個
- メディア: 食品&飲料
初めの頃は、ローソンストア100で売られてるもので、2人前のものでした。1人前を探し求めた結果、S&Bのものに落ち着きました。
トマトソース系を使ってた頃もありますが、洗い物が面倒になるので辞めました。
パスタに追加する素材
西友で購入可能な「みなさまのお墨付き 焼きのり」です。
みなさまのお墨付き 焼のり|楽天西友ネットスーパー
パスタソースに元から海苔は付いてますが、海苔は食物繊維が多く、他にも効果があるので大目にします。
他にも、これまた西友で購入可能な「みなさまのお墨付き 徳用 かつおパック」です。
みなさまのお墨付き 徳用 かつおパック|楽天西友ネットスーパー
かつお入れたらどんなもんでも旨くなる。
かつおを入れると少しパサパサになるので、パスタの湯切りは軽めにした方が良い。
煮干しのこだわり
西友で購入可能な「みなさまのお墨付き 国産にぼし 塩無添加 150g」にしました。
西友 - みなさまのお墨付き - 国産にぼし 塩無添加 150g | SEIYU
塩無添加にしましょう。
3〜5日で1袋を食べる調子。
煮干し100gあたり70gがたんぱく質です。
小腹が空いたり、口元が寂しくなったら、ボリボリ食べましょう。
世の中には「出汁ダイエット」なんてのもありますが、面倒臭いので、煮干しボリボリ食べます。
サラダチキンの方が低カロリーですが、煮干しは汁気がないし、食べたい量を簡単に調整可能です。
そして、カルシウムに加えて、EPAとDHAが豊富なのも良い点です。EPAとDHAには、血液をサラサラにして、中性脂肪やコレステロールを下げる効果があると言われてます。
魚って、肉に比べて高いし、なかなか手が出ないんだけど、煮干しなら躊躇なくいける。
他にもDHAには抗ガン作用があると、認知症の予防に良いとか言われてて、魚をもっと食べれば日本救われるんじゃないかと思うくらい魚は大事。
その他の食品
乳酸飲料を少々。
ガゼリが良いという評価ではなく、乳酸飲料で手頃な量が欲しかっただけです。紙パックのピルクルは容器のサイズ的に1日で全部飲むのは勧められないもので、カロリーオーバーになる。
黒酢飲料を少々。
ミツカンの「ちょっと黒酢 りんご味」は、表記上カロリーは0になってます。100g中に5kcal以下なので。本当は3kcalあります。スッキリした味が好きというだけで、
黒酢のダイエット効果を期待してるわけではありません。
夕食後、しばらくして飲んでます。
運動しない
ジョギング、ランニングの類は今はしてません。
しかし、タバコを外で吸う習慣のせいで、近所の灰皿まで歩くことにより、日に5キロは歩いてます。
運動してるのではなく、タバコ吸いに行ってるだけなのでノーカウントです。
膝を痛めない程度には痩せたのでジョギングしてもいいけれど、このような低カロリー戦略のときは、ただ筋肉が溶けるだけなので、運動するならば、そのためのカロリーを摂取して、消費カロリーとトントンになるようにするでしょう。
Glueをローカルで動かすための考察
まだ未解決なのでアイデアを考え中。
実現できると分かってること
glueContext.create_dynamic_frame.from_optionsを使うケース
AWS Glueをローカル環境で実行してみた | Developers.IO
あと、こちらも。
AWS Glueの単体テスト環境の構築手順 | フューチャー技術ブログ
s3だけならば、localstack以外にもminioを使う選択肢はあるかもしれない。
やりたいが方法が見つかってないこと
glueContext.create_dynamic_frame.from_catalogを使うケース
なぜできてないか
localstackにはGlueのAPIはない。
Support AWS Glue · Issue #1446 · localstack/localstack · GitHub
型や場所に関する情報がデータカタログにあるが、それがないので何もしないと失敗するだろう。
考察1
sparkのenableHiveSupportを有効にする。
根拠はこれ。
Spark SQL ジョブの AWS Glue データカタログ サポート - AWS Glue
この根拠どおりなら、sparkがHiveを意識するように構成すれば良いことになる。
考察2
sparkがHiveを意識するとき、javax.jdo関連のプロパティを構成するようにする。spark-warehouseはまだ咀嚼できてない。
Hive Metastore · The Internals of Spark SQL
あとこれも。
Apache Hive メタストアを Amazon EMR に移行してデプロイする | Amazon Web Services ブログ
AWSの例の場合のうち、RDSを使う状態になれば良いと思っている。ローカルでやるにはMySQLかpostgresqlを用意すれば良いだろう。
考察3
分けてもいいけど、Hadoop+Hive+MySQLを入れたコンテナを用意する。
S3用のlocalstackのコンテナを用意する。
Spark用のコンテナを用意する。
HiveもSparkもjdoの設定で、MySQLを見るように構成する。
Hiveのcreate databaseとcreate tableでMySQLに、Glueのカタログテーブルに相当するものを作成する。
考察4
HiveのテーブルとGlueのテーブル。
LanguageManual DDL - Apache Hive - Apache Software Foundation
CREATE TABLE - Amazon Athena
だいたいcreate tableの構文は同じはず。
考察5
やばそうなのは、カタログのアップデートをどうするかだな。enableUpdateCatalogは厳しい。単体試験なら困らないかな?いや、困るな。試験データのパーティションを作って消してを繰り返す必要がある。単体試験以降もローカルでやりたいときは、複数のSpark Jobを先行後続で動かしたときに、Hive Metastoreが更新されてないだろう。
考察6
リモートのmetastoreだから、thriftもいるかも。Hiveのhive.metastore.urisにthriftのホストとポートが必要そう。Spark側からはいらないかも。
道のりは遠いな。。。
LightGBMの目的関数、評価関数の話
しっかり調べられてないけど、
考えを整理するためにメモっておく。
LightGBMの目的関数は、パラメーターでobjectiveを何にするかによって決まるのが原則。
binaryを選んだ場合、src/objective/binary_objective.hppが使われる。
これはBinaryLoglossになる。
このような対応関係は、src/objective/objective_function.cppを読めば分かる。
目的関数を独自のものにする場合、lgb.trainやcvにfobjを渡す。
一方で、fevalというのがある。
これは目的関数ではない。
early_stoppingの時には使われるし、
evals_resultにも関係する。
fevalを与えない場合、early_stoppingは何を使って判断するかというと、パラメーターのmetricが使われる。
ちょっと細かく書くとこうなってるはず。
feval未指定、かつmetric未指定なら、objectiveが使われる。
feval未指定、かつmetric指定かつ、first_metric_only未指定なら、metricが使われ、metricが複数指定されていた場合、どのmetricも改善され続けないといけない。
feval未指定、かつmetric指定かつ、
first_metric_only=Trueなら、
metricの1番目のもののみでearly_stoppingする
feval指定、かつmetric指定かつ、
first_metric_only未指定の場合はどうなるか?metric、fevalの全てで改善が必要なはず。
feval指定、かつmetric指定かつ、
first_metric_only=Trueの場合はどうなるか?
evals_resultはmetric、fevalの順番で作成されているはずで、metricの1番目で評価するはず。
ひょっとすると、fevalとfirst_metric_onlyは良くない組み合わせなのかもしれない。
metricを指定しない場合、objectiveが使われるはずなので、fevalを使ったearly_stoppingにはならないのではないだろうか。
LightGBM + Optuna でベストモデル保存
さいきん書いたんで、どういうコードを書いたかをテキトーに書く。
ちなみに、公式のモデル保存の例はこちら。
嫌だ、trial数分のモデルを保存するだなんて!
では、話を戻して。。。
今回の実装の制限事項は、シリアルでしか正しく動かないことかな。
準備するもの
Objectiveクラス
Callbackクラス
Objectiveの実装
Objectiveクラスは、__call__(self, trial)を実装。
__init__で、X_trainなどに加えて、Callbackクラスも受け取る。
__call__(self, trial)内で、学習したのち、良い悪いに関わらず、出来たモデルをCallbackクラスに引き渡す、その際のキーは、trialのnumberにする。
max_binもチューニングしたいなら、lgb.Datasetは毎回作らないといけない。
class Objective: def __init__(self, X_train, y_train, X_test, y_test, callback): self.X_train = X_train self.y_train = y_train self.X_test = X_test self.y_test = y_test self.callback = callback def __call__(self, trial): # do something params = self.create_params(trial) train_set = lgb.Dataset(self.X_train, self.y_train) valid_set = lgb.Dataset(self.X_test, self.y_test, reference=train_set) pruning_callback = optuma.integration.LightGBMPruningCallback(trial, 'binary_logloss') model = lgb.train(params, train_set, valid_sets=[valid_set], callbacks=[pruning_callback]) self.callback.register_model(trial.number, model) y_test_pred = np.rint(model.predict(self.X_test)) auc_test = roc_auc_score(self.y_test, y_test_pred) return 1 - auc_test def create_params(self, trial): return something trial suggestion code
Callbackの実装
Callbackクラスは元来、study.optimize()に渡すものです。今回も渡しますけどね。
__call__(self, study, trial)を実装。
study.best_trial.numberがtrial.numberと一致したら、ベストが更新されてます。だったら、登録されてるはずのモデルを取り出せるはずです。ベストが更新されてないなら、要らないモデルが登録されてるはずです、消してしまいましょう。
モデルの登録はただのdictを使うだけです。
ついでに、bestが更新されたら、best_paramsをjsonにして保存したり、更新されなくても、studyのdataframeを毎回保存したりなんかも出来ます。これをしてると、何トライアル目かを高速に流れるログから探さなくてすみます。
class Callback: def __init__(self): self.models = {} def register_model(self, trial_number, model): self.models[str(trial_number)] = model def unregister_model(self, trial_number): self.models.pop(str(trial_number), None) def unregister_other_model(self, trial_number): model = self.models.pop(str(trial_number), None) self.models.clear() self.models[str(trial_number)] = model def get_model(self, trial_number): return self.models[str(trial_number)] def __call__(self, study, trial): if study.best_trial.number == trial.number: self.unregister_other_model(study.best_trial.number) self.save(study) else: self.unregister_model(trial.number) # save study.trials_dataframe() def save(self, study): model = self.get_model(study.best_trial.number) # save model # save study.best_params # save study.trials_dataframe()
思ったより終わらずに、止めたくなることがある。そういう時に備えて、ベスト更新時に、ファイル保存。ベスト未更新でもtrials_dataframe()を毎回ファイル保存。
利用例
callback = Callback() objective = Objective(X_train, y_train, X_test, y_test, callback) sampler = optuna.samplers.TPESampler(seed=1) study = optuna.study.create_study(sampler=sampler) study.optimize(objective, n_trials=100, callbacks=[callback]) best_model = callback.get_model(study.best_trial.number)
再現性を持たせたい時には、TPESamplerのseedを固定する必要がある。
ベストモデルを毎回保存するなら、Callbackの__call__の中で書けばいい。
最後にcallbackから取り出して保存するなら、optimize後に取り出して保存すればいい。
add_modelを明示的に呼ぶのがやはりださい。
callback = Callback()
objective = Objective(X_train, y_train, X_test, y_test, lambda x, y: callback.add_model(x, y))
こっちのパターンのほうが汎用性が高いか?
結局はobjective側で何を登録するのか決めてるので、利点はなさそう。
Go言語で、ファイルの「所有者」を拾えるか? You、DLL呼び出しちゃいなよ
発端
java.nio.file.attributeで「所有者」情報が取れる。
PowerShellでも取れる。
では、Go言語はどうだ?
調査
たとえば、C++でそれを行う場合、こういうことをする。
Finding the Owner of a File Object in C++ (Windows)
ここで登場する「GetSecurityInfo関数」はGo言語で使われているか?
調べた結果、使われていない。
大抵のdll呼び出しは、"src/syscall/zsyscall_windows.go"を読むと分かる。
「GetSecurityInfo関数」は、「advapi32.dll」に含まれています。
GetSecurityInfo function (Windows)
Go言語は、内部で「advapi32.dll」を読み込んではいるものの、「GetSecurityInfo関数」を呼び出せるようにはなっていません。
今見ているソースはGo1.8です。
挑戦
ネジがゆるい私は、「よし、DLL呼び出してみよう」と、泥船に足を突っ込んだ。
Go内部では、ロードしているDLLですが、変数が公開されていません。
しかたない、自分でロードすんぜ。
var ( modadvapi32 = syscall.NewLazyDLL("advapi32.dll") procGetSecurityInfo = modadvapi32.NewProc("GetSecurityInfo") )
はい、ただのもろパクリです。
あ、でもsysdll.Add()を挟むのは除外しました、なぜならばinternalはコンパイルエラーになるからです。
NewLazyDLLやNewProcについては、「src/syscall/dll_windows.go」に書かれています。
Procには呼び出し用のポインタレシーバが付いてます。
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
とりあえず、引数を全部uintptrで渡せってことか。
1つ目の引数 HANDLE
handleには、ファイルハンドルを渡すことになる。
os.OpenFileして、fileからFdを得ると、それはuintptrなので、そのまま渡してみる。
2つ目の引数 SE_OBJECT_TYPE
今回は、SE_FILE_OBJECTを渡したいわけです。
SE_OBJECT_TYPE enumeration (Windows)
enumで先頭が0初期化してますから、2つ目の定数SE_FILE_OBJECTは、1ってことですね。
これをuintptrで渡すとか、まどろっこしいなぁ。
3つ目の引数 SECURITY_INFORMATION
今回はOWNER_SECURITY_INFORMATIONを渡したいので、おそらく1です
DWORDとあるので、uint32にしときます。
4つ目の引数 ppsidOwner 型はPSID
これが受け取りたい。ぜひ受け取りたい。
でもPSIDって何?
どうやら、可変長な構造であるSIDをポイントしているポインターっぽい。
var psidOwner = make([]byte, uint32(50)) sid := (unsafe.Pointer(&psidOwner)) ppsidOwner := uintptr(unsafe.Pointer(sid))
ちょっと変数名をどうするか、ちぐはぐな感じになったが、こんな感じで書いてみた。
これは、「src/syscall/security_windows.go」にあるfunc LookupSIDを参考にしている。
残りの引数
どうせオプションだし、渡さないことにする。
ダメだった点
1をあらわしている引数をuintptrにして渡してるのがどうもダメっぽかったので修正。
最終的にこの形にした。1をベタ書きです。。。ひどい。なんだコレ。
r1, r2, lastErr := procGetSecurityInfo.Call(handle, 1, 1, ppsidOwner)
なんとか結果が返ってきた。
ここからバッファ渡してるやつをポイントしなおす。
ownerSID := (*syscall.SID)(unsafe.Pointer(&psidOwner[0])) str, convertErr := ownerSID.String() if convertErr != nil { } else { fmt.Printf("string SID = %s\n", str) } account, domain, accountType, lookUpErr := ownerSID.LookupAccount("") if lookUpErr != nil { } else { fmt.Printf("account = %s\n", account) fmt.Printf("domain = %s\n", domain) fmt.Printf("accountType = %d\n", accountType) }
ownerSID.String()すると「S-X-X-XX-XXXXXXXX」みたいな形式のSIDが取れてます。
ownerSID.LookupAccount("")するとアカウントがとれました。
このあたりのことは「src/syscall/security_windows.go」を見てください。
こまごました点は考慮していませんが、DLL呼び出せちゃいました。
.
Go言語でzipファイルを作る、ただしWindowsに限る
Go言語でzipファイルを扱うとき、とりあえず"archive/zip"を使いますよね。
しかし、使ってみて、気付いたことがある。
思っていたzipファイルを作ることができなかった。
ちなみに今は古いgo 1.6.2を使ってます。最新は1.8です。
zipのエントリ(zip.FileHeader)を作る方法が複数あるみたいだ。
- ファイルパスから作る writer.Create(relativepath)
- FileInfoから作る zip.FileInfoHeader(fileInfo)
- 自分で作る &zip.FileHeader{}
ファイルパスから作ると細かいことができない。
FileInfoから作ると、unix形式(rwxrwxrxw)が強制される <- SetModeを呼ばせたくない。
仕方ないから自分で作るよ。
属性
一般的なファイルは"A"が7zで表示されます。エクスプローラーの表示を変えれば属性も出せます。
setModeを呼び出すとホストOSがUnixになり、属性もrwx形式になる。
そのため、それは迂回して、自分でExternalAttrsを設定しないといけない。
とりあえず、何を設定すればどうなるのか手で打ち込んでチェックしてみた。
D ディレクトリ <- fh.ExternalAttrs = 16 R 読み取り専用 <- fh.ExternalAttrs = 1 H 隠しファイル <- fh.ExternalAttrs = 2 A アーカイブ <- fh.ExternalAttrs = 32 S システム ファイル <- fh.ExternalAttrs = 4 I 非インデックス対象ファイル <- 不明。。。力尽きた。 L 再解析ポイント <- fh.ExternalAttrs = 1024 - その属性以外
上記の調査は、だいたいあってると思う。
src/syscall/ztypes_windows.go - The Go Programming Language
https://msdn.microsoft.com/ja-jp/library/windows/desktop/gg258117(v=vs.85).aspx
あ、これでいいのかも。
fh.ExternalAttrs = data.FileAttributes
すみません、dataっつーのは、更新日のところで説明しますが、
fileInfo.Sys().(*syscall.Win32FileAttributeData).FileAttributesということです。
ファイル所有者
java.nio.file.attributeならファイル所有者の情報を取得できるらしい。
goの場合、データをどこから抜いてくるのか発見できてない。
できるんだろうか??
そして、どうやって詰めるんだっけ・・・。
どうも、zipにするときには詰められないように思える。
7zには所有者情報は見えないし。
とりあえず、zipのためには取得する必要はなさそうだ。
おそらく、これを実現するには、fileの情報をもとに、セキュリティ情報を拾って、
その情報をもとにルックアップするイメージなんだろう。(C++でもJavaでもそんなイメージ)
更新日時、作成日時、アクセス日時
更新日時のみであれば、FileHeader#SetModTime(info.ModTime())で十分です。
作成日時とアクセス日時は、FileHeaderのExtraを設定する必要がある。
作成日時とアクセス日時は、FileInfoのSys()から取得する。
とりあえず以下の記事は参考になるでしょう。
data := info.Sys().(*syscall.Win32FileAttributeData)
これを使ってExtraに突っ込む。
注意事項はリトルエンディアンであるということだ。
どういう[]byteになればいいのかは適宜バイナリエディタでzipファイルを比較確認するほうがいい。
理屈的な形式は、ここが参考になったので是非ご覧いただきたい。
https://opensource.apple.com/source/zip/zip-6/unzip/unzip/proginfo/extra.fld
リトルエンディアンを簡単に扱うためには、
"encoding/binary"パッケージのLittleEndianを使うほうが間違いがないが、
bufの先頭から詰めてくるので、Extraのbyte配列に直接詰め込めない。
binary.LittleEndian.PutUint32(buf, data)
とりあえず自分はこんな感じで書いた。間違ってたらスマン。
序盤は自前でエンディアンを意識して書いたが、後半はbinaryパッケージを利用する関数を用意した。
data := info.Sys().(*syscall.Win32FileAttributeData) var extra []byte extra = append(extra, 0x0a, 0x00) // NTFS @Short extra = append(extra, 0x20, 0x00) // TSize @Short extra = append(extra, 0x00, 0x00, 0x00, 0x00) // Reserved @Long extra = append(extra, 0x01, 0x00) // NTFS attribute Tag @Short extra = append(extra, 0x18, 0x00) // Size of attribute#1 @Short extra = appendUint32(extra, data.LastWriteTime.LowDateTime) extra = appendUint32(extra, data.LastWriteTime.HighDateTime) extra = appendUint32(extra, data.LastAccessTime.LowDateTime) extra = appendUint32(extra, data.LastAccessTime.HighDateTime) extra = appendUint32(extra, data.CreationTime.LowDateTime) extra = appendUint32(extra, data.CreationTime.HighDateTime)
あ、すみませんが、infoってのはFileInfoですよ。
filepath.Walkを呼ぶので、WalkFuncを実装していて、引数でFileInfoが渡ってきます。
ホストOS
7zで言うところのホストOSは、FileHeaderのCreatorVersionに相当する。
fh.CreatorVersion = 11 << 8
意味不明な数字がでてくるでしょう。
goのstructを見ると数字が解るので、それを8ビットシフトして使えばなんとかなる。
この11はNTFS。