libmysqlclient.so.18はいかにしてlibz.so.1を壊したか

  • 投稿日:
  • by
  • カテゴリ:

追記(2020年5月6日)

以下の挙動は5.7.28で確認をしていましたが、5.7.30で修正されています。

結論

同じシンボルをGLOBALで公開している複数の共有ライブラリをdlopenを使って動的にロードした場合に、解決されるシンボルがファイル間で前後することがある。

ということが原因のようです。

サンプルコード

https://github.com/usualoma/break-zlibにサンプルコードがあります。

発生した問題

以下のようなDockerfileで環境を構築したときに

「(perlでImage::Magickを使って)PNG画像のサムネイルが生成できない」という問題が発生し、条件を絞り込んでいくとどうも「DBD::mysqlをImage::Magickよりも先に読み込んだ場合に生成できなくなる」ということが分かりました。ポイントは、

  • MySQL Yum Repositoryのmysql-community-libs-compatでlibmysqlclient.so.18をインストールしている
  • libmysqlclient.so.18にリンクされた共有ライブラリを動的な環境で読み込んでいる

というころにあるようです。

この環境では、以下のようにDBD::mysqlを先に読み込んだ場合には失敗し、後に読み込んだ場合には成功します。

さらに(識者からの助言をもらいつつ)もう少し追っていくと、PNG画像の読み込み時のgzipの初期化に失敗していることが分かりました。

以下のように、zlibを直接呼び出すperlモジュールでも問題が発生します。

発生条件

色々試した結果、https://github.com/usualoma/break-zlib/tree/master/srcにあるようなコードで、

  • libmysqlclient.so.18にリンクした共有ライブラリを作成
  • libz.so.1にリンクした共有ライブラリを作成
  • それらを、libmysqlclient ->libz の順に動的にロードする

というやり方で発生させることができました。

以下のようにLD_PRELOAD使った場合には発生しなかったので「ロードの順序」だけではなく、「動的にロードすること」が条件になりそうです。

発生した理由

$ ldd /usr/lib64/mysql/libmysqlclient.so.18

の結果にlibz.so.1が含まれてもいるのですが、

$ readelf -Ws /usr/lib64/mysql/libmysqlclient.so.18.1.0

の結果にも「inflateReset」のようなlibzのシンボルがGLOBALとして含まれており、結果としてlibzの共有ライブラリとして利用できるようになっているようです。

gdbでブレークポイントを設定して見ていったところ、

  • inflateReset2は、libz.so.1のもの
  • その中で呼ばれるinflateResetは、libmysqlclient.so.18のもの

というようになっていて、それぞれに含まれるzlibのバージョンの差異によりエラーが発生しているようでした。

mysql.specを見る限り、WITH_EMBEDDED_SHARED_LIBRARYは明示的に1を指定しているようなので、libzを含むところまでは意図してやっていそうでしたが、GLOBALになっている理由はよく分かりませんでした。(mysql-community-libsからインストールされる/usr/lib64/mysql/libmysqlclient.so.20.3.15には、libzが含まれているものGLOBALではありませんでした。)

感想

mysql-community-libs-compatをインストールすると依存関係でmysql-community-libsが入り、/usr/lib64/mysql/libmysqlclient.soがlibmysqlclient.so.20を指すようになるので、MySQL Yum Repositoryとしてはlibmysqlclient.so.20を推奨っぽいので、可能な場合にはそちらにリンクしたモジュールをビルドし直した方がいいのかもしれません。

ビルドをしたくない場合、CentOSのパッケージで済まそうとすると基本的にはlibmysqlclient.so.18にリンクされていて上の状況は割と発生しそうな気がするので、その場合にはlibz.so.1を先にロードしてあげたりするなどの対応が必要かもしれません。

Squooshでアップロード時に画像を最適化する

  • 投稿日:
  • by
  • カテゴリ:

tl;dr;

こんな感じで動くMovable Typeのプラグインです。

Squooshとは

Google Chrome Labsからリリースされている、サーバーを介さずブラウザだけで画像の最適化を行うことのできるツールです。

https://squoosh.app/

Squooshは基本的には外部との連携をサポートしていないのですが、GithubにClient-side APIというプルリクエストがあったので、これを現在の最新版に適用しつつ少し改造して、外部からAPI経由で呼び出して利用できるようにしています。(IE11とEdgeでは動作させることができませんでしたが。)

プラグインについて

動作

プラグインをインストールすると、アセットとして画像をアップロードしようとした際にSquooshが開かれるようになります。右側のペインで最適化の設定を行い、「アップロード」のボタンをクリックすると、変換された結果がアップロードされます。

「画像をアップロードする際にSquooshを適用する」のチェックを外してアップロードすると、Squooshを適用せずにそのままアップロードすることができます。

mt-plugin-squoosh-each-upload.png

またSquooshの適用する場合、アップロード時にファイル名を変更することができます。「IMG_1234.jpg」のような名前のファイルを、公開時には異なるファイル名にしたいことがあると思いますが、そういったケースでも事前にローカル環境でファイル名を変更しておくことなく、アップロード時にファイル名を指定することができます。

mt-plugin-Squoosh-rename.png

設定

システムのプラグイン設定で、Squooshをデフォルトで有効にするかどうかを指定することができます。

mt-plugin-Squoosh-system-config.png

またユーザー設定で、自分の好みに合わせてデフォルトで有効にするかどうかを指定することができます。

mt-plugin-Squoosh-per-user.png

動作するMTのバージョン

  • Movable Type 7

動作するブラウザ

  • Google Chrome
  • Firefox
  • Safari

リサイズ等を自動で行いたい場合

「指定した大きさへ自動でリサイズ」のようなことはこのプラグインではできないので、その場合にはImageUploadUtilityのようなプラグインを利用するとよいと思います。(ImageUploadUtility Proならウォーターマーク自動合成などの処理もできるようです。)

最適化を自動で行いたい場合

自動での最適化はこのプラグインではできないので、その場合にはLightFileのような画像軽量化サービスを利用するとよいと思います。

この記事について

この記事は Movable Type Advent Calendar 2019 の25日目の記事です。今年も無事完走できたようでほっとしました。参加された方、また楽しみに待っていただいた方、皆様お疲れさまでした。Advent Calendarを作成していただいた西山さん、ありがとうございました。それでは良いお年を!

tl;dr;

MTAppjQueryを使っている場合にはuser.jsに以下のような記述を追加すると、A要素の中にDIV要素をいれてもリッチテキストエディタに変更されてしまうというトラブルを回避できます。

どんな問題か

HTML5ではA要素に入れられる内容が結構幅広くなっているので、DIV要素も入れらるようになっているのですが、Movable Type(r.4603以下)のリッチテキストではソースモードで入れたとしても消されてしまったりという問題があります。

具体的には、ソースモードなどから以下のようなHTMLを入力した時に、WYSIWYGモードに戻して保存するとA要素が消えるなどの症状が発生します。

<a href="#"><div>contents</div></a>

ポイントは2点あって、それらが合わさって意図しない書き換えが発生しています。

  • A要素の中にDIV要素を入れることが正しくないと認識されてしまう。
  • A要素がトップレベルにあると、最上位の要素としてP要素が補完されてしまう。

これらを回避するのが上の設定です。

「forced_root_block: false」をすると最上位のP要素の補完がなくなるので、それが好ましくない場合には設定の方を以下のようにした上で、

記事を書くときに、以下のように「A要素を最上位にせずにDIV要素で囲む」などの運用でカバーすることもできると思います。

<div><a href="#"><div>contents</div></a></div>

MTMLとJavaScriptで、Movable TypeをHeadless CMSとして使う

  • 投稿日:
  • by
  • カテゴリ:

Headless CMSとMovable TypeとData API

Headless CMSを簡単に説明すると、「コンテンツをグラフィカルな表現で閲覧するための機能をもたない、データを管理するためのバックエンド側の機能のみを提供するCMS。一般的にコンテンツの公開はAPIを通じて行われ、CMSとは切り離された方法でグラフィカルな表現に変換される。」といったところでしょうか。(英語版Wikipediaにはページはあったのですが、やや偏っている印象もあったので引用はしませんでした。)デバイスの多様化に伴い、数年前からよく聞くようになってきたCMSのトレンドの一つだと思います。

MTはテンプレートを書いてファイルとして書き出すのが基本なので、これを「グラフィカルな表現に変換する機能」すると、その面から見たときはHeadless CMSではないと考えるのが妥当だと思います。

しかし一方で、MTではData APIというRESTfulなAPIを使ってコンテンツを取り出すこともできるので、これを使えばMTをHeadless CMSと考える(Headless CMSとして使う)こともできると思います。

この記事では、

  • Data APIを利用して
  • しかしJavaScriptなどのプログラムを意識することなく
  • 馴染みのあるMTタグを使って
  • MTをHeadless CMSとして使う

ということを実現できるツールを紹介します。

MT Data API React

MT Data API Reactは、Data APIで取得したデータをMTタグで書いたテンプレートでHTMLに変換して表示することのできるツールです。

以下のURLで試すことができます。「ブログのエンドポイント」に試したいブログのData APIのエンドポイントを入れれば、そのデータでブラウザ上で対話的に動作を確認することができます。

https://usualoma.github.io/mt-data-api-react/docs/playground.ja.html

「開発環境」タブの手順で「SFTPでテンプレートを更新して確認」や、「開発環境」のタブの手順で「本番環境では軽量化のために書き出したテンプレートを利用する」といったこともできます。

特徴

MTタグを書くだけで簡単に設置できる

これが最大の特徴ですが、JavaScriptを1行も書くことなくMTタグを書くだけで設置できます。

「JavaScriptを使ってAPIで取得したデータを表示する」というのもシンプルな作業ではあるのですが、実際にやるとなると「Data APIの仕様を理解する」という学習は必要になります。またJavaScriptを使うので安易に実装するとXSSなどの脆弱性に繋がる可能性があります。

MovableTypeにある程度なれている場合には、MTタグを使ってHTMLを構築するのは容易なはずなので、新しい学習のコストや脆弱性のリスクなしに、簡単に設置することができます。

ただこの部分には課題もあり、現状では使えるタグは少ないので、「サイト全体を構築する」ということはまだ難しい状態です。

JSはそれほど重くない

読み込みに必要なJSは、圧縮した状態で90KBほどです(2019年12月現在)。軽いとはいえないサイズですが、利便性とのトレードオフで考えれば許容できるサイトも多いのではないかと思います。

用途

外部のサイトへの埋め込み

メインのサイトの表示としてではなく、外部のサイトへの埋め込みの用途として利用できると思います。

  • 「新製品の特設サイト」と「コーポレートサイト」を別のMT、別の組織で管理している
  • 「コーポレートサイト」でも「新製品」のニュースを表示したいが、デザインは「コーポレートサイト」の方で管理したい

というケースで、従来であれば「JSON形式でファイル書き出してからAJAXで読み込んで表示」という手順になったと思いますが、これを(内容を示し合わせた)JSONファイルを書き出したりすることなく、表示する側で柔軟に管理できるようになります。

Rubyの関数オブジェクト

  • 投稿日:
  • by
  • カテゴリ:

tl;dr;

基本的には以下のページで説明されているとおりだと思います。

この記事は、これらを少し長めのコードで試してみたものです。

関数オブジェクトを取得

Object#methodで取得できるとのことで、まずは試してみました。

以下のことが分かりました。

  • トップレベルでのメソッド定義はmodule Kernelにプライベートメソッドとして追加される
    • トップレベルのselfであるmainで呼べるようになる
  • method(:hello) (つまり self.method(:hello) で)トップレベルの hello をオブジェクトとして取得できる
  • 定義したクラスのメソッドも instance.method(:hello) で取得できる

レシーバの確認

Object#methodから返ってくるのはclass Methodのオブジェクトで、「メソッドの実体(名前でなく)とレシーバの組を封入します」とのことなので、どのような意味か確認しました。

Object#methodを呼んだ際のレシーバが、暗黙のレシーバであるselfとして封入されていそうです。

Procはどうなっているか?

暗黙のレシーバの確認をしていて、そういえばProcだどどうなんだろうと思って調べてみました。

Procは「ブロックをコンテキスト(ローカル変数のスコープやスタックフ レーム)とともにオブジェクト化した手続きオブジェクトです」とのことなので、selfへの参照も含まれるということだと思います。

class UnboundMethodの存在

ここまで忘れていましたが、Module#instance_methodを使うと、レシーバをもたないオブジェクトを取得することができるようです。

Module#instance_method に「バインドできるのは、 生成元のクラスかそのサブクラスのインスタンスのみです。」とあるとおり、確かにサブクラスでもないObjectをbindしようとしたらTypeErrorになりました。

まとめ

  • Object#methodで関数オブジェクトを取得できる
  • 関数オブジェクト(MethodやProc)はレシーバの情報ももっていて、オブジェクトをcallした場合にはそれが暗黙のレシーバとして使われる
    • レシーバをもたないclass UnboundMethodもあるが、UnboundMethod#bindしないと呼べない

ということのようです。