ARROWS NX F-05F 再起動ループ

2016/4/22

1年半ほど前に白ロムで購入したARROWS NX F-05Fが再起動ループに陥り使い物にならなくなりました。

先週ぐらいから、カメラ撮影をしていると突然リブートしてしまう症状が発生。
それから日に日に負荷のかかる処理をすると、リブートが発生する頻度が高くなってきました。

終いには、一度リブートが始まるとAndroidロゴが表示された後に再びリブートしてしまい、いわゆる再起動ループに陥ってしまいました。USBより給電している状態であれば正常に起動できることが分かり、恐らく原因は電源系統にあると推測できます。
(PCのUSB端子など電圧が安定していないと再起動ループから復帰できないこともある。)
正常に起動した後、給電を止めると再びリブートしてしまうことから、バッテリーが劣化しており、負荷がかかると急激に電圧が低下し、リセットがかかるというところでしょう。

同様の症状は、ググってみると各所で報告が上がっていますね。
例によってバッテリーパックが取り外せない機種なので、有償で修理に出すことになります。
docomoのサポート情報によれば6,400円でバッテリー交換を行っているようです。

うーん、1年半でこのような状態になったことに多少の不満はありますが、修理に出すほどの愛着もない・・・。
新たな端末を購入することにします。

ASUSTOR AS1004T 購入

2016/3/16

アニメ録画サーバと、エンコード済み動画の保存用NASの空き容量が逼迫してきたため、新たにNASを購入しました。

既存のNASはNETGEAR ReadyNAS 102(RN10200)です。
低価格帯の製品だけあってCPU(Marvell Armada 370)が非力で、転送速度もそこそこといったところ。
とはいえ、自分の用途には十分な性能なので良い買い物ではありました。

今回も同じシリーズでReadyNAS 104を買おうかな、とも考えたのですが、せっかくなら新しい製品にしようと思い色々物色することに・・・。

NAS

比較検討した結果、ASUSTOR AS1004Tが良さげと判断し、購入しました。ハードウェア仕様をみると、ReadyNAS 100シリーズとメモリ容量は同じですがCPUがArmada 385へ強化された構成になっています。Synology DiskStation DS216jなんかも似たような構成ですね。

転送速度については、公式製品ページに記載のベンチマーク結果によると、CIFSでRead 110.66MB/s, Write 95.72MB/sとあり、必要十分な性能だと思いました。(SSD*4 RAID5環境での数字ということに注意)

HDD

HDDは悩んだのですが、録画サーバにも使用しているTOSHIBA MD04ACA300を2本購入。
ちょうどTSUKUMOでセールをやっていたので、1つ\9,374で購入できました。
RAID構成はRAID1のボリュームを2つ作成することにして、価格動向を見つつ残り2本を追加購入する予定です。

TOSHIBA MD04シリーズはネット上のレビューなどでゴリゴリうるさいとの声をよく聴きます。ケースや筐体にも依りますが、私もうるさいと思います。
しかし、それを避けるためにDT01やWD EZRZ(旧Green)を選択するのはあまりお勧めしません。
MD04は旧富士通のニアライン向けの系譜に連なり、一般デスクトップ向けとは異なるラインだと推測されます。NAS向けのWD Redは高すぎる・・・と思うのであれば、おすすめです。

東芝 MD04ACA300 ※20周年記念セール!
3TB 3.5インチHDD SATA6Gb/s 7200rpm バッファ128MB
商品スペック
容量 3TB 回転数 7200rpm
インターフェイス シリアルATA 600 バッファ 128MB
パッケージ バルク品
TSUKUMO 商品詳細バッジ で 2016-03-15 に生成しました

ベンチマーク

さて、セットアップを終えてお手並み拝見です。
CrystalDiskMarkのベンチマーク結果を以下に載せておきます。

いずれもクライアントはWindows 10 x64からの接続で、SMB 3.0でネゴシエートされています。
また、ジャンボフレーム(MTU: 9000byte)対応です。

ReadyNAS10ベンチ HDD: WD10PURX *2 (RAID1)

AS1004Tベンチ HDD: MD04ACA300 *2 (RAID1)

3桁には届きませんでしたが、なかなか良い数字ではないでしょうか。

ついでに録画サーバも載せておきます。
CPU: Xeon E3-1225, Mem: 16GB KVM上のCentOS 7 x64で構築した録画サーバです。
ゲストには2 cores、4GBを振ってあります。
ファイルサーバはSamba 4.3.6で、SMB 1.0でネゴシエートしています。

録画サーバ ベンチ HDD: MD04ACA300 (non RAID)

システム情報

この製品もSSHでログインできるので、やろうと思えば色々できるはずです。

admin@AS1004T:/volume1/home/admin $ uname -a
Linux AS1004T 3.10.70 #1 SMP Tue Feb 2 00:15:00 CST 2016 armv7l GNU/Linux
admin@AS1004T:/volume1/home/admin $ cat /proc/cpuinfo
processor : 0
model name : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 2125.00
Features : swp half thumb fastmult vfp edsp neon vfpv3 tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x4
CPU part : 0xc09
CPU revision : 1

processor : 1
model name : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 2125.00
Features : swp half thumb fastmult vfp edsp neon vfpv3 tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x4
CPU part : 0xc09
CPU revision : 1

Hardware : Marvell Armada 380/381/382/383/384/385/388 (Device Tree)
Revision : 0000
Serial : 0000000000000000

使い始めたばかりではありますが、管理画面のUIもReadyNASと比べ洗練されています。
ウィンドウシステムのようなUIを採用しているのが特徴的ですね。
ところどころ日本語の翻訳が気になる部分はありますが、全体のユーザビリティは良さげです。

性能もファイルサーバとしてホームユースでは十分ですが、多くのサービスを動かしたり、多くの同時接続数を要求されたりする場合は、素直にAtomやCeleron搭載のモデルを使用した方が良いと思います。

筐体についても、正直、価格相応といったところです。
商品画像を見ると、前面パネルの表面加工が高級感あるように見えますが、実際は・・・。
そして、金属筐体でないという点が残念なところです。非常にチープです。
筐体の作りとしては、ReadyNASの方が安いにもかかわらず、かなりしっかりしていますね。

後は、出来るだけ長く動き続けてくれることを願いましょう。

DELL XPS 13 9343 無線LANカード換装

2015/12/13

半年ほど前にXPS 13(9343)を購入したのですが、評判の通りとても良いマシンで気に入っています。
やっぱりある程度金出さないとダメなんだなあと思いました。
Skylakeが搭載された新しいモデル(9350)も販売されているらしいですね。

さて、今回は内蔵の無線LANカード(DELL  DW1560)をIntel Dual Band Wireless-AC 7265へ換装するお話です。
なぜ換装したかといえば、このPCにはFedoraをインストールして使っているのですが、どうもWiFI接続が不安定なのです。DELL DW1560の中身はBroadcom BCM94352Zなのですが、b43ではサポートされておらず、broadcom-wlを導入する必要があります。

当初は問題なく動作していると思いきや、度々全く接続できなくなったり、特定のAPに接続できなかったり、Bluetoothが動作しないなどの症状が出ていました。
有線LANが使えないので、WiFiが不安定なのは致命傷です。

Arch Linux WikiのDell XPS 13 (2015)ページを見ても、メインラインカーネルでサポートされているIntel 7265に換装した方が幸せになれそうということが分かります。
早速どこで買おうかと色々検索したのですが、国内よりAmazon.comの方が安いことが分かり日本円にして3,330円で購入できました。Amazon.co.jpにも3,000円前後のものがありますが、それはWireless-Nで802.11 acに対応していないモデルなので要注意です(偽物もあるかも)。

Amazon.com: Intel 7265NGW Dual Band 2×2 Wireless AC + Bluetooth 4.0 M2 Interface Also Supports 802.11 AC-B/G/N

注文から10日ほどで到着しました。それでは換装作業です。
例によってサービスマニュアルに分解から換装まで丁寧に記載されているので、それほど困りませんね。

Dell 製品サポート: XPS 13 9343 サービスマニュアル PDF (8748 KB)

p.12~の通り、裏面のベースカバーさえ外せてしまえば、あとは簡単です。
とは言いつつも、ネジを全て外した後、カバーのツメが非常に外しづらく、無理に力を入れれば折れます。(数カ所折れてしまいました・・・)
アルミ削り出しの筐体であの薄さですから、遊びも少なく少し歪ませることもできません。
図中にもありますが、ヒンジの側から何か差し込んで少しずつ外していく必要があります。
私はステンレス直定規でこじ開けつつ竹串を挟んで作業しましたが、プラスティック製のスクレーパーがあったほうが良いと思います。

XPS13_1

写真中央にあるのがSSDで、右上に「WLAN」と書かれているのが無線LANカードです。
ちゃちゃっと交換して、カバーを戻して、起動。

無事認識して、正常に動作していました!
以前あったような症状も一切なくなり、快適そのものです。
数千円のお金と多少の時間をかけてでも、やる価値はありますね。

FuelPHPのEmailパッケージをS/MIMEに対応させる

2015/9/28

FuelPHPのEmailパッケージをS/MIMEに対応させるお話です。(今回は電子署名のみ)

Emailパッケージを拡張するのには、以下のような方法で行いました。

#! /bin/blog: [FuelPHP]ネームスペースを上書きしてパッケージのクラスを書き換える

変更が必要なのは Email\Email_Driver だけなので、Emailパッケージから必要なファイルをコピーして以下のような構成を作ります。

fuel
└ packages
    └myemail
        ├─ bootstrap.php
        |─ classes
        |    └─ email
        |         └─ driver.php
        └─ config
             └─ email.php

bootstrap.php は以下のように指定して、コピーした方のファイルでEmail_Driverを上書きします。
また、fuel/app/config 以下の config.php のalways_load に myemail を追加するのも忘れずに。

<?php
\Autoloader::add_core_namespace('Email');

\Autoloader::add_classes(array(
	/**
	 * Email classes.
	 */
	'Email\\Email_Driver'                      => __DIR__.'/classes/email/driver.php',

));

これで下準備は完了です。それでは、コピーしたファイルに変更を加えていきましょう。
driver.php
は build_message メソッドを以下のように変更します。

	protected function build_message($no_bcc = false)
	{
		$newline = $this->config['newline'];
		$charset = $this->config['charset'];
		$encoding = $this->config['encoding'];

		$headers = '';
		$parts = array('Date', 'Return-Path', 'From', 'To', 'Cc', 'Bcc', 'Reply-To', 'Subject', 'Message-ID', 'X-Priority', 'X-Mailer', 'MIME-Version', 'Content-Type');
		$no_bcc and array_splice($parts, 5, 1);
		$this->config['smime']['enabled'] and array_splice($parts, -2, 2);
		
		foreach ($parts as $part)
		{
			$headers .= $this->get_header($part);
		}

		foreach ($this->extra_headers as $header => $value)
		{
			$headers .= $header.': '.$value.$newline;
		}

		$headers .= $newline;

		$body = '';

		if ($this->type === 'plain' or $this->type === 'html')
		{
			if ($this->config['smime']['enabled'])
			{
				$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
				$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
			}
			$body .= $this->body;
		}
		else
		{
			if ($this->config['smime']['enabled'])
			{
				$body .= $this->get_header('Content-Type').$newline;
			}
			
			switch ($this->type)
			{
				case 'html_alt':
					$body .= '--'.$this->boundaries[0].$newline;
					$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->alt_body.$newline.$newline;
					$body .= '--'.$this->boundaries[0].$newline;
					$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->body.$newline.$newline;
					$body .= '--'.$this->boundaries[0].'--';
					break;
				case 'plain_attach':
				case 'html_attach':
				case 'html_inline':
					$body .= '--'.$this->boundaries[0].$newline;
					$text_type = (stripos($this->type, 'html') !== false) ? 'html' : 'plain';
					$body .= 'Content-Type: text/'.$text_type.'; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->body.$newline.$newline;
					$attach_type = (stripos($this->type, 'attach') !== false) ? 'attachment' : 'inline';
					$body .= $this->get_attachment_headers($attach_type, $this->boundaries[0]);
					$body .= '--'.$this->boundaries[0].'--';
					break;
				case 'html_alt_inline':
					$body .= '--'.$this->boundaries[0].$newline;
					$body .= 'Content-Type: text/plain'.'; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->alt_body.$newline.$newline;
					$body .= '--'.$this->boundaries[0].$newline;
					$body .= 'Content-Type: multipart/related;'.$newline."\tboundary=\"{$this->boundaries[1]}\"".$newline.$newline;
					$body .= '--'.$this->boundaries[1].$newline;
					$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->body.$newline.$newline;
					$body .= $this->get_attachment_headers('inline', $this->boundaries[1]);
					$body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
					$body .= '--'.$this->boundaries[0].'--';
					break;
				case 'html_alt_attach':
				case 'html_inline_attach':
					$body .= '--'.$this->boundaries[0].$newline;
					$body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline;
					if (stripos($this->type, 'alt') !== false)
					{
						$body .= '--'.$this->boundaries[1].$newline;
						$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
						$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
						$body .= $this->alt_body.$newline.$newline;
					}
					$body .= '--'.$this->boundaries[1].$newline;
					$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->body.$newline.$newline;
					if (stripos($this->type, 'inline') !== false)
					{
						$body .= $this->get_attachment_headers('inline', $this->boundaries[1]);
						$body .= $this->alt_body.$newline.$newline;
					}
					$body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
					$body .= $this->get_attachment_headers('attachment', $this->boundaries[0]);
					$body .= '--'.$this->boundaries[0].'--';
					break;
				case 'html_alt_inline_attach':
					$body .= '--'.$this->boundaries[0].$newline;
					$body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline;
					$body .= '--'.$this->boundaries[1].$newline;
					$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->alt_body.$newline.$newline;
					$body .= '--'.$this->boundaries[1].$newline;
					$body .= 'Content-Type: multipart/related;'.$newline."\t boundary=\"{$this->boundaries[2]}\"".$newline.$newline;
					$body .= '--'.$this->boundaries[2].$newline;
					$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
					$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
					$body .= $this->body.$newline.$newline;
					$body .= $this->get_attachment_headers('inline', $this->boundaries[2]);
					$body .= $this->alt_body.$newline.$newline;
					$body .= '--'.$this->boundaries[2].'--'.$newline.$newline;
					$body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
					$body .= $this->get_attachment_headers('attachment', $this->boundaries[0]);
					$body .= '--'.$this->boundaries[0].'--';
					break;
			}

		}
		
		if ($this->config['smime']['enabled'])
		{
			$tmp_dir = $this->config['smime']['tmp_dir'];
			$infilename = 'email_'.md5($body.microtime());
			$outfilename = $infilename.'_out';
			$signcert = 'file://'.$this->config['smime']['signcert'];
			$privkey = 'file://'.$this->config['smime']['privkey'];
			$headers = substr($headers, 0, -strlen($newline)*2);
			
			if ( ! is_writable($tmp_dir))
			{
				throw new \FuelException('Cannot write into directory "'.$tmp_dir.'"');
			}
			if ( ! is_readable($signcert) || ! is_readable($privkey))
			{
				throw new \FuelException('Cannot read certificate or key.');
			}
			
			if ($this->config['smime']['keypass'])
			{
				$privkey = array($privkey, $this->config['smime']['keypass']);
			}
			
			\File::create($tmp_dir, $infilename, $body);

			openssl_pkcs7_sign($tmp_dir.$infilename, $tmp_dir.$outfilename, $signcert, $privkey, array($headers));

			$body = \File::read($tmp_dir.$outfilename, true);

			\File::delete($tmp_dir.$infilename);
			\File::delete($tmp_dir.$outfilename);
			
			$headers = '';
			$body = strtr($body, array_fill_keys(array("\r\n", "\r", "\n"), $newline));
		}

		return array(
			'header' => $headers,
			'body' => $body,
		);
	}

config/email.php には、以下の設定を追加します。

	'defaults' => array(
		
		<省略>
		
		/**
		 * S/MIME
		 */
		'smime' => array(
			'enabled' => false,
			'tmp_dir' => APPPATH.'tmp/',
			'signcert' => '',
			'privkey' => '',
			'keypass' => '',
		),
	),

実際使うときは、もちろんこれを fuel/app/config以下にコピーして書き換えてください。
tmp_dir は、openssl_pkcs7_sign の入出力に必要なファイルが読み書きされます。
適切なパーミッションが与えられたディレクトリを指定する必要があります。

証明書と秘密鍵はsigncert, privkeyでファイルパスを指定します。PEM形式じゃないとダメっぽいです。
詳しことは「PHP: OpenSSL キー/証明書パラメータ」をご覧ください。

これで設定が正しければS/MIME電子署名メールが送れるはずです。
注意としては、Emailのドライバがmail, smtp, sendmailでないとうまくいかないはずです。
mailgunだとWeb API経由で送信しており、こちらで組み立てたメール本文がそのまま配送されないためです。
mailgunでもSMTP経由で送信することも可能ですので、smtpドライバを用いてmailgunのSMTPサーバーで設定を行いましょう。

多次元配列の要素を特定の値をキーとしてマージする

2015/8/25

FuelPHPのArrクラスでは、非常に便利な配列操作のヘルパー関数が用意されているのですが、ありそうでなかったので…

それから、記事タイトルが何言ってるのか正直自分もよく分からないので例を。

array(
    array(
        'group' => 'CV1',
        'name' => 'Miku'
    ),
    array(
        'group' => 'CV2',
        'name' => 'Rin'
    ),
    array(
        'group' => 'CV2',
        'name' => 'Len'
    )
);

のような入力を与えた時に、

array(
    'CV1' => array(
        array(
            'group' => 'CV1',
            'name' => 'Miku'
        )
    ),
    'CV2' => array(
        array(
            'group' => 'CV2',
            'name' => 'Rin'
        ),
        array(
            'group' => 'CV2',
            'name' => 'Len'
        )
    )
);

あるいは、

array(
    'CV1' => array(
        'Miku'
    ),
    'CV2' => array(
        'Rin',
        'Len'
    )
);

のような出力を得たい、ということです。あるキーの同じ値同士でまとめるような感覚でしょうか。

とりあえず以下のように書いてみました。

class Arr extends \Fuel\Core\Arr
{
	/**
	 * @param array  $assoc     変換する配列
	 * @param string $key_field keyとなる連想配列の添字
	 * @param string $val_field valueとなる連想配列の添字
	 * @return array
	 */
	public static function merge_assoc_key($assoc, $key_field, $val_field = null)
	{
		$output = array_fill_keys(array_column($assoc, $key_field, $key_field), array());

		foreach ($assoc as $elm)
		{
			array_push($output[$elm[$key_field]], $val_field ? $elm[$val_field] : $elm);
		}

		return $output;
	}
}

パフォーマンスがどうか全く分からんですけど、

Arr::merge_assoc_key($input, 'group');
Arr::merge_assoc_key($input, 'group', 'name');

みたいな感じで、期待した結果が得られます。

データベースから取ってきた配列をこんな感じで整形したいことがそこそこある気がします、よね?

書いてて思ったけど、PHPには豊富な配列操作の標準関数が用意されるけども、マニュアルを眺めてあーこれこれ、こんなんあったね、とかやってるあたりまだ未熟だなぁ…