pivot_root(2)

الاسم
pivot_root - تغيير نقطة الوصل الجذرية

المكتبة
مكتبة سي المعيارية (libc، -lc)

موجز
#include <sys/syscall.h>      /* تعريف ثوابت SYS_* */
#include <unistd.h>

int syscall(SYS_pivot_root, const char *new_root, const char *put_old);

ملاحظة: لا توفر glibc غلافًا لـ pivot_root()، مما يستلزم استخدام syscall(2).

الوصف
يغير pivot_root() نقطة الوصل الجذرية في مساحة أسماء الوصل الخاصة بالعملية المستدعية. بشكل أدق، ينقل نقطة الوصل الجذرية إلى الدليل put_old ويجعل new_root نقطة الوصل الجذرية الجديدة. يجب أن تمتلك العملية المستدعية إمكانية CAP_SYS_ADMIN في مساحة أسماء المستخدم التي تمتلك مساحة أسماء الوصل الخاصة بالمستدعي.

يغير pivot_root() الدليل الجذري ودليل العمل الحالي لكل عملية أو خيط في نفس مساحة أسماء الوصل إلى new_root إذا كانا يشيران إلى الدليل الجذري القديم. (انظر أيضًا الملاحظات.) من ناحية أخرى، لا يغير pivot_root() دليل العمل الحالي للمستدعي (إلا إذا كان على الدليل الجذري القديم)، وبالتالي يجب أن يتبعه استدعاء chdir("/").

تنطبق القيود التالية:

•   يجب أن يكون new_root و put_old دليلين.

•   يجب ألا يكون new_root و put_old على نفس نقطة الوصل مثل الجذر الحالي.

•   يجب أن يكون put_old عند أو أسفل new_root؛ أي أن إضافة عدد غير سالب من اللواحق "/.." إلى اسم المسار المشار إليه بواسطة put_old يجب أن ينتج نفس الدليل مثل new_root.

•   يجب أن يكون new_root مسارًا إلى نقطة وصل، لكن لا يمكن أن يكون "/". يمكن تحويل مسار ليس بالفعل نقطة وصل إلى واحدة عن طريق وصل ربط المسار على نفسه.

•   يجب ألا يكون نوع الانتشار لنقطة الوصل الأم لـ new_root ونقطة الوصل الأم للدليل الجذري الحالي MS_SHARED؛ وبالمثل، إذا كان put_old نقطة وصل موجودة، يجب ألا يكون نوع انتشارها MS_SHARED. تضمن هذه القيود أن pivot_root() لا ينشر أي تغييرات أبدًا إلى مساحة أسماء وصل أخرى.

•   يجب أن يكون الدليل الجذري الحالي نقطة وصل.

قيمة الإرجاع
عند النجاح، يُعاد الصفر. وعند حدوث خطأ، يُعاد الرقم -1، ويُضبط errno للإشارة إلى الخطأ.

الأخطاء
قد يفشل pivot_root() مع أي من نفس الأخطاء مثل stat(2). بالإضافة إلى ذلك، قد يفشل مع الأخطاء التالية:

EBUSY
    new_root أو put_old على نقطة الوصل الجذرية الحالية. (يغطي هذا الخطأ الحالة المرضية حيث new_root هو "/".)

EINVAL
    new_root ليس نقطة وصل.

EINVAL
    put_old ليس عند أو أسفل new_root.

EINVAL
    الدليل الجذري الحالي ليس نقطة وصل (بسبب chroot(2) سابق).

EINVAL
    الجذر الحالي على نقطة وصل rootfs (ramfs الأولية)؛ انظر الملاحظات.

EINVAL
    إما نقطة الوصل عند new_root، أو نقطة الوصل الأم لتلك النقطة، لها نوع انتشار MS_SHARED.

EINVAL
    put_old هو نقطة وصل وله نوع انتشار MS_SHARED.

ENOTDIR
    new_root أو put_old ليس دليلاً.

EPERM
    لا تمتلك العملية المستدعية إمكانية CAP_SYS_ADMIN.

المعايير
لينكس.

التاريخ
لينكس 2.3.41.

ملاحظات
توفر pivot_root(8) واجهة سطر أوامر لاستدعاء النظام هذا.

يسمح pivot_root() للمستدعي بالتبديل إلى نظام ملفات جذري جديد مع وضع نقطة الوصل الجذرية القديمة في موقع تحت new_root حيث يمكن فصلها لاحقًا. (حقيقة أنه ينقل جميع العمليات التي لديها دليل جذري أو دليل عمل حالي على الدليل الجذري القديم إلى الجذر الجديد يحرر الدليل الجذري القديم من المستخدمين، مما يسمح بفصل نقطة الوصل الجذرية القديمة بسهولة أكبر.)

أحد استخدامات pivot_root() هو أثناء بدء تشغيل النظام، عندما يقوم النظام بوصل نظام ملفات جذري مؤقت (مثل initrd(4))، ثم يصل نظام الملفات الجذري الحقيقي، ويحول الأخير في النهاية إلى الدليل الجذري لجميع العمليات والخيوط ذات الصلة. استخدام حديث هو إعداد نظام ملفات جذري أثناء إنشاء حاوية.

حقيقة أن pivot_root() يعدل الدلائل الجذرية ودلائل العمل الحالية للعمليات بالطريقة المذكورة في الوصف ضرورية لمنع خيوط النواة من إبقاء نقطة الوصل الجذرية القديمة مشغولة بدلائلها الجذرية ودلائل العمل الحالية، حتى لو لم تصل إلى نظام الملفات بأي شكل.

لا يمكن تطبيق pivot_root() على rootfs (ramfs الأولية). الطريقة الموصى بها لتغيير نظام الملفات الجذري في هذه الحالة هي حذف كل شيء في rootfs، ووصل rootfs فوقه بالجذر الجديد، وإرفاق stdin/stdout/stderr بـ /dev/console الجديد، وتنفيذ init(1) الجديد. توجد برامج مساعدة لهذه العملية؛ انظر switch_root(8).

  pivot_root(".", ".")
يمكن أن يكون new_root و put_old نفس الدليل. على وجه الخصوص، يسمح التسلسل التالي بعملية pivot-root دون الحاجة إلى إنشاء وإزالة دليل مؤقت:

    chdir(new_root);
    pivot_root(".", ".");
    umount2(".", MNT_DETACH);

ينجح هذا التسلسل لأن استدعاء pivot_root() يكدس نقطة وصل الجذر القديم فوق نقطة وصل الجذر الجديد في /. في تلك النقطة، يشير دليل الجذر ودليل العمل الحالي لعملية الاستدعاء إلى نقطة وصل الجذر الجديد (new_root). أثناء استدعاء umount() اللاحق، يبدأ تحليل "." من new_root ثم يتحرك لأعلى قائمة الوصلات المكدسة في /، مما يؤدي إلى فك وصل نقطة وصل الجذر القديم.

  ملاحظات تاريخية
لسنوات عديدة، حملت صفحة الدليل هذه النص التالي:

    قد يغير pivot_root() أو لا يغير الجذر الحالي ودليل العمل الحالي لأي عمليات أو خيوط تستخدم دليل الجذر القديم. يجب على مستدعي pivot_root() التأكد من أن العمليات التي لها جذر أو دليل عمل حالي في الجذر القديم تعمل بشكل صحيح في كلتا الحالتين. طريقة سهلة لضمان ذلك هي تغيير جذرها ودليل عملها الحالي إلى new_root قبل استدعاء pivot_root().

هذا النص، الذي كتب قبل حتى الانتهاء من تنفيذ استدعاء النظام في النواة، ربما كان يهدف إلى تحذير المستخدمين في ذلك الوقت بأن التنفيذ قد يتغير قبل الإصدار النهائي. ومع ذلك، ظل السلوك المذكور في الوصف ثابتًا منذ تنفيذ استدعاء النظام هذا لأول مرة ولن يتغير الآن.

أمثلة
يوضح البرنامج أدناه استخدام pivot_root() داخل مساحة اسم وصل تم إنشاؤها باستخدام clone(2). بعد التمحور إلى دليل الجذر المسمى في وسيطة سطر الأوامر الأولى للبرنامج، ينفذ الطفل الذي أنشأه clone(2) البرنامج المسمى في وسيطات سطر الأوامر المتبقية.

نوضح البرنامج عن طريق إنشاء دليل سيعمل كنظام ملفات جذر جديد ووضع نسخة من الملف التنفيذي busybox(1) (المرتبط بشكل ثابت) في ذلك الدليل.

    $ mkdir /tmp/rootfs;
    $ ls -id /tmp/rootfs;    # Show inode number of new root directory
    319459 /tmp/rootfs
    $ cp $(which busybox) /tmp/rootfs;
    $ PS1='bbsh$ ' sudo ./pivot_root_demo /tmp/rootfs /busybox sh;
    bbsh$ PATH=/;
    bbsh$ busybox ln busybox ln;
    bbsh$ ln busybox echo;
    bbsh$ ln busybox ls;
    bbsh$ ls;
    busybox  echo     ln       ls
    bbsh$ ls -id /;          # Compare with inode number above
    319459 /
    bbsh$ echo 'hello world';
    hello world

  مصدر البرنامج

    /* pivot_root_demo.c */

    #define _GNU_SOURCE
    #include <err.h>
    #include <limits.h>
    #include <sched.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <sys/mount.h>
    #include <sys/stat.h>
    #include <sys/syscall.h>
    #include <sys/wait.h>
    #include <unistd.h>

    static int
    pivot_root(const char *new_root, const char *put_old)
    {
        return syscall(SYS_pivot_root, new_root, put_old);
    }

    #define STACK_SIZE (1024 * 1024)

    static int              /* Startup function for cloned child */
    child(void *arg)
    {
        char        path[PATH_MAX];
        char        **args = arg;
        char        *new_root = args[0];
        const char  *put_old = "/oldrootfs";

        /* Ensure that 'new_root' and its parent mount don't have
           shared propagation (which would cause pivot_root() to
           return an error), and prevent propagation of mount
           events to the initial mount namespace.  */

        if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1)
            err(EXIT_FAILURE, "mount-MS_PRIVATE");

        /* Ensure that 'new_root' is a mount point.  */

        if (mount(new_root, new_root, NULL, MS_BIND, NULL) == -1)
            err(EXIT_FAILURE, "mount-MS_BIND");

        /* Create directory to which old root will be pivoted.  */

        snprintf(path, sizeof(path), "%s/%s", new_root, put_old);
        if (mkdir(path, 0777) == -1)
            err(EXIT_FAILURE, "mkdir");

        /* And pivot the root filesystem.  */

        if (pivot_root(new_root, path) == -1)
            err(EXIT_FAILURE, "pivot_root");

        /* Switch the current working directory to "/".  */

        if (chdir("/") == -1)
            err(EXIT_FAILURE, "chdir");

        /* Unmount old root and remove mount point.  */

        if (umount2(put_old, MNT_DETACH) == -1)
            perror("umount2");
        if (rmdir(put_old) == -1)
            perror("rmdir");

        /* Execute the command specified in argv[1]...  */

        execv(args[1], &args[1]);
        err(EXIT_FAILURE, "execv");
    }

    int
    main(int argc, char *argv[])
    {
        char *stack;

        /* Create a child process in a new mount namespace.  */

        stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
                     MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
        if (stack == MAP_FAILED)
            err(EXIT_FAILURE, "mmap");

        if (clone(child, stack + STACK_SIZE,
                  CLONE_NEWNS | SIGCHLD, &argv[1]) == -1)
            err(EXIT_FAILURE, "clone");

        /* Parent falls through to here; wait for child.  */

        if (wait(NULL) == -1)
            err(EXIT_FAILURE, "wait");

        exit(EXIT_SUCCESS);
    }

انظر أيضًا
chdir(2), chroot(2), mount(2), stat(2), initrd(4), mount_namespaces(7), pivot_root(8), switch_root(8)

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.