Posted at

Android 8.0以降でNode.jsのrequire('os').cpus()が未定義エラー

More than 1 year has passed since last update.

こんにちわ。

先日、新しいAndroid端末を購入したので、とりあえずTermuxとNode.jsを入れたのですが、思わぬ壁が大きく立ちはだかる事となってしまいました。


概要

表題の通りですが、Android8.0でNode.jsのrequire('os').cpus()を走らせるとundefinedが返ってきます。

確認端末はGALAXY S9+(SM-G9650)です。


原因

Android8.0から、/proc/配下の各仮想ファイルへのアクセスが大きく制限されてしまった事によります。

cpus()/proc/statを参照しますが、これがアクセス禁止対象となってしまったのです。

ちなみに、/proc/cpuinfoなどは従来通り参照可能です。

Google曰く


/proc/statは別アプリケーションの稼働状態を推測可能にし、すなわちサイドチャネル攻撃の危険性がある為、アクセス禁止にした(意訳)


との事です。


弊害

まずnpmコマンドが使えません。

内部でcpus().lengthが使われている為、異常終了してしまいます。

つまり、npm installさえ出来ないのです。

他モジュールについても、cpus()を使う物に関しては全滅です。

色々と致命的です。


対処方法(暫定)

npmコマンド禁止縛りは流石にお話にならないので、かなり強引ですが暫定処置があります。


/data/data/com.termux/files/usr/lib/node_modules/npm/node_modules/worker-farm/lib/farm.js

// before

maxConcurrentWorkers: require('os').cpus().length

// after
maxConcurrentWorkers: 8 // CPUコア数で決め打ち(SDM845は8コア)


幸いnpm内部でcpus()を使う場所はここだけなので、手っ取り早くハードコードしてしまいます。

const execSync = require("child_process").execSync;

require("os").cpus = ()=>{
return execSync("grep Hardware /proc/cpuinfo | sed -r -e \"s|^.*?: ||\"").toString();
}

require("os").cpus().length = Number(execSync("grep processor /proc/cpuinfo | wc -l").toString());

cpus().lengthは使用頻度が高い為、/proc/cpuinfoからコア数を抽出して、標準APIに上書きする事も可能です。


おわりに

根本解決するには、Termux向けNode.jsのビルド時に別ファイルを参照するよう改変するか、端末をroot化するかの2択です。

上述にもある通り、Android8.0はセキュリティが大幅強化された為、root化は無茶苦茶しんどい上に、ファームウェアバージョン等の条件もかなり限定されます。

(SM-G9650のroot化も途中で断念しました)

そんなこんなで、根本解決は現状ほぼ不可能という、悲しい事実。

自由度の高さがウリだったAndroidも、だんだん林檎化が進んでいるように感じ、少し寂しく思います。


参考