版權聲明:本文為本文為博主原創文章,轉載請注明出處,博客地址:https://www.cnblogs.com/wsg1100/。如有錯誤,歡迎指正。
1.介紹
ubuntu-base 是Ubuntu官方構建的ubuntu最小文件系統,包含debain軟件包管理器,基礎包大小通常只有幾十兆,其背后有整個ubuntu軟件源支持,ubuntu軟件一般穩定性比較好,基於ubuntu-base按需安裝Linux軟件,深度可定制......,常用於嵌入式rootfs構建。
嵌入式常見的幾種文件系統構建方法:busybox、yocto、builroot,我覺得它們都不如Ubuntu方便,強大的包管系統,有強大的社區支持,可以直接apt-get install來安裝新軟件包。本文介紹了如何基於Ubuntu-base構建完整的ubuntu 系統。對於安裝過archlinux或者構建過LFS的朋友,對該方式再熟悉不過了。
ubuntu支持很多架構,arm、X86、powerpc、ppc等,本文主要基於X86_64為例,arm、arm64也給出了簡單的制作步驟,其他架構操作類似。
2.目的
從ubuntu最小文件系統ubuntu-base開始,構建一個具有X-windows的完整系統。
2.准備宿主系統
2.1 宿主系統需求
- 宿主系統是一台linux系統電腦,可以是虛擬機,為避免Linux不同發行版間的差異影響系統構建,推薦使用ubuntu,需要能夠連接Ubuntu源(有網絡),構建時軟件需從源下載安裝。另外宿主系統硬件架構與目標系統一致(本文認為兩者屬同一架構,如果不一致需要使用使用qemu等模擬目標架構)。
- ubuntu-base包,下載連接,里面包含arm、X86等架構的Ubuntu所有版本的base包,base包是ubuntu的最小文件系統,大小約幾十M。這里使用的是X86-64架構,Ubuntu16.04最新base包:
ubuntu-base-16.04.6-base-amd64.tar.gz
- 一個磁盤分區≥5GB(也可創建一個環回設備,分區后進行操作,方法自尋搜索),目標系統將構建到該分區上,根據目的自行預估所需磁盤大小,本文構建一個僅運行QT的文件系統,5GB足夠了。由於直接下載安裝,所以對宿主系統存儲大小無要求。;
本文構建一個X86-64架構,基於Ubuntu 16.04的自定義系統, 本文磁盤分區情況如下:
/dev/sda1 #UEFI分區 500MB
/dev/sda2 #宿主系統根分區 20GB
/dev/sda3 #用來構建ubuntu-base 5GB
附:創建回環文件並分區
環回(loopback)文件系統是Linux類系統中非常有趣的部分。我們通常是在設備上(例如磁盤分區)創建文件系統。這些存儲設備能夠以設備文件的形式來使用,比如 /dev/device_name。為了使用存儲設備上的文件系統,我們需要將其掛載到一些被稱為掛載點(mount point)的目錄上。環回文件系統是指那些在文件中而非物理設備中創建的文件系統。我們可以將這些文件作為文件系統掛載到掛載點上。這實際上可以讓我們在物理磁盤上的文件中創建邏輯磁盤。
我們的目的:創建一個回環文件模擬一個大小10GB的物理磁盤,並在該回環鏡像中分兩個區:
- UEFI分區,大小500M,FAT32格式。
- 根分區(/),大小剩余大小,EXT4格式。
(1). 創建鏡像文件
也就是創建一個大小10G的文件。
$dd if=/dev/zero of=ubuntu_base.img bs=1G count=10
記錄了10+0 的讀入
記錄了10+0 的寫出
10737418240 bytes (11 GB, 10 GiB) copied, 84.1916 s, 128 MB/s
你會發現創建好的文件大小超過了1GB。這是因為硬盤作為塊設備,其分配存儲空間時是按照塊大小的整數倍來進行的。此時可以直接對這個文件作為一整個分區格式化並使用,操作如下。
用mkfs
命令將1GB的文件格式化成ext4文件系統:
$mkfs.ext4 ubuntu_base.img
可使用下面的命令就可看到已經是文件系統了:
$ file ubuntu_base.img
loobackfile.img: Linux rev 1.0 ext4 filesystem data, UUID=3be1775c-8976-445d-9134-8daabb2bade7 (extents) (64bit) (large files) (huge files)
現在就可以掛載環回文件了:
$sudo mkdir /mnt/loopback
$sudo mount -o loop ubuntu_base.img /mnt/loopback
-o loop
用來掛載環回文件系統。
這實際上是一種快捷的掛載方法,我們無需手動連接任何設備。但是在內部,這個環回文件連接到了一個名為/dev/loop1
或loop2
的設備上。
我們也可以手動來操作:
$sudo losetup /dev/loop1 ubuntu_base.img
$sudo mount /dev/loop1 /mnt/loopback
使用下面的方法進行卸載(umount):
$sudo umount /mnt/loopback
也可以用設備文件的路徑作為umount命令的參數:
$sudo umount /dev/loop1
以上是將整個文件作為一個分區使用的方法,但我們的目的是在該文件內分多個區,繼續。
(2). 建立分區
a.分區方式一
建立分區可以使用fdisk
或parted
工具,本人比較喜歡用parted
,對於設置啟動分區非常方便。
$ sudo parted ubuntu_base.img
GNU Parted 3.2
使用 /home/work/loobackfile.img
歡迎使用 GNU Parted! 輸入 'help'可獲得命令列表.
(parted)
新建UEFI分區:
對於UEFI啟動方式,首先要設置創建label為msdos;
(parted) mklabel msdos
創建分區大小500M:
(parted)mkpart primary fat32 0 500MB
查看新創建的分區,該分區編號為1:
(parted) p
Model: (file)
磁盤 /home/work/ubuntu_base.img: 10.7GB
Sector size (logical/physical): 512B/512B
分區表:msdos
Disk Flags:
數字 開始: End 大小 類型 文件系統 標志
1 512B 500MB 500MB primary fat32 lba
(parted)
將該分區設置為boot(UEFI啟動)分區:
(parted) set 1 boot on
(parted) p
Model: (file)
磁盤 /home/work/ubuntu_base.img: 10.7GB
Sector size (logical/physical): 512B/512B
分區表:msdos
Disk Flags:
數字 開始: End 大小 類型 文件系統 標志
1 512B 500MB 500MB primary fat32 啟動, lba
(parted)
設置后通過p
命令可以看到,標志處多了啟動(boot)
標志。
將剩余空間創建根分區:
(parted) mkpart primary ext4 500MB 100%
到此兩個分區創建完畢,使用p
查看,q
保存退出:
(parted) p
Model: (file)
磁盤 /home/work/ubuntu_base.img: 10.7GB
Sector size (logical/physical): 512B/512B
分區表:msdos
Disk Flags:
數字 開始: End 大小 類型 文件系統 標志
1 512B 500MB 500MB primary fat32 啟動, lba
2 500MB 10.7GB 10.2GB primary ext4 lba
b.分區方式二
使用parted分區,需要自己計算大小,對齊等,難免有些麻煩,可以使用sfdisk來簡化分區,得到上述同樣的分區效果只需執行:
sudo sfdisk --force /dev/sdc << EOF
1,500M,0x0c,*
500M,,0x83
EOF
sfdisk會從標准輸入讀取分區描述信息;每一行描述一個分區,常用格式為:<起始柱面>,<柱面數 量>,<分區ID>,< bootable >。如果參數沒有指定,則使用默認值;而<起始柱面>的默認值為當前最小可用的柱面編號。
1,500M,0x0c,*
表示的是,從1扇區開始,分配大小為500M的一個分區,分區類型為0x0c
表示fat32,'*'標識這是一個啟動分區。500M,,0x83
表示從500M大小開始,分配大小為自動分配,即剩余所有大小作為一個分區,0x83
表示這是一個ext分區。
(3). 映射分區設備並格式化
鏡像文件已經被分為兩個區,但是還沒有格式化,要對內部的兩個分區進行格式化就需要先將兩分區掛載到設備。
有一個更快的方法可以掛載鏡像中的所有分區——kpartx
。它並沒有安裝默認在系統中,你得使用軟件包管理器進行安裝:
$sudo apt-get install kpartx
執行以下命令自動掛載鏡像文件中的分區:
$sudo kpartx -v -a ubuntu_base.img
add map loop0p1 (253:0): 0 976562 linear 7:0 1
add map loop0p2 (253:1): 0 19994624 linear 7:0 976896
這條命令在磁盤鏡像中的分區與/dev/mapper
中的設備之間建立了映射,隨后便可以格式化/掛載這些設備。
格式化兩個分區:
$ sudo mkfs.fat -F 32 /dev/mapper/loop0p1 #loop0p1 回環設備的分區1
$ sudo mkfs.ext4 /dev/mapper/loop0p2 #loop0p1 回環設備的分區2
(4). 掛載分區
格式換完成后就可以掛載兩個分區:
$ sudo mkdir /mnt/loopback
$ sudo mount /dev/mapper/loop0p2 /mnt/ #掛載根分區
$ sudo mkdir -p /mnt/loopback/boot/efi
$ sudo mount /dev/mapper/loop0p1 /mnt/boot/efi #掛載uefi啟動分區
2.2 掛載新分區
將目標分區格式化后掛載到/mnt
目錄,如果掛載的是其他目錄后面的所有命令均需更改:
sudo mkfs.ext4 /dev/sda3
sudo mount /dev/sda3 /mnt
將ubuntu-base-16.04.6-base-amd64.tar.gz
解壓到/mnt
目錄:
sudo tar -xpvf ubuntu-base-16.04.6-base-amd64.tar.gz -C /mnt
注意:需要保留ubuntu-base中的文件權限及所有者,解壓時需要root權限或者sudo操作,且使用-p
參數保留權限。
2.3 配置目標Ubuntu源
ubuntu-base中有默認ubuntu官方源,如果連接上互聯網且訪問官方源速度不受限制的話不需要更換。配置文件/etc/apt/sources.list
更換源,本文宿主系統與要構建的系統硬件架構及版本一致,所以直接拷貝即可。
sudo cp /etc/apt/sources.list /mnt/etc/apt/
2.3 配置DNS
進入目標環境需要聯網,需要先配置DNS,拷貝宿主系統文件/etc/resolv.conf
到/mnt/etc/
目錄下:
sudo cp /etc/resolv.conf /mnt/etc/
2.3 配置用戶創建默認配置文件
ubuntu-base默認只有root用戶,如果需要像普通ubuntu那樣可隨意創建普通用戶,需要向Ubuntu-bae里添加用戶默認配置文件夾/etc/skel
,該文件夾內包含用戶創建時的默認配置文件如.bashrc、.profile
等,若沒有該文件夾,在構建出的文件系統中執行adduser
添加的用戶會有各種問題,所以將宿主系統/etc/skel
拷貝至ubuntu-base:
sudo cp -R /etc/skel /mnt/etc/
2.3 進入chroot環境
方法1:使用原始的方法,來進入chroot環境
掛載和激活 /dev:通常激活 /dev 目錄下設備的方式是在 /dev 目錄掛載一個虛擬文件系統(比如 tmpfs),然后允許在檢測 到設備或打開設備時在這個虛擬文件系統里動態創建設備節點。這個通常是在啟動過程中由 udev 完成。由於我們的ubuntu-base新系統還沒有 udev,也沒有被引導,有必要手動掛載和激活 /dev 這可以通過綁定掛載宿主機系統的/dev 目錄來實現。綁定掛載是一種特殊的掛載模式,它允許在另外的位置創建某個目錄或掛載點的鏡像。運行下面的命令來實現:
sudo mount -v --bind /dev /mnt/dev
掛載虛擬文件系統:
sudo mount -vt devpts devpts /mnt/dev/pts -o gid=5,mode=620
sudo mount -vt proc proc /mnt/proc
sudo mount -vt sysfs sysfs /mnt/sys
sudo mount -vt tmpfs tmpfs /mnt/run
進入chroot環境:
chroot /mnt
方法2:使用arch-chroot
linux發行版archlinux提供了一個自動化chroot的腳本arch-chroot
,包含自動配置DNS文件、自動掛載虛擬文件系統等操作,用來維護linux系統非常方便,chroot時無需掛載等操作直接執行:
sudo arch-chroot /mnt
arch-chroot
是方法1的封裝,除此之外有會對目標系統進行檢測並預先配置,其源碼見附錄。
3.安裝軟件
3.1 基本配置
chroot 后為root用戶,直接執行操作:
更新軟件包列表並升級:
apt-get update
apt-get upgrade
apt-get locales language-pack-en-base
設置默認使用的字符集:
echo "LANG=en_US.UTF-8" > /etc/locale.conf
設置hostname
echo "myhostname" > /etc/hostname
配置/etc/hosts,文件內寫入如下內容,其中myhostname
替換為上面設置的:
127.0.0.1 localhost
127.0.0.1 myhostname
127.0.1.1 myhostname.localdomain myhostname
自動補全工具bash-completion
,方便后續操作輸入時補全.
apt-get install bash-completion
修改文件~/.bashrc
尾,刪除bash-completion前注釋符#
,並刷新環境變量,使bash-completion生效。
su -
后面的指令就可以正常使用tab補全了。
3.2安裝內核
安裝內核,Ubuntu16的代號xenial :
apt-get install linux-image-generic-lts-xenial linux-headers-generic-lts-xenial
linux-modules-generic-lts-xenial
安裝常用工具:
apt install sudo openssh-server net-tools ethtool vim fish htop iputils-ping
設置時區:
dpkg-reconfigure tzdata
為root設置密碼:
passwd
安裝uefi內核引導 grub。
apt-get install grub-efi-amd64-bin
3.3 安裝X-window
apt-get x-window-system-core
apt-get qt5-default libsqlite3-dev
到此該系統可運行基本的圖形程序。
4.安裝各種桌面(可選)
按需ubuntu各種桌面環境,任選其一,也可同時安裝多個,通過systemd選擇開機啟動的登錄管理器來登錄對應的桌面。
GDM-GNOME登錄管理器;
SDDM - 基於QML的顯示管理器和KDM的后繼者; 推薦用於 Plasma和 LXQt;
XDM - X顯示管理器,支持XDMCP;
LightDM - 跨桌面顯示管理器,可以使用任何工具包中編寫的各種前端,Ubuntu16.04默認使用該管理器。
4.1 安裝Lubuntu的定制LXDE
sudo apt-get install lubuntu-desktop
4.2 安裝gnome桌面
其登錄管理器為gdm,需要先安裝xdn。
sudo apt-get install gdm
sudo -y --no-install-recommends ubuntu-gnome-desktop
4.3 安裝xfce桌面環境
xfce 是一款輕量級桌面.其登錄管理器為xdm,需要先安裝xdn。
apt-get install -y xdm
安裝桌面環境
apt-get install -y --no-install-recommends xubuntu-desktop
開機啟動桌面管理器。
systemctl enable xdm
4.4 安裝mate桌面
sudo apt-get install ubuntu-mate-core
sudo apt-get install ubuntu-mate-desktop
.......
5.arm構建簡要
arm下構建流程與上面類似,可以不使用換回鏡像,直接使用一個空文件夾rootfs
,在該文件夾內進行Ubuntu rootfs構建,構建完成后再將該文件夾下的所有文件拷貝到SD卡已格式格式化后的rootfs分區內即可,也可直接掛載SD卡內的rooyfs分區操作。
目前一般arm的板子都支持從SD卡啟動,同時SD卡內有兩個分區,一個Fat32的啟動分區內存放u-boot,另一個ext4分區為根文件系統。對於nand flash啟動的板子,將roofs按文件系統類型制作燒寫鏡像,燒寫nandflash即可。
由於arm板子的開放性,板子外設、接口等硬件資源不盡相同,不同的廠家區別很大,不像X86由硬件廠商通過BIOS(UEFI)屏蔽底層差異,兼容性強(不過現在arm這方面已改善,在arm服務器領域已與X86一樣使用UEFI標准了,樹莓派安裝win10就是UEFI應用的一個例子,此外設備樹也是借鑒了UFEI ACPI,關於UEFI,可通過https://www.zhihu.com/topic/19573354/top-answers了解)。所以完成rootfs構建后,還需要移植好Linux內核和u-boot、設備樹等。
5.1下載ubuntu-base
以ubuntu16為例,下載arm架構的ubuntu-base壓縮包,可以看到有幾類以后綴armXXX結尾的,它們的含義如下:
- ubuntu-base-xx.xx-core--arm64.tar.gz 適用於64位arm架構,幾乎所有ARMv8-A都是64位處理器,例如ARM Cortex-A53、 ARM Cortex-A57、ARM Cortex-A72、ARM Cortex-A73、RM_Cortex-A76等。
- ubuntu-base-xx.xx-core-armhf.tar.gz 適用於32位帶硬浮點arm處理器,hf(hard float),即帶有浮點單元 (FPU),主要用於ARMv7-A,例如[ARM Cortex-A5、ARM Cortex-A7、ARM Cortex-A8、ARM Cortex-A9、ARM Cortex-A12、ARM Cortex-A15、ARM Cortex-A17。
下面以armhf為例。
5.3 安裝qemu
sudo apt-get install multistrap qemu qemu-user-static binfmt-support dpkg-cross
5.4 將ubuntu-base解壓
將ubuntu-base包解壓到准備的rootfs文件夾,這里為/mnt
,下面命令根據實際情況更換。
$sudo tar -xpvf ubuntu-base-16.04.4-base-armhf.tar.gz -C /mnt
拷貝qemu-arm-static
到剛剛解壓出來的目錄/mnt/usr/bin/
:
$sudo cp /usr/bin/qemu-arm-static /mnt/usr/bin
若是arm64則拷貝qemu-aarch64-static
:
$sudo cp /usr/bin/qemu-aarch64-static /mnt/usr/bin
5.4 chroot操作
接下來的一切都和2.3 進入chroot環境,一致了。更新源並安裝需要的軟件。
修改/mnt/etc/apt/sources.list
,這里使用清華源(ubuntu arm 使用的是ubuntu-ports鏡像)。
# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main restricted
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main restricted
## Major bug fix updates produced after the final release of the
## distribution.
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main restricted
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main restricted
## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team. Also, please note that software in universe WILL NOT receive any
## review or updates from the Ubuntu security team.
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial universe
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates universe
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates universe
## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team, and may not be under a free licence. Please satisfy yourself as to
## your rights to use the software. Also, please note that software in
## multiverse WILL NOT receive any review or updates from the Ubuntu
## security team.
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial multiverse
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates multiverse
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates multiverse
## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main restricted universe multiverse
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main restricted universe multiverse
## Uncomment the following two lines to add software from Canonical's
## 'partner' repository.
## This software is not part of Ubuntu, but is offered by Canonical and the
## respective vendors as a service to Ubuntu users.
# deb http://archive.canonical.com/ubuntu xenial partner
# deb-src http://archive.canonical.com/ubuntu xenial partner
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main restricted
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main restricted
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security universe
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security multiverse
# deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security multiverse
apt-get update
apt-get install net-tools vim bash-completion ...
也可通過chroot直接執行某個命令,例如修改root密碼,,其中/mnt
是我們的rootfs目錄:
$sudo chroot /mnt passwd
直接安裝軟件:
$sudo LC_ALL=C LANGUAGE=C LANG=C chroot /mnt apt-get install packagename
5.5 最后
安裝內核,將內核和設備樹保存到rootfs中的boot目錄,即/mnt/boot/
下。nand flash除外。
與普通文件系統燒寫一致,制作燒寫鏡像,燒寫SD卡或nandflash。
附錄
arch-chroot源碼
請遵守相關開源協議。
#!/bin/bash
shopt -s extglob
# generated from util-linux source: libmount/src/utils.c
declare -A pseudofs_types=([anon_inodefs]=1
[autofs]=1
[bdev]=1
[binfmt_misc]=1
[cgroup]=1
[cgroup2]=1
[configfs]=1
[cpuset]=1
[debugfs]=1
[devfs]=1
[devpts]=1
[devtmpfs]=1
[dlmfs]=1
[fuse.gvfs-fuse-daemon]=1
[fusectl]=1
[hugetlbfs]=1
[mqueue]=1
[nfsd]=1
[none]=1
[pipefs]=1
[proc]=1
[pstore]=1
[ramfs]=1
[rootfs]=1
[rpc_pipefs]=1
[securityfs]=1
[sockfs]=1
[spufs]=1
[sysfs]=1
[tmpfs]=1)
# generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort
declare -A fsck_types=([cramfs]=1
[exfat]=1
[ext2]=1
[ext3]=1
[ext4]=1
[ext4dev]=1
[jfs]=1
[minix]=1
[msdos]=1
[reiserfs]=1
[vfat]=1
[xfs]=1)
out() { printf "$1 $2\n" "${@:3}"; }
error() { out "==> ERROR:" "$@"; } >&2
warning() { out "==> WARNING:" "$@"; } >&2
msg() { out "==>" "$@"; }
msg2() { out " ->" "$@";}
die() { error "$@"; exit 1; }
ignore_error() {
"$@" 2>/dev/null
return 0
}
in_array() {
local i
for i in "${@:2}"; do
[[ $1 = "$i" ]] && return 0
done
return 1
}
chroot_add_mount() {
mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}")
}
chroot_maybe_add_mount() {
local cond=$1; shift
if eval "$cond"; then
chroot_add_mount "$@"
fi
}
chroot_setup() {
CHROOT_ACTIVE_MOUNTS=()
[[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
trap 'chroot_teardown' EXIT
chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev &&
chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro &&
ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \
efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev &&
chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid &&
chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec &&
chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev &&
chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 &&
chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid
}
chroot_teardown() {
if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
umount "${CHROOT_ACTIVE_MOUNTS[@]}"
fi
unset CHROOT_ACTIVE_MOUNTS
}
try_cast() (
_=$(( $1#$2 ))
) 2>/dev/null
valid_number_of_base() {
local base=$1 len=${#2} i=
for (( i = 0; i < len; i++ )); do
try_cast "$base" "${2:i:1}" || return 1
done
return 0
}
mangle() {
local i= chr= out=
local {a..f}= {A..F}=
for (( i = 0; i < ${#1}; i++ )); do
chr=${1:i:1}
case $chr in
[[:space:]\\])
printf -v chr '%03o' "'$chr"
out+=\\
;;
esac
out+=$chr
done
printf '%s' "$out"
}
unmangle() {
local i= chr= out= len=$(( ${#1} - 4 ))
local {a..f}= {A..F}=
for (( i = 0; i < len; i++ )); do
chr=${1:i:1}
case $chr in
\\)
if valid_number_of_base 8 "${1:i+1:3}" ||
valid_number_of_base 16 "${1:i+1:3}"; then
printf -v chr '%b' "${1:i:4}"
(( i += 3 ))
fi
;;
esac
out+=$chr
done
printf '%s' "$out${1:i}"
}
optstring_match_option() {
local candidate pat patterns
IFS=, read -ra patterns <<<"$1"
for pat in "${patterns[@]}"; do
if [[ $pat = *=* ]]; then
# "key=val" will only ever match "key=val"
candidate=$2
else
# "key" will match "key", but also "key=anyval"
candidate=${2%%=*}
fi
[[ $pat = "$candidate" ]] && return 0
done
return 1
}
optstring_remove_option() {
local o options_ remove=$2 IFS=,
read -ra options_ <<<"${!1}"
for o in "${!options_[@]}"; do
optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]'
done
declare -g "$1=${options_[*]}"
}
optstring_normalize() {
local o options_ norm IFS=,
read -ra options_ <<<"${!1}"
# remove empty fields
for o in "${options_[@]}"; do
[[ $o ]] && norm+=("$o")
done
# avoid empty strings, reset to "defaults"
declare -g "$1=${norm[*]:-defaults}"
}
optstring_append_option() {
if ! optstring_has_option "$1" "$2"; then
declare -g "$1=${!1},$2"
fi
optstring_normalize "$1"
}
optstring_prepend_option() {
local options_=$1
if ! optstring_has_option "$1" "$2"; then
declare -g "$1=$2,${!1}"
fi
optstring_normalize "$1"
}
optstring_get_option() {
local opts o
IFS=, read -ra opts <<<"${!1}"
for o in "${opts[@]}"; do
if optstring_match_option "$2" "$o"; then
declare -g "$o"
return 0
fi
done
return 1
}
optstring_has_option() {
local "${2%%=*}"
optstring_get_option "$1" "$2"
}
dm_name_for_devnode() {
read dm_name <"/sys/class/block/${1#/dev/}/dm/name"
if [[ $dm_name ]]; then
printf '/dev/mapper/%s' "$dm_name"
else
# don't leave the caller hanging, just print the original name
# along with the failure.
print '%s' "$1"
error 'Failed to resolve device mapper name for: %s' "$1"
fi
}
fstype_is_pseudofs() {
(( pseudofs_types["$1"] ))
}
fstype_has_fsck() {
(( fsck_types["$1"] ))
}
usage() {
cat <<EOF
usage: ${0##*/} chroot-dir [command]
-h Print this help message
-u <user>[:group] Specify non-root user and optional group to use
If 'command' is unspecified, ${0##*/} will launch /bin/bash.
Note that when using arch-chroot, the target chroot directory *should* be a
mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an
accurate hierarchy of the mounted filesystems within the chroot.
If your chroot target is not a mountpoint, you can bind mount the directory on
itself to make it a mountpoint, i.e. 'mount --bind /your/chroot /your/chroot'.
EOF
}
chroot_add_resolv_conf() {
local chrootdir=$1 resolv_conf=$1/etc/resolv.conf
[[ -e /etc/resolv.conf ]] || return 0
# Handle resolv.conf as a symlink to somewhere else.
if [[ -L $chrootdir/etc/resolv.conf ]]; then
# readlink(1) should always give us *something* since we know at this point
# it's a symlink. For simplicity, ignore the case of nested symlinks.
resolv_conf=$(readlink "$chrootdir/etc/resolv.conf")
if [[ $resolv_conf = /* ]]; then
resolv_conf=$chrootdir$resolv_conf
else
resolv_conf=$chrootdir/etc/$resolv_conf
fi
# ensure file exists to bind mount over
if [[ ! -f $resolv_conf ]]; then
install -Dm644 /dev/null "$resolv_conf" || return 1
fi
elif [[ ! -e $chrootdir/etc/resolv.conf ]]; then
# The chroot might not have a resolv.conf.
return 0
fi
chroot_add_mount /etc/resolv.conf "$resolv_conf" --bind
}
while getopts ':hu:' flag; do
case $flag in
h)
usage
exit 0
;;
u)
userspec=$OPTARG
;;
:)
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
;;
?)
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
(( EUID == 0 )) || die 'This script must be run with root privileges'
(( $# )) || die 'No chroot directory specified'
chrootdir=$1
shift
[[ -d $chrootdir ]] || die "Can't create chroot on non-directory %s" "$chrootdir"
if ! mountpoint -q "$chrootdir"; then
warning "$chrootdir is not a mountpoint. This may have undesirable side effects."
fi
chroot_setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir"
chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf"
chroot_args=()
[[ $userspec ]] && chroot_args+=(--userspec "$userspec")
echo "${chroot_args[@]}"
echo "$chrootdir"
echo "$@"
SHELL=/bin/bash unshare --fork --pid chroot "${chroot_args[@]}" -- "$chrootdir" "$@"