prilmit についての調べ物

社内 slack で prlimit の話題があがったので、どういうもんだったかと調べた

prlimit(2) システムコール

任意の pid のリソースリミットを変更するシステムコールである。

ulimit, setrlimit, getrlimit, /proc/$pid/limits, /etc/security/limits.conf, pam_imits.so ... と関連キーワードを並べると「ああ、リソースリミットって あれ のことですね」と分かるよね

#include <sys/time.h>
#include <sys/resource.h>

int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
struct rlimit *old_limit);

一見、複雑そうなインタフェースに見えるが、 pid が指定できる getrlimit(2)setrlimit(2) の合体インタフェースだと見なせばいいかな?

prlimit(1) コマンド

prlimit(2) システムコールを呼び出して、任意のプロセスのリソースリミットを変更するのが prlimit(1) コマンド

使用例

nginx の リソースリミットを取得してみよう.

$ sudo LANG=C prlimit --pid $( pgrep nginx -n  )
RESOURCE   DESCRIPTION                             SOFT      HARD UNITS
AS         address space limit                unlimited unlimited bytes
CORE       max core file size                         0 unlimited blocks
CPU        CPU time                           unlimited unlimited seconds
DATA       max data size                      unlimited unlimited bytes
FSIZE      max file size                      unlimited unlimited blocks
LOCKS      max number of file locks held      unlimited unlimited 
MEMLOCK    max locked-in-memory address space     65536     65536 bytes
MSGQUEUE   max bytes in POSIX mqueues            819200    819200 bytes
NICE       max nice prio allowed to raise             0         0 
NOFILE     max number of open files                1024      4096  👈
NPROC      max number of processes                 7084      7084 
RSS        max resident set size              unlimited unlimited pages
RTPRIO     max real-time priority                     0         0 
RTTIME     timeout for real-time tasks        unlimited unlimited microsecs
SIGPENDING max number of pending signals           7084      7084 
STACK      max stack size                       8388608 unlimited bytes

次に、リソースリミットを変更してみよう.

  • <soft>:<hard> で値を指定できる (ソフトリミット、ハードリミットの説明は省略)
  • さらに細かな指定方法は man 3 prlimit で確認してね
-bash-4.2$  sudo prlimit --pid $( pgrep nginx -n  ) --nofile=9999:9999

実行後、NOFILE max number of open files9999:999 になったのが確認できる 😊

-bash-4.2$  sudo prlimit --pid $( pgrep nginx -n  ) 
RESOURCE   DESCRIPTION                             SOFT      HARD UNITS
AS         address space limit                unlimited unlimited バイト
CORE       max core file size                         0 unlimited blocks
CPU        CPU time                           unlimited unlimited seconds
DATA       max data size                      unlimited unlimited バイト
FSIZE      max file size                      unlimited unlimited blocks
LOCKS      max number of file locks held      unlimited unlimited 
MEMLOCK    max locked-in-memory address space     65536     65536 バイト
MSGQUEUE   max bytes in POSIX mqueues            819200    819200 バイト
NICE       max nice prio allowed to raise             0         0 
NOFILE     max number of open files                9999      9999 👈
NPROC      max number of processes                 7084      7084 
RSS        max resident set size              unlimited unlimited pages
RTPRIO     max real-time priority                     0         0 
RTTIME     timeout for real-time tasks        unlimited unlimited microsecs
SIGPENDING max number of pending signals           7084      7084 
STACK      max stack size                       8388608 unlimited バイト

オプションを変えれば他のリソースリミットも操作できる

コマンドのユースケース

「とある巨大なデーモン を起動したけどファイルデスクリプタの上限を上げるのを忘れていた ... 上限を上げ直したいけど、再起動は手間がかかるしヘビーなんだよなぁ。うーんどうしよう 😭 」

という風景を何度も見たことがある。こういうケースで役に立つだろう

あるいは 動的 にプロセスのリソースリミットを 制限 したいケースでも使えるが、制限しないと困る !!! というケースが思い浮かばない. 何か有用な事例があったら教えていただきい

prlimit(2) カーネルソースコード

prlimit(2) のカーネルソースコードはコンパクトで、詳細に入り込まず斜め読みでも理解できる感じだ

/* バージョンは 4.17rc-2 */

SYSCALL_DEFINE4(prlimit64, pid_t, pid, unsigned int, resource,
        const struct rlimit64 __user *, new_rlim,
        struct rlimit64 __user *, old_rlim)
{
    struct rlimit64 old64, new64;
    struct rlimit old, new;
    struct task_struct *tsk;
    unsigned int checkflags = 0;
    int ret;

    if (old_rlim)
        checkflags |= LSM_PRLIMIT_READ;

    if (new_rlim) {
        if (copy_from_user(&new64, new_rlim, sizeof(new64)))
            return -EFAULT;
        rlim64_to_rlim(&new64, &new);
        checkflags |= LSM_PRLIMIT_WRITE;
    }

    rcu_read_lock();
    tsk = pid ? find_task_by_vpid(pid) : current;
    if (!tsk) {
        rcu_read_unlock();
        return -ESRCH;
    }
    ret = check_prlimit_permission(tsk, checkflags);
    if (ret) {
        rcu_read_unlock();
        return ret;
    }
    get_task_struct(tsk);
    rcu_read_unlock();

    ret = do_prlimit(tsk, resource, new_rlim ? &new : NULL,
            old_rlim ? &old : NULL); 👈

    if (!ret && old_rlim) {
        rlim_to_rlim64(&old, &old64);
        if (copy_to_user(old_rlim, &old64, sizeof(old64)))
            ret = -EFAULT;
    }

    put_task_struct(tsk);
    return ret;
}

中で呼び出している do_prlimit() がシステムコールのコアで、ここも把握するとよい

/* make sure you are allowed to change @tsk limits before calling this */
int do_prlimit(struct task_struct *tsk, unsigned int resource,
        struct rlimit *new_rlim, struct rlimit *old_rlim)
{
    struct rlimit *rlim;
    int retval = 0;

    if (resource >= RLIM_NLIMITS)
        return -EINVAL;
    if (new_rlim) {
        if (new_rlim->rlim_cur > new_rlim->rlim_max)
            return -EINVAL;
        if (resource == RLIMIT_NOFILE &&
                new_rlim->rlim_max > sysctl_nr_open)
            return -EPERM;
    }

    /* protect tsk->signal and tsk->sighand from disappearing */
    read_lock(&tasklist_lock);
    if (!tsk->sighand) {
        retval = -ESRCH;
        goto out;
    }

    rlim = tsk->signal->rlim + resource;
    task_lock(tsk->group_leader);
    if (new_rlim) {
        /* Keep the capable check against init_user_ns until
          cgroups can contain all limits */
        if (new_rlim->rlim_max > rlim->rlim_max &&
                !capable(CAP_SYS_RESOURCE))
            retval = -EPERM;
        if (!retval)
            retval = security_task_setrlimit(tsk, resource, new_rlim);
        if (resource == RLIMIT_CPU && new_rlim->rlim_cur == 0) {
            /*
            * The caller is asking for an immediate RLIMIT_CPU
            * expiry.  But we use the zero value to mean "it was
            * never set".  So let's cheat and make it one second
            * instead
            */
            new_rlim->rlim_cur = 1;
        }
    }
    if (!retval) {
        if (old_rlim)
            *old_rlim = *rlim;
        if (new_rlim)
            *rlim = *new_rlim;
    }
    task_unlock(tsk->group_leader);

    /*
    * RLIMIT_CPU handling.   Note that the kernel fails to return an error
    * code if it rejected the user's attempt to set RLIMIT_CPU.  This is a
    * very long-standing error, and fixing it now risks breakage of
    * applications, so we live with it
    */
     if (!retval && new_rlim && resource == RLIMIT_CPU &&
         new_rlim->rlim_cur != RLIM_INFINITY &&
         IS_ENABLED(CONFIG_POSIX_TIMERS))
        update_rlimit_cpu(tsk, new_rlim->rlim_cur);
out:
    read_unlock(&tasklist_lock);
    return retval;
}

関数の命名が明瞭なので、だいたい何をやっているか抽象的には理解できるだろう

こんな感じ

prlimit(2) が呼び出している do_prlimit() は、getrlimit(2), setrlimit(2) からも呼び出しされていて、デザインパターンで言う所の Facade パターン(ほんとかな?) のような構造になっている

f:id:hiboma:20180424160032p:plain

getrlimit(2) と setrlimit(2) のソースコード

COMPAT_SYSCALL_DEFINE2(getrlimit, unsigned int, resource,
               struct compat_rlimit __user *, rlim)
{
    struct rlimit r;
    int ret;

    ret = do_prlimit(current, resource, NULL, &r); 👈
    if (!ret) {
        struct compat_rlimit r32;
        if (r.rlim_cur > COMPAT_RLIM_INFINITY)
            r32.rlim_cur = COMPAT_RLIM_INFINITY;
        else
            r32.rlim_cur = r.rlim_cur;
        if (r.rlim_max > COMPAT_RLIM_INFINITY)
            r32.rlim_max = COMPAT_RLIM_INFINITY;
        else
            r32.rlim_max = r.rlim_max;

        if (copy_to_user(rlim, &r32, sizeof(struct compat_rlimit)))
            return -EFAULT;
    }
    return ret;
}
SYSCALL_DEFINE2(setrlimit, unsigned int, resource, struct rlimit __user *, rlim)
{
    struct rlimit new_rlim;

    if (copy_from_user(&new_rlim, rlim, sizeof(*rlim)))
        return -EFAULT;
    return do_prlimit(current, resource, &new_rlim, NULL); 👈
}

つまり prlimit(2) を読むと一緒に getlimit(2), setrlimit(2) も把握したことになってお得だ !!!

その他

prlimit(2) に対応していない古いカーネルの場合は gdb で変更する技がある

qiita.com

感想

  • ソースを見て見ると以外やシンプル
  • リソースリミットって暗黙的な設定になりがちで、結構な確率で足を撃っているように思う

荒川〜吉見さくら堤公園〜熊谷 🚲

3/31 (土) 荒川を北上して、吉見の桜をみて、熊谷で折り返して 132 km

f:id:hiboma:20180331215739p:plain

暖かくなり調子もでてきて 久々にしっかり走った. 夕方の凶悪な向かい風がなければ、あと +15km 追加で走りたかった

続きを読む

ディレクトリを getdents(2) しつつ rename(2) を繰り返す実験

kunst1080.hatenablog.com

シンプルそうな問題でありながら、実は手強いネタで、 背後にいろんな理由が工夫やあるのだな〜と非常におもしろかったです.

この手の調査では strace を取ってシステムコールを追いかけたくなる。find(1) が呼び出す getdents(2) がどんな風に動作するのか、あるいは mv、つまり、rename(2) と併用したケースなど 調べようとすらしたことなかったなと思い実験をした

このエントリで取り扱うお題

getdents(2) で 1ディレクトリエントリずつ読み出して rename(2) していくと、ファイルシステムによってどんな違いがでるか?

先のブログで書かれていた find(1) + mv(1) あるいは readdir(3), fts_read(3) ではなく、システムコールを直接呼び出して実験する.

  • コマンドやライブラリの実装レイヤをすっ飛ばすことで、ファイルシステム の挙動を把握しやすくなるだろう
  • 1ディレクトリエントリ= getdents(2) で扱いうる最小単位で実験して結果を観察することで、それよりもエントリ数が多い場合の挙動を演繹して考えることが可能になるだろう
POSIX を確認する
This is not a bug; the POSIX specification explicitly allows this
behavior.  If a filename is renamed during a readdir() session of a
directory, it is undefined where that neither, either, or both of the
new and old filenames will be returned.

readdir() nonatomicity (Theodore Ts'o)

同ブログで引用されていた ML を読むと、readdir(3) している最中に rename(2) した場合は POSIX 仕様でも未定義らしい. getdents(2) でも同様と考えてよいだろう

先にまとめ

XFS と Btrfs が面白いことになります

f:id:hiboma:20180323194843g:plain

f:id:hiboma:20180323194849g:plain

f:id:hiboma:20180322182444g:plain

続きを読んでね

続きを読む

aws-sdk-ruby Aws::S3::Object#presigned_ur のバグレポート - 修正済み

hiboma.hatenadiary.jp

このエントリで aws-sdk-ruby のバグレポートを出したことを記したが、既に修正されて aws-sdk-core (3.17.1) がリリースされている

github.com

めでたし

感想

  • aws-sdk-ruby で問題をレポートして「バグ」と 見なされた場合、数日で修正が入り対応が速い
  • JSON を喰わせてテストコードを生成してるんだなぁ

aws-sdk-ruby Aws::S3::Object#presigned_ur のバグレポート

aws-sdk-rubyAws::S3::Object#presigned_url を呼び出す際に :get + :reponse_expires を指定すると、署名付きURLを生成せずに NotImplementedError を raise してしまうのに遭遇した

どのようなコードなんですか?

#!/usr/bin/env ruby

require 'aws-sdk-s3'

s3 = Aws::S3::Resource.new(
  access_key_id: '***',
  secret_access_key: '***',
)

obj = s3.bucket('examplebucket').object('test.txt')
puts obj.presigned_url(:get, response_expires: Time.now)

このコードを実行すると NotImplementedError を raise する。

「バグ」なんですか?

レポートを出す前に、SDK の使い方を間違ってるという悲しい凡ミスではないことを確かめないといけない

ソースとドキュメントをきっちり調べた結果、 正式にサポートされているはずのパラメータだと確信を得て、issue でバグレポートを出した。

github.com

返信がついて「使い方は間違ってないね」とのことで、 Bug として ACK された

例外の発生箇所

問題となる例外をあげているコードは以下のようなコードだ

        def build_part(shape_ref, param_value)
          case shape_ref.shape # 👈
          # supported scalar types
          when StringShape, BooleanShape, FloatShape, IntegerShape, StringShape
            param_name = shape_ref.location_name
            "#{param_name}=#{escape(param_value.to_s)}"
          when MapShape
            if StringShape === shape_ref.shape.value.shape
              query_map_of_string(param_value)
            elsif ListShape === shape_ref.shape.value.shape
              query_map_of_string_list(param_value)
            else
              msg = "only map of string and string list supported"
              raise NotImplementedError, msg
            end
          when ListShape
            if StringShape === shape_ref.shape.member.shape
              list_of_strings(shape_ref.location_name, param_value)
            else
              msg = "Only list of strings supported, got "
              msg << shape_ref.shape.member.shape.class.name
              raise NotImplementedError, msg
            end
          else
            raise NotImplementedError # 🔥
          end
        end`

再現コードを実行すると、👈 で指す値が TimestampShape となるが、 case 〜 when で TimestampShape を扱わないので、🔥 で例外となる

テストすれば気がつきそうだが、: reponse_expires を指定した場合のテストケースが無いので、ただ単に実装漏れなんじゃないか? という