ごらくらいふ

プログラミングしたりゲームしたり

cron実行時のPATHがどこで決められるのか調査した

経緯

  • CentOS鯖が2本ある
  • Let's encryptの自動更新をしたくてシェルスクリプトを書き、cronに食わせた
  • 2本の内1本から「コマンドが見つかりません」とエラーメールが飛んできた

環境

原因

両方とも失敗していたが片方しかrootメールアドレスを設定していなかった。

結論

PATHMAILTOcrontab -eで一緒に書いちゃった方がいい。

デフォルト値はCronのコード内で定義されていた。

PATHはどんな値?

cron実行中の環境変数

crontabの環境変数PATHについて調べてみた · DQNEO起業日記

# crontab -e
*/1 * * * * printenv > /tmp/printenv.txt
# cat /tmp/printenv.txt
SHELL=/bin/sh
USER=root
PATH=/usr/bin:/bin
PWD=/root
LANG=ja_JP.UTF-8
SHLVL=1
HOME=/root
LOGNAME=root

出た

ユーザの環境変数

Q.ユーザー環境変数は引き継がれるの? A.されない

Crontabの環境変数はどうなっているのか、調べる - それマグで!

まぁカスタムしたPATHとか.bash_profileに書いてあるし、cronが使えるとは思ってない。

ただ、/root/binも放り出されているなんて…

システム的な初期値は何?

# grep PATH /etc/init.d/functions
PATH="/sbin:/usr/sbin:/bin:/usr/bin"
export PATH

「CentOS でデフォルトで設定される環境変数について」(1) Linux Square − @IT

まったく一致しない……。

もはや答えはソースを見るしかない

# whereis -b crontab | cut -d' ' -f2 | xargs rpm -qf
cronie-1.4.4-15.el6_7.1.x86_64

2018/01/18 追記: fedorahosted.orgが引退した結果使用できなくなったリンク( https://git.fedorahosted.org/git/cronie.git )を差し替え

# git clone https://github.com/cronie-crond/cronie.git
# cd cronie
# git tag | grep 1.4.4 | xargs git checkout
# find . -type d | grep -v '.git'
.
./anacron
./contrib
./man
./pam
./src

cron の意外な落とし穴! - もろず blog

問題のPATHを探す

# grep '/usr/bin:/bin' src/*
src/pathnames.h:# define _PATH_DEFPATH "/usr/bin:/bin"

幸運なことにあからさまな要素がHit。 この値が利用される箇所を確認する。

# grep -n '_PATH_DEFPATH' src/*
src/cron.c:177: if (putenv("PATH=" _PATH_DEFPATH) < 0) {
src/entry.c:299:                if (glue_strings(envstr, sizeof envstr, "PATH", _PATH_DEFPATH, '=')) {
src/pathnames.h:61:#ifndef _PATH_DEFPATH
src/pathnames.h:62:# define _PATH_DEFPATH "/usr/bin:/bin"
cron.c 134行目から
int main(int argc, char *argv[]) {

    /* 省略 */

    if (putenv("PATH=" _PATH_DEFPATH) < 0) {
        log_it("CRON", pid, "DEATH", "can't putenv PATH", errno);
        exit(1);
    }

    /* 省略 */

}

大当たりだ。

C言語なんてめっきり触っておらず("PATH=" _PATH_DEFPATH)なる記法にウッときた。 (業務でC言語に振れたのはせいぜいがObjective-Csqlite直接使ったときくらい)

参考リンク

起動デーモン(rc.d)

cron

C言語

rootメールアドレス設定

検索に使った正規表現はMarkdownにでも書いて残すと良い

気がする。

特定のリポジトリでしか使わない検索内容だったので、同じリポジトリの中にregexps.mdを作って書いた。 使い方も一緒に書いておけば忘れても大丈夫だ。

こんな感じで。


Match parameter name for obj-c

(:\([^()]+?\))[^:\s]+?(?= [a-zA-z]+?:|$)
Example
// Base
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

// Erase matched-word
- (BOOL)application didFinishLaunchingWithOptions

// Use $1 when replace.
- (BOOL)application:(UIApplication *) didFinishLaunchingWithOptions:(NSDictionary *)

手元の録画データをバックアップするときに選ぶAmazonのクラウドストレージサービスはGlacier? Unlimited Drive?

Mac Book Airを買ってから最近放置気味のデスクトップPC。

1TBもあったストレージがいつの間にか残り100GBと少し。 ところがインストールしたプログラムもホームフォルダ以下も大して消費していない。なぜか。

f:id:yajamon:20161103140735p:plain

答え) C:/直下にあるプレイ動画の録画ファイルが犯人だから。

録画はちょちょいとエンコードしたら実質不要。でも消すのはもったいない。

→そうだクラウド環境に保存しよう

頻繁に取り出すわけでもないし、Amazon GlacierとAmazon Unlimited Driveを比較してみた。

Amazon Unlimited Drive

料金

  • 13800円/年
    • 1150円/月

Amazon Glacier

料金

ストレージ使用量
  • $0.0114 : GB/月
    • 100GBで約1$ => 約100円
    • 1TBで約1$ => 約1000円

結論

1TBを超える予定ならAmazon Unlimited Drive。 (使い勝手の面からもこっちでいいやという気持ち)

ファイル数や解凍関係で料金の変動はあれど、ひとまず月額の容量単価基準で答えは出た感ある。

EntyのPayPal自動支払いがいつまでたっても停止しないのでPayPalでキャンセルした

大体の経緯

キャンセル問い合わせの流れ

PayPalのお問い合わせフォームにて以下を送信。

以下の自動請求IDについて、支払い期日がYYYY/MM/DDの分から受け付けられておりません。 自動請求ID

onetapが提供するサービスについても、同時期から提供を受けることができず、請求だけが加算されております。

サービス提供者に問い合わせても音沙汰がないため、Paypal様からキャンセルをお願いいたします。

PayPalから自動返信メールが届くので以下を返信。

PayPal

頂きました下記の例では解決いたしません。 お手数ですがご対応をお願いいたします。

2日弱くらいで返事Paypalからキャンセル処理の通知メールと、返信が届く。

心理的負担からの解放

健やかに月を跨げるようになった。

自分の.vimrcをgistからダウンロードする

gist idやraw_urlを目視確認してからcurlするのは人間のすることじゃない、ということで。

前提

  • jqが使える
  • curlが使える
  • gistにある.vimrcはpublicである
  • .vimrcはちゃんと.vimrcという名前にしている
  • 複数.vimrcがある場合は、一番ファイルサイズの大きい.vimrcを信頼する

結論

# curl https://api.github.com/users/yajamon/gists | jq 'map(.files | select(has(".vimrc"))) | max_by(.[".vimrc"].size) | .[".vimrc"].raw_url' | xargs -n1 curl > .vimrc

URL listをcurlにパイプで渡してダウンロードしたい

jqでURL取り出してCurlに引き渡したかった。(しかも1件だけ)

curl (標準出力 OR パイプ)」とかでggってもPostするデータをtextからとかそんなんばっか。

結論

# cat url_list.txt | xargs -n1 curl

xargsコマンドを使って指定したコマンドを並列実行させる - 技術メモ帳

xargsの使い方を探しに行ってやっと見つけたんだけど、この情報のたどり着けなさ、みんなマジで困らなかったの…?

メソッドの責任について考えたログ

エラー処理という名のガードレールが無いプログラミングを続けてきた。 慣れないことを考えたので思案のログを残す。

条件設定

あるコレクションのメソッドにて、引数の識別子に一致する要素を探し、仕事をさせる。 この時、引数が存在しないとき(undefined, null)デフォルトの値を使用する。

雰囲気つかみ

class Worker {
  digOilfield(): boolean{};
}

class WorkerCollection {
  workerList: Worker[];

  digOilfieldWithId(id: number): boolean{
  }

  takeDefaultId(): number {
  }
}

let workerCollection = new WorkerCollection();

digOilfieldWithId の引数に対して、どこまで不安になればいいのか考えていた。

思案1: digOilfieldWithId の中でデフォルトIDを取っちゃえ

class WorkerCollection {
  workerList: Worker[];

  digOilfieldWithId(id: number): boolean{
    if(!id){
      id = this.takeDefaultId();
    }
    this.workerList[id].digOilfield();
  }

  takeDefaultId(): number {
  }
}

let workerCollection = new WorkerCollection();

/* workerをかき集めたりする */

workerCollection.digOilfieldWithId(id);

一旦書いて、undefinedやnulllが問題じゃなく、Workerが逃亡しているかどうかのほうが重要だなと思った。

改善

class WorkerCollection {
  workerList: Worker[];

  digOilfieldWithId(id: number): boolean{
    if(!this.workerList[id]){
      id = this.takeDefaultId();
    }
    this.workerList[id].digOilfield();
  }

  takeDefaultId(): number {
    // 必ず有効なIDを返す
  }
}

let workerCollection = new WorkerCollection();

/* workerをかき集めたりする */

workerCollection.digOilfieldWithId(id);

「指定のIDに仕事をさせる」という名称なのに、居ないからという理由で他のIDに仕事をさせるのは、名前に嘘をついており”無責任”なのではと思う。

いると思ったID:10に仕事をさせたつもりが、じつはID:1が仕事をしてしまうのは使う側に対して無責任。*1

思案2: 使う側にちょっと手をかけさせる

class WorkerCollection {
  workerList: Worker[];

  digOilfieldWithId(id: number): boolean{
    this.workerList[id].digOilfield();
  }

  takeDefaultId(): number {
    // 必ず有効なIDを返す
  }

  existsWorkerWithId(id:number): boolean{}
}

let workerCollection = new WorkerCollection();

/* workerをかき集めたりする */

if( !workerCollection.existsWorkerWithId(id) ){
  id = workerCollection.takeDefaultId();
}
workerCollection.digOilfieldWithId(id);

確かに使う側が気を使って引数を厳選すれば回避できるかもしれないが、それは責務が違う。 それにdigOilfieldWithIdがまた隙だらけになってしまった。

思案3: digOilfieldWithIdもっと怒れ

class WorkerCollection {
  workerList: Worker[];

  digOilfieldWithId(id: number): boolean{
    if( !this.workerList[id] ) {
        throw Error("そんな奴ァいねぇ!!");
    }
    this.workerList[id].digOilfield();
  }

  takeDefaultId(): number {
    // 必ず有効なIDを返す
  }

  existsWorkerWithId(id:number): boolean{}
}

let workerCollection = new WorkerCollection();

/* workerをかき集めたりする */

if( !workerCollection.existsWorkerWithId(id) ){
  id = workerCollection.takeDefaultId();
}
workerCollection.digOilfieldWithId(id);

メソッドの名前と異なることをしちゃあいけない。でも有効な値だけをもらえるかもわからない。 いざ無効な値が来たときどうする。

もう怒るしかない。怒ることを使う側は知るべきだ。

思案4: 使う側は手間をかけたくない

class WorkerCollection {
  workerList: Worker[];

  digOilfieldWithIdOrDefaultId(id: number): number{
    if( !workerCollection.existsWorkerWithId(id) ){
      id = workerCollection.takeDefaultId();
    }
    return this.digOilfieldWithId(id);
  }

  digOilfieldWithId(id: number): boolean{
    if( !this.workerList[id] ) {
        throw Error("そんな奴ァいねぇ!!");
    }
    this.workerList[id].digOilfield();
  }

  takeDefaultId(): number {
    // 必ず有効なIDを返す
  }

  existsWorkerWithId(id:number): boolean{}
}

let workerCollection = new WorkerCollection();

/* workerをかき集めたりする */

workerCollection.digOilfieldWithIdOrDefaultId(id);

引数IDが無効ならばデフォルトIDを使うことを明言した。*2

この処理を使うということはデフォルトIDが使用されるかもしれないことを、使う側に認識させることができた。

とりあえずここで止め。

*1:そして副作用がID:1に起こってトラブルにつながるかもしれない

*2:流石に名前がおかしい気がする