diff options
| author | 2018-03-20 22:56:16 -0400 | |
|---|---|---|
| committer | 2018-03-20 22:56:16 -0400 | |
| commit | d929c8cbc09732337fb4805accbf3564e9cca0bb (patch) | |
| tree | 27f71b63f26cdf0ef957ce2d7390f5991561b839 | |
Import Upstream version 18upstream/18
| -rw-r--r-- | COPYING | 339 | ||||
| -rw-r--r-- | Makefile | 43 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | arch-chroot.in | 78 | ||||
| -rw-r--r-- | common | 252 | ||||
| -rw-r--r-- | genfstab.in | 230 | ||||
| -rw-r--r-- | pacstrap.in | 119 | ||||
| -rw-r--r-- | test/common | 45 | ||||
| -rwxr-xr-x | test/test_mangle | 8 | ||||
| -rwxr-xr-x | test/test_option_twiddling | 55 | ||||
| -rwxr-xr-x | test/test_valid_number_of_base | 15 | ||||
| -rw-r--r-- | zsh-completion | 164 |
12 files changed, 1363 insertions, 0 deletions
@@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6824f3f --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +VER=18 + +PREFIX = /usr/local + +BINPROGS = \ + arch-chroot \ + genfstab \ + pacstrap + +BASH = bash + +all: $(BINPROGS) + +V_GEN = $(_v_GEN_$(V)) +_v_GEN_ = $(_v_GEN_0) +_v_GEN_0 = @echo " GEN " $@; + +edit = $(V_GEN) m4 -P $@.in >$@ && chmod go-w,+x $@ + +%: %.in common + $(edit) + +clean: + $(RM) $(BINPROGS) + +check: all + @for f in $(BINPROGS); do bash -O extglob -n $$f; done + @r=0; for t in test/test_*; do $(BASH) $$t || { echo $$t fail; r=1; }; done; exit $$r + +install: all + install -dm755 $(DESTDIR)$(PREFIX)/bin + install -m755 $(BINPROGS) $(DESTDIR)$(PREFIX)/bin + install -Dm644 zsh-completion $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_archinstallscripts + +uninstall: + for f in $(BINPROGS); do $(RM) $(DESTDIR)$(PREFIX)/bin/$$f; done + $(RM) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_archinstallscripts + +dist: + git archive --format=tar --prefix=arch-install-scripts-$(VER)/ v$(VER) | gzip -9 > arch-install-scripts-$(VER).tar.gz + gpg --detach-sign --use-agent arch-install-scripts-$(VER).tar.gz + +.PHONY: all clean install uninstall dist diff --git a/README.md b/README.md new file mode 100644 index 0000000..34aff43 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Arch Install Scripts + +This is a small suite of scripts aimed at automating some menial +tasks when installing [Arch Linux](https://www.archlinux.org). + +## Requirements + +* GNU coreutils (>= v8.15) +* util-linux (>= 2.23) +* POSIX awk +* bash (>= 4.1) + +## License + +See COPYING for details. diff --git a/arch-chroot.in b/arch-chroot.in new file mode 100644 index 0000000..a954953 --- /dev/null +++ b/arch-chroot.in @@ -0,0 +1,78 @@ +#!/bin/bash + +shopt -s extglob + +m4_include(common) + +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. + +EOF +} + +chroot_add_resolv_conf() { + local chrootdir=$1 resolv_conf=$1/etc/resolv.conf + + # 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" + +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") +chroot_args+=("$chrootdir" "$@") + +SHELL=/bin/bash unshare --fork --pid chroot "${chroot_args[@]}" @@ -0,0 +1,252 @@ +# 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 +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() { + umount "${CHROOT_ACTIVE_MOUNTS[@]}" + 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"] )) +} diff --git a/genfstab.in b/genfstab.in new file mode 100644 index 0000000..1e8a2c8 --- /dev/null +++ b/genfstab.in @@ -0,0 +1,230 @@ +#!/bin/bash + +shopt -s extglob + +m4_include(common) + +write_source() { + local src=$1 spec= label= uuid= comment=() + + label=$(lsblk -rno LABEL "$1" 2>/dev/null) + uuid=$(lsblk -rno UUID "$1" 2>/dev/null) + + # bind mounts do not have a UUID! + + case $bytag in + '') + [[ $uuid ]] && comment=("UUID=$uuid") + [[ $label ]] && comment+=("LABEL=$(mangle "$label")") + ;; + LABEL) + spec=$label + [[ $uuid ]] && comment=("$src" "UUID=$uuid") + ;; + UUID) + spec=$uuid + comment=("$src") + [[ $label ]] && comment+=("LABEL=$(mangle "$label")") + ;; + *) + [[ $uuid ]] && comment=("$1" "UUID=$uuid") + [[ $label ]] && comment+=("LABEL=$(mangle "$label")") + [[ $bytag ]] && spec=$(lsblk -rno "$bytag" "$1" 2>/dev/null) + ;; + esac + + [[ $comment ]] && printf '# %s\n' "${comment[*]}" + + if [[ $spec ]]; then + printf '%-20s' "$bytag=$(mangle "$spec")" + else + printf '%-20s' "$(mangle "$src")" + fi +} + +optstring_apply_quirks() { + local varname=$1 fstype=$2 + + # SELinux displays a 'seclabel' option in /proc/self/mountinfo. We can't know + # if the system we're generating the fstab for has any support for SELinux (as + # one might install Arch from a Fedora environment), so let's remove it. + optstring_remove_option "$varname" seclabel + + # Prune 'relatime' option for any pseudofs. This seems to be a rampant + # default which the kernel often exports even if the underlying filesystem + # doesn't support it. Example: https://bugs.archlinux.org/task/54554. + if awk -v fstype="$fstype" '$1 == fstype { exit 1 }' /proc/filesystems; then + optstring_remove_option "$varname" relatime + fi + + case $fstype in + f2fs) + # These are Kconfig options for f2fs. Kernels supporting the options will + # only provide the negative versions of these (e.g. noacl), and vice versa + # for kernels without support. + optstring_remove_option "$varname" noacl,acl,nouser_xattr,user_xattr + ;; + vfat) + # Before Linux v3.8, "cp" is prepended to the value of the codepage. + if optstring_get_option "$varname" codepage && [[ $codepage = cp* ]]; then + optstring_remove_option "$varname" codepage + optstring_append_option "$varname" "codepage=${codepage#cp}" + fi + ;; + esac +} + +usage() { + cat <<EOF +usage: ${0##*/} [options] root + + Options: + -L Use labels for source identifiers (shortcut for -t LABEL) + -p Exclude pseudofs mounts (default behavior) + -P Include pseudofs mounts + -t TAG Use TAG for source identifiers + -U Use UUIDs for source identifiers (shortcut for -t UUID) + + -h Print this help message + +genfstab generates output suitable for addition to an fstab file based on the +devices mounted under the mountpoint specified by the given root. + +EOF +} + +if [[ -z $1 || $1 = @(-h|--help) ]]; then + usage + exit $(( $# ? 0 : 1 )) +fi + +while getopts ':LPpt:U' flag; do + case $flag in + L) + bytag=LABEL + ;; + U) + bytag=UUID + ;; + P) + pseudofs=1 + ;; + p) + pseudofs=0 + ;; + t) + bytag=${OPTARG^^} + ;; + :) + die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG" + ;; + ?) + die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" + ;; + esac +done +shift $(( OPTIND - 1 )) + +(( $# )) || die "No root directory specified" +root=$(realpath -mL "$1"); shift + +if ! mountpoint -q "$root"; then + die "$root is not a mountpoint" +fi + +# handle block devices +findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root" | + while read -r src target fstype opts fsroot; do + if (( !pseudofs )) && fstype_is_pseudofs "$fstype"; then + continue + fi + + # default 5th and 6th columns + dump=0 pass=2 + + src=$(unmangle "$src") + target=$(unmangle "$target") + target=${target#$root} + + if (( !foundroot )) && findmnt "$src" "$root" >/dev/null; then + # this is root. we can't possibly have more than one... + pass=1 foundroot=1 + fi + + # if there's no fsck tool available, then only pass=0 makes sense. + if ! fstype_has_fsck "$fstype"; then + pass=0 + fi + + if [[ $fsroot != / ]]; then + if [[ $fstype = btrfs ]]; then + opts+=,subvol=${fsroot#/} + else + # it's a bind mount + src=$(findmnt -funcevo TARGET "$src")$fsroot + if [[ $src -ef $target ]]; then + # hrmm, this is weird. we're probably looking at a file or directory + # that was bound into a chroot from the host machine. Ignore it, + # because this won't actually be a valid mount. Worst case, the user + # just re-adds it. + continue + fi + fstype=none + opts+=,bind + pass=0 + fi + fi + + # filesystem quirks + case $fstype in + fuseblk) + # well-behaved FUSE filesystems will report themselves as fuse.$fstype. + # this is probably NTFS-3g, but let's just make sure. + if ! newtype=$(lsblk -no FSTYPE "$src") || [[ -z $newtype ]]; then + # avoid blanking out fstype, leading to an invalid fstab + error 'Failed to derive real filesystem type for FUSE device on %s' "$target" + else + fstype=$newtype + fi + ;; + esac + + optstring_apply_quirks "opts" "$fstype" + + # write one line + write_source "$src" + printf '\t%-10s' "/$(mangle "${target#/}")" "$fstype" "$opts" + printf '\t%s %s' "$dump" "$pass" + printf '\n\n' +done + +# handle swaps devices +{ + # ignore header + read + + while read -r device type _ _ prio; do + options=defaults + if [[ $prio != -1 ]]; then + options+=,pri=$prio + fi + + # skip files marked deleted by the kernel + [[ $device = *'\040(deleted)' ]] && continue + + if [[ $type = file ]]; then + printf '%-20s' "$device" + elif [[ $device = /dev/dm-+([0-9]) ]]; then + # device mapper doesn't allow characters we need to worry + # about being mangled, and it does the escaping of dashes + # for us in sysfs. + write_source "$(dm_name_for_devnode "$device")" + else + write_source "$(unmangle "$device")" + fi + + printf '\t%-10s\t%-10s\t%-10s\t0 0\n\n' 'none' 'swap' "$options" + done +} </proc/swaps + +# vim: et ts=2 sw=2 ft=sh: diff --git a/pacstrap.in b/pacstrap.in new file mode 100644 index 0000000..72f2809 --- /dev/null +++ b/pacstrap.in @@ -0,0 +1,119 @@ +#!/bin/bash + +# +# Assumptions: +# 1) User has partitioned, formatted, and mounted partitions on /mnt +# 2) Network is functional +# 3) Arguments passed to the script are valid pacman targets +# 4) A valid mirror appears in /etc/pacman.d/mirrorlist +# + +shopt -s extglob + +m4_include(common) + +hostcache=0 +copykeyring=1 +copymirrorlist=1 + +usage() { + cat <<EOF +usage: ${0##*/} [options] root [packages...] + + Options: + -C config Use an alternate config file for pacman + -c Use the package cache on the host, rather than the target + -G Avoid copying the host's pacman keyring to the target + -i Avoid auto-confirmation of package selections + -M Avoid copying the host's mirrorlist to the target + + -h Print this help message + +pacstrap installs packages to the specified new root directory. If no packages +are given, pacstrap defaults to the "base" group. + +EOF +} + +if [[ -z $1 || $1 = @(-h|--help) ]]; then + usage + exit $(( $# ? 0 : 1 )) +fi + +(( EUID == 0 )) || die 'This script must be run with root privileges' + +while getopts ':C:cdGiM' flag; do + case $flag in + C) + pacman_config=$OPTARG + ;; + d) + # retired flag. does nothing. + ;; + c) + hostcache=1 + ;; + i) + interactive=1 + ;; + G) + copykeyring=0 + ;; + M) + copymirrorlist=0 + ;; + :) + die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG" + ;; + ?) + die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" + ;; + esac +done +shift $(( OPTIND - 1 )) + +(( $# )) || die "No root directory specified" +newroot=$1; shift +pacman_args=("${@:-base}") + +if (( ! hostcache )); then + pacman_args+=(--cachedir="$newroot/var/cache/pacman/pkg") +fi + +if (( ! interactive )); then + pacman_args+=(--noconfirm) +fi + +if [[ $pacman_config ]]; then + pacman_args+=(--config="$pacman_config") +fi + +[[ -d $newroot ]] || die "%s is not a directory" "$newroot" + +# create obligatory directories +msg 'Creating install root at %s' "$newroot" +mkdir -m 0755 -p "$newroot"/var/{cache/pacman/pkg,lib/pacman,log} "$newroot"/{dev,run,etc} +mkdir -m 1777 -p "$newroot"/tmp +mkdir -m 0555 -p "$newroot"/{sys,proc} + +# mount API filesystems +chroot_setup "$newroot" || die "failed to setup chroot %s" "$newroot" + +msg 'Installing packages to %s' "$newroot" +if ! pacman -r "$newroot" -Sy "${pacman_args[@]}"; then + die 'Failed to install packages to new root' +fi + +if (( copykeyring )); then + # if there's a keyring on the host, copy it into the new root, unless it exists already + if [[ -d /etc/pacman.d/gnupg && ! -d $newroot/etc/pacman.d/gnupg ]]; then + cp -a /etc/pacman.d/gnupg "$newroot/etc/pacman.d/" + fi +fi + +if (( copymirrorlist )); then + # install the host's mirrorlist onto the new root + cp -a /etc/pacman.d/mirrorlist "$newroot/etc/pacman.d/" +fi + +# vim: et ts=2 sw=2 ft=sh: diff --git a/test/common b/test/common new file mode 100644 index 0000000..8773077 --- /dev/null +++ b/test/common @@ -0,0 +1,45 @@ +#!/bin/bash + +fail=0 +testcount=0 + +EXPECT_success() { + (( ++testcount )) + if ! "$@"; then + (( ++fail )) + printf 'expectation failed: did not succeed: %s\n' "$*" >&2 + fi +} + +EXPECT_failure() { + (( ++testcount )) + if "$@"; then + (( ++fail )) + printf 'expectation failed: did not fail: %s\n' "$*" >&2 + fi +} + +TEST_exit() { + local result + + trap -- EXIT + + (( fail == 0 )) && result=PASS || result=FAIL + + printf '%s: %s\n' "$result" "$1" + + exit $(( fail != 0 )) +} + +ASSERT_streq() { + if [[ $1 != "$2" ]]; then + printf 'assertion failed [line %d]: [[ %s = "%s" ]]\n' "$BASH_LINENO" "$1" "$2" >&2 + fi +} + +TEST_start() { + trap "TEST_exit '$1'" EXIT +} + +TEST_start "${0##*/test_}" + diff --git a/test/test_mangle b/test/test_mangle new file mode 100755 index 0000000..d9b61b1 --- /dev/null +++ b/test/test_mangle @@ -0,0 +1,8 @@ +#!/bin/bash + +. "${1:-./common}" +. ./test/common + +ASSERT_streq ' deleted' "$(unmangle "$(mangle ' deleted')")" +ASSERT_streq '\040deleted' "$(mangle "$(unmangle '\040deleted')")" +ASSERT_streq 'foo\011\040\011bar' "$(mangle $'foo\t \tbar')" diff --git a/test/test_option_twiddling b/test/test_option_twiddling new file mode 100755 index 0000000..214a63c --- /dev/null +++ b/test/test_option_twiddling @@ -0,0 +1,55 @@ +#!/bin/bash + +. "${1:-./common}" +. ./test/common + +optstring=rw,relatime,fd=29,pgrp=1,timeout=300,minproto=5,maxproto=5,direct +optstring_remove_option optstring fd +optstring_remove_option optstring pgrp=400 +ASSERT_streq "$optstring" 'rw,relatime,pgrp=1,timeout=300,minproto=5,maxproto=5,direct' + +optstring_append_option optstring pgrp=400 +optstring_append_option optstring pgrp=400 +optstring_append_option optstring pgrp=400 +ASSERT_streq "$optstring" 'rw,relatime,pgrp=1,timeout=300,minproto=5,maxproto=5,direct,pgrp=400' + +optstring_prepend_option optstring pgrp=600 +optstring_prepend_option optstring pgrp=600 +optstring_prepend_option optstring pgrp=600 +ASSERT_streq "$optstring" 'pgrp=600,rw,relatime,pgrp=1,timeout=300,minproto=5,maxproto=5,direct,pgrp=400' + +EXPECT_success optstring_has_option optstring pgrp=400 +EXPECT_success optstring_has_option optstring pgrp=1 +EXPECT_success optstring_has_option optstring pgrp +EXPECT_failure optstring_has_option optstring maxproto=6 +EXPECT_success optstring_has_option optstring maxproto + +EXPECT_failure optstring_get_option optstring proto +EXPECT_success optstring_get_option optstring maxproto +ASSERT_streq "$maxproto" "5" +EXPECT_success optstring_get_option optstring timeout +ASSERT_streq "$timeout" "300" + +optstring_remove_option optstring pgrp +ASSERT_streq "$optstring" 'rw,relatime,timeout=300,minproto=5,maxproto=5,direct' + +optstring_remove_option optstring minproto,relatime +ASSERT_streq "$optstring" 'rw,timeout=300,maxproto=5,direct' + +optstring_remove_option optstring ro,direct +ASSERT_streq "$optstring" 'rw,timeout=300,maxproto=5' + +optstring=,,,,,,defaults,,,,rw,,,,,,minproto=5,,, +optstring_normalize optstring +ASSERT_streq "$optstring" 'defaults,rw,minproto=5' + + +optstring= +optstring_normalize optstring +ASSERT_streq "$optstring" 'defaults' + + +EXPECT_success optstring_match_option key key=val +EXPECT_success optstring_match_option key=val key=val +EXPECT_failure optstring_match_option key=val key=val2 +EXPECT_failure optstring_match_option 'key=*' key=val2 diff --git a/test/test_valid_number_of_base b/test/test_valid_number_of_base new file mode 100755 index 0000000..5afdc59 --- /dev/null +++ b/test/test_valid_number_of_base @@ -0,0 +1,15 @@ +#!/bin/bash + +. "${1:-./common}" +. ./test/common + +EXPECT_success valid_number_of_base 16 feedfacebeef +EXPECT_failure valid_number_of_base 16 feedsfacebeef +EXPECT_success valid_number_of_base 16 1234567890 + +EXPECT_success valid_number_of_base 10 1234567890 +EXPECT_failure valid_number_of_base 10 1234.567890 +EXPECT_failure valid_number_of_base 10 1234567890abcdef + +EXPECT_success valid_number_of_base 18 1234567890abcdefgh +EXPECT_failure valid_number_of_base 18 1234567890abcdefghi diff --git a/zsh-completion b/zsh-completion new file mode 100644 index 0000000..bc4e4d3 --- /dev/null +++ b/zsh-completion @@ -0,0 +1,164 @@ +#compdef pacstrap genfstab arch-chroot + +_pacstrap_args=( + '-h[display help]' +) + +_pacstrap_args_nonh=( + '(-h --help)-c[Use the package cache on the host, rather than the target]' + '(-h --help)-d[Allow installation to a non-mountpoint directory]' + '(--help -h)-i[Avoid auto-confirmation of package selections]' +) + + +# builds command for invoking pacman in a _call_program command - extracts +# relevant options already specified (config file, etc) +# $cmd must be declared by calling function +_pacman_get_command() { + # this is mostly nicked from _perforce + cmd=( "pacman" "2>/dev/null") + integer i + for (( i = 2; i < CURRENT - 1; i++ )); do + if [[ ${words[i]} = "--config" || ${words[i]} = "--root" ]]; then + cmd+=( ${words[i,i+1]} ) + fi + done +} + +# provides completions for packages available from repositories +# these can be specified as either 'package' or 'repository/package' +_pacman_completions_all_packages() { + local -a cmd packages repositories packages_long + _pacman_get_command + + if compset -P1 '*/*'; then + packages=( $(_call_program packages $cmd[@] -Sql ${words[CURRENT]%/*}) ) + typeset -U packages + _wanted repo_packages expl "repository/package" compadd ${(@)packages} + else + packages=( $(_call_program packages $cmd[@] -Sql) ) + typeset -U packages + _wanted packages expl "packages" compadd - "${(@)packages}" + + repositories=(${(o)${${${(M)${(f)"$(</etc/pacman.conf)"}:#\[*}/\[/}/\]/}:#options}) + typeset -U repositories + _wanted repo_packages expl "repository/package" compadd -S "/" $repositories + fi +} + +_pacstrap_none(){ + _arguments -s : \ + "$_pacstrap_args[@]" \ + "$_longopts[@]" \ +} + +_genfstab_args=( + '-h[display help]' +) +_genfstab_args_nonh=( + '(--help -h)-p[Avoid printing pseudofs mounts]' + '(-U --help -h)-L[Use labels for source identifiers]' + '(-L --help -h)-U[Use UUIDs for source identifiers]' +) + +_arch_chroot_args=( '-h[display help]' ) + +_longopts=( '--help[display help]' ) + +_pacstrap(){ + if [[ -z ${(M)words:#--help} && -z ${(M)words:#-h} ]]; then + case $words[CURRENT] in + -c*|-d*|-i*) + _arguments -s "$_pacstrap_args_nonh[@]" + ;; + -*) + _arguments -s : \ + "$_pacstrap_args[@]" \ + "$_pacstrap_args_nonh[@]" \ + "$_longopts[@]" + ;; + --*) + _arguments -s : \ + "$_longopts[@]" + ;; + *) + _arguments -s : \ + "$_pacstrap_args[@]" \ + "$_pacstrap_args_nonh[@]" \ + "$_longopts[@]" \ + ":*:_path_files -/" \ + ":*:_pacman_completions_all_packages" + ;; + esac + else + return 1 + fi +} + +_genfstab(){ + if [[ -z ${(M)words:#--help} && -z ${(M)words:#-*h} ]]; then + case $words[CURRENT] in + -p*|-L*|-U*) + _arguments -s : \ + "$_genfstab_args_nonh[@]" + ;; + -*) + _arguments -s : \ + "$_genfstab_args[@]" \ + "$_genfstab_args_nonh[@]" \ + "$_longopts[@]" + ;; + --*) + _arguments -s : \ + "$_longopts[@]" + ;; + *) + _arguments \ + "$_genfstab_args[@]" \ + "$_genfstab_args_nonh[@]" \ + "$_longopts[@]" \ + ":*:_path_files -/" + ;; + esac + else + return 1 + fi +} + +_arch_chroot(){ + if [[ -z ${(M)words:#--help} && -z ${(M)words:#-*h} ]]; then + case $words[CURRENT] in + -*) + _arguments -s : \ + "$_arch_chroot_args[@]" \ + "$_longopts[@]" \ + ;; + --*) + _arguments -s : \ + "$_longopts[@]" + ;; + *) + _arguments \ + ':*:_path_files -/' + ;; + esac + else + return 1 + fi +} + +_install_scripts(){ + case "$service" in + pacstrap) + _pacstrap "$@" + ;; + genfstab) + _genfstab "$@";; + arch-chroot) + _arch_chroot "$@";; + *) + _message "Error";; + esac +} + +_install_scripts "$@" |
