Difference between pages "ZFS Install Guide" and "Awk by Example, Part 1"

(Difference between pages)
(Installing the ZFS userspace tools and kernel modules)
 
 
Line 1: Line 1:
== Introduction ==
+
{{Article
 +
|Subtitle=An intro to the great language with the strange name
 +
|Keywords=command,unix,variables,print,space
 +
|Author=Drobbins
 +
|Next in Series=Awk by Example, Part 2
 +
}}
 +
=== In defense of awk ===
 +
In this series of articles, I'm going to turn you into a proficient awk coder. I'll admit, awk doesn't have a very pretty or particularly "hip" name, and the GNU version of awk, called gawk, sounds downright weird. Those unfamiliar with the language may hear "awk" and think of a mess of code so backwards and antiquated that it's capable of driving even the most knowledgeable UNIX guru to the brink of insanity (causing him to repeatedly yelp "kill -9!" as he runs for coffee machine).
  
This tutorial will show you how to install Funtoo on ZFS (rootfs). This tutorial is meant to be an "overlay" over the [[Funtoo_Linux_Installation|Regular Funtoo Installation]]. Follow the normal installation and only use this guide for steps 2, 3, and 8.
+
Sure, awk doesn't have a great name. But it is a great language. Awk is geared toward text processing and report generation, yet features many well-designed features that allow for serious programming. And, unlike some languages, awk's syntax is familiar, and borrows some of the best parts of languages like C, python, and bash (although, technically, awk was created before both python and bash). Awk is one of those languages that, once learned, will become a key part of your strategic coding arsenal.
  
=== Introduction to ZFS ===
+
=== The first awk ===
 +
Let's go ahead and start playing around with awk to see how it works. At the command line, enter the following command:
  
Since ZFS is a new technology for Linux, it can be helpful to understand some of its benefits, particularly in comparison to BTRFS, another popular next-generation Linux filesystem:
+
<console>$##i## awk '{ print }' /etc/passwd</console>
  
* On Linux, the ZFS code can be updated independently of the kernel to obtain the latest fixes. btrfs is exclusive to Linux and you need to build the latest kernel sources to get the latest fixes.
+
You should see the contents of your /etc/passwd file appear before your eyes. Now, for an explanation of what awk did. When we called awk, we specified /etc/passwd as our input file. When we executed awk, it evaluated the print command for each line in /etc/passwd, in order. All output is sent to stdout, and we get a result identical to catting /etc/passwd.
  
* ZFS is supported on multiple platforms. The platforms with the best support are Solaris, FreeBSD and Linux. Other platforms with varying degrees of support are NetBSD, Mac OS X and Windows. btrfs is exclusive to Linux.
+
Now, for an explanation of the { print } code block. In awk, curly braces are used to group blocks of code together, similar to C. Inside our block of code, we have a single print command. In awk, when a print command appears by itself, the full contents of the current line are printed.
  
* ZFS has the Adaptive Replacement Cache replacement algorithm while btrfs uses the Linux kernel's Last Recently Used replacement algorithm. The former often has an overwhelmingly superior hit rate, which means fewer disk accesses.
+
Here is another awk example that does exactly the same thing:
  
* ZFS has the ZFS Intent Log and SLOG devices, which accelerates small synchronous write performance.
+
<console>$##i## awk '{ print $0 }' /etc/passwd</console>
  
* ZFS handles internal fragmentation gracefully, such that you can fill it until 100%. Internal fragmentation in btrfs can make btrfs think it is full at 10%. Btrfs has no automatic rebalancing code, so it requires a manual rebalance to correct it.
+
In awk, the $0 variable represents the entire current line, so print and print $0 do exactly the same thing. If you'd like, you can create an awk program that will output data totally unrelated to the input data. Here's an example:
  
* ZFS has raidz, which is like RAID 5/6 (or a hypothetical RAID 7 that supports 3 parity disks), except it does not suffer from the RAID write hole issue thanks to its use of CoW and a variable stripe size. btrfs gained integrated RAID 5/6 functionality in Linux 3.9. However, its implementation uses a stripe cache that can only partially mitigate the effect of the RAID write hole.
+
<console>$##i## awk '{ print "" }' /etc/passwd</console>
  
* ZFS send/receive implementation supports incremental update when doing backups. btrfs' send/receive implementation requires sending the entire snapshot.
+
Whenever you pass the "" string to the print command, it prints a blank line. If you test this script, you'll find that awk outputs one blank line for every line in your /etc/passwd file. Again, this is because awk executes your script for every line in the input file. Here's another example:
  
* ZFS supports data deduplication, which is a memory hog and only works well for specialized workloads. btrfs has no equivalent.
+
<console>$##i## awk '{ print "hiya" }' /etc/passwd</console>
  
* ZFS datasets have a hierarchical namespace while btrfs subvolumes have a flat namespace.
+
Running this script will fill your screen with hiya's. :)
  
* ZFS has the ability to create virtual block devices called zvols in its namespace. btrfs has no equivalent and must rely on the loop device for this functionality, which is cumbersome.
+
=== Multiple fields ===
 +
Awk is really good at handling text that has been broken into multiple logical fields, and allows you to effortlessly reference each individual field from inside your awk script. The following script will print out a list of all user accounts on your system:
  
The only area where btrfs is ahead of ZFS is in the area of small file
+
<console>$##i## awk -F":" '{ print $1 }' /etc/passwd</console>
efficiency. btrfs supports a feature called block suballocation, which
+
enables it to store small files far more efficiently than ZFS. It is
+
possible to use another filesystem (e.g. reiserfs) on top of a ZFS zvol
+
to obtain similar benefits (with arguably better data integrity) when
+
dealing with many small files (e.g. the portage tree).
+
  
=== Disclaimers ===
+
Above, when we called awk, we use the -F option to specify ":" as the field separator. When awk processes the print $1 command, it will print out the first field that appears on each line in the input file. Here's another example:
  
{{fancywarning|This guide is a work in progress. Expect some quirks.}}
+
<console>$##i## awk -F":" '{ print $1 $3 }' /etc/passwd</console>
{{fancyimportant|'''Since ZFS was really designed for 64 bit systems, we are only recommending and supporting 64 bit platforms and installations. We will not be supporting 32 bit platforms'''!}}
+
  
== Video Tutorial ==
+
Here's an excerpt of the output from this script:
 +
<pre>
 +
halt7
 +
operator11
 +
root0
 +
shutdown6
 +
sync5
 +
bin1
 +
....etc.
 +
</pre>
 +
As you can see, awk prints out the first and third fields of the /etc/passwd file, which happen to be the username and uid fields respectively. Now, while the script did work, it's not perfect -- there aren't any spaces between the two output fields! If you're used to programming in bash or python, you may have expected the print $1 $3 command to insert a space between the two fields. However, when two strings appear next to each other in an awk program, awk concatenates them without adding an intermediate space. The following command will insert a space between both fields:
  
As a companion to the install instructions below, a YouTube video ZFS install tutorial is now available:
+
<console>$##i## awk -F":" '{ print $1 " " $3 }' /etc/passwd</console>
  
{{#widget:YouTube|id=kxEdSXwU0ZI|width=640|height=360}}
+
When you call print this way, it'll concatenate $1, " ", and $3, creating readable output. Of course, we can also insert some text labels if needed:
  
== Downloading the ISO (With ZFS) ==
+
<console>$##i## awk -F":" '{ print "username: " $1 "\t\tuid:" $3 }' /etc/passwd</console>
In order for us to install Funtoo on ZFS, you will need an environment that provides the ZFS tools. Therefore we will download a customized version of System Rescue CD with ZFS already included. When booting, use the "alternate"-kernel. The ZFS-module won't work with the default kernel.
+
  
 +
This will cause the output to be:
 
<pre>
 
<pre>
Name: sysresccd-3.8.1_zfs_0.6.2.iso  (510 MB)
+
username: halt    uid:7
Release Date: 2013-11-03
+
username: operator uid:11
md5sum aa33ef61c5d85ad564372327940498c3
+
username: root    uid:0
 +
username: shutdown uid:6
 +
username: sync    uid:5
 +
username: bin      uid:1
 +
....etc.  
 
</pre>
 
</pre>
  
 +
=== External Scripts ===
 +
Passing your scripts to awk as a command line argument can be very handy for small one-liners, but when it comes to complex, multi-line programs, you'll definitely want to compose your script in an external file. Awk can then be told to source this script file by passing it the -f option:
  
'''[http://ftp.osuosl.org/pub/funtoo/distfiles/sysresccd/ Download System Rescue CD with ZFS]'''<br />
+
<console>$##i## awk -f myscript.awk myfile.in </console>
  
== Creating a bootable USB from ISO ==
+
Putting your scripts in their own text files also allows you to take advantage of additional awk features. For example, this multi-line script does the same thing as one of our earlier one-liners, printing out the first field of each line in /etc/passwd:
After you download the iso, you can do the following steps to create a bootable USB:
+
<pre>
 +
BEGIN {
 +
        FS=":"
 +
}
 +
{ print $1 }
 +
</pre>
 +
The difference between these two methods has to do with how we set the field separator. In this script, the field separator is specified within the code itself (by setting the FS variable), while our previous example set FS by passing the -F":" option to awk on the command line. It's generally best to set the field separator inside the script itself, simply because it means you have one less command line argument to remember to type. We'll cover the FS variable in more detail later in this article.
  
<console>
+
It is also possible to make the script directly executable, by placing a "#!/usr/bin/awk -f" at the top of the file, as follows:
Make a temporary directory
+
<pre>
# ##i##mkdir /tmp/loop
+
#!/usr/bin/awk -f
 +
BEGIN {
 +
FS=":"
 +
}
 +
{ print $1 }
 +
</pre>
 +
Next, the script must be made executable by setting the script file's execute bit:
  
Mount the iso
+
<console>$##i## chmod +x myscript.awk</console>
# ##i##mount -o ro,loop /root/sysresccd-3.7.1_zfs_0.6.2.iso /tmp/loop
+
  
Run the usb installer
+
Now, you should be able to execute the script as follows:
# ##i##/tmp/loop/usb_inst.sh
+
</console>
+
  
That should be all you need to do to get your flash drive working.
+
<console>$##i## ./myscript.awk myfile.in</console>
  
When you are booting into system rescue cd, make sure you select the '''alternative 64 bit kernel'''. ZFS support was specifically added to the alternative 64 bit kernel rather than the standard 64 bit kernel.
+
=== The BEGIN and END blocks ===
 +
Normally, awk executes each block of your script's code once for each input line. However, there are many programming situations where you may need to execute initialization code before awk begins processing the text from the input file. For such situations, awk allows you to define a BEGIN block. We used a BEGIN block in the previous example. Because the BEGIN block is evaluated before awk starts processing the input file, it's an excellent place to initialize the FS (field separator) variable, print a heading, or initialize other global variables that you'll reference later in the program.
  
== Creating partitions ==
+
Awk also provides another special block, called the END block. Awk executes this block after all lines in the input file have been processed. Typically, the END block is used to perform final calculations or print summaries that should appear at the end of the output stream.
There are two ways to partition your disk: You can use your entire drive and let ZFS automatically partition it for you, or you can do it manually.
+
  
We will be showing you how to partition it '''manually''' because if you partition it manually you get to create your own layout, you get to have your own separate /boot partition (Which is nice since not every bootloader supports booting from ZFS pools), and you get to boot into RAID10, RAID5 (RAIDZ) pools and any other layouts due to you having a separate /boot partition.
+
=== Regular expressions and blocks ===
 +
Awk allows the use of regular expressions to selectively execute an individual block of code, depending on whether or not the regular expression matches the current line. Here's an example script that outputs only those lines that contain the character sequence foo:
  
==== gdisk (GPT Style) ====
+
<pre>/foo/ { print }</pre>
  
'''A Fresh Start''':
+
Of course, you can use more complicated regular expressions. Here's a script that will print only lines that contain a floating point number:
  
First lets make sure that the disk is completely wiped from any previous disk labels and partitions.
+
<pre>/[0-9]+\.[0-9]*/ { print }</pre>
We will also assume that <tt>/dev/sda</tt> is the target drive.<br />
+
  
<console>
+
=== Expressions and blocks ===
# ##i##gdisk /dev/sda
+
There are many other ways to selectively execute a block of code. We can place any kind of boolean expression before a code block to control when a particular block is executed. Awk will execute a code block only if the preceding boolean expression evaluates to true. The following example script will output the third field of all lines that have a first field equal to fred. If the first field of the current line is not equal to fred, awk will continue processing the file and will not execute the print statement for the current line:
  
Command: ##i##x ↵
+
<pre>$1 == "fred" { print $3 }</pre>
Expert command: ##i##z ↵
+
About to wipe out GPT on /dev/sda. Proceed?: ##i##y ↵
+
GPT data structures destroyed! You may now partition the disk using fdisk or other utilities.
+
Blank out MBR?: ##i##y ↵
+
</console>
+
  
{{fancywarning|This is a destructive operation. Make sure you really don't want anything on this disk.}}
+
Awk offers a full selection of comparison operators, including the usual "==", "<", ">", "<=", ">=", and "!=". In addition, awk provides the "~" and "!~" operators, which mean "matches" and "does not match". They're used by specifying a variable on the left side of the operator, and a regular expression on the right side. Here's an example that will print only the third field on the line if the fifth field on the same line contains the character sequence root:
  
Now that we have a clean drive, we will create the new layout.
+
<pre>$5 ~ /root/ { print $3 }</pre>
  
'''Create Partition 1''' (boot):
+
=== Conditional statements ===
<console>
+
Awk also offers very nice C-like if statements. If you'd like, you could rewrite the previous script using an if statement:
Command: ##i##n ↵
+
<pre>
Partition Number: ##i##↵
+
{
First sector: ##i##↵
+
    if ( $5 ~ /root/ ) {
Last sector: ##i##+250M ↵
+
        print $3
Hex Code: ##i##↵
+
    }
</console>
+
}
 +
</pre>
 +
Both scripts function identically. In the first example, the boolean expression is placed outside the block, while in the second example, the block is executed for every input line, and we selectively perform the print command by using an if statement. Both methods are available, and you can choose the one that best meshes with the other parts of your script.
  
'''Create Partition 2''' (BIOS Boot Partition):
+
Here's a more complicated example of an awk if statement. As you can see, even with complex, nested conditionals, if statements look identical to their C counterparts:
<console>Command: ##i##n ↵
+
<pre>
Partition Number: ##i##↵
+
{
First sector: ##i##↵
+
    if ( $1 == "foo" ) {
Last sector: ##i##+32M ↵
+
        if ( $2 == "foo" ) {
Hex Code: ##i##EF02 ↵
+
            print "uno"
</console>
+
        } else {
 +
            print "one"
 +
        }
 +
    } else if ($1 == "bar" ) {
 +
        print "two"
 +
    } else {
 +
        print "three"
 +
    }
 +
}
 +
</pre>
 +
Using if statements, we can also transform this code:
 +
<pre>
 +
! /matchme/ { print $1 $3 $4 }
 +
</pre>
 +
to this:
 +
<pre>
 +
{
 +
    if ( $0 !~ /matchme/ ) {
 +
        print $1 $3 $4
 +
    }
 +
}
 +
</pre>
 +
Both scripts will output only those lines that don't contain a matchme character sequence. Again, you can choose the method that works best for your code. They both do the same thing.
  
'''Create Partition 3''' (ZFS):
+
Awk also allows the use of boolean operators "||" (for "logical or") and "&&"(for "logical and") to allow the creation of more complex boolean expressions:
<console>Command: ##i##n ↵
+
<pre>
Partition Number: ##i##↵
+
( $1 == "foo" ) && ( $2 == "bar" ) { print }
First sector: ##i##↵
+
</pre>
Last sector: ##i##↵
+
This example will print only those lines where field one equals foo and field two equals bar.
Hex Code: ##i##bf00 ↵
+
  
Command: ##i##p ↵
+
=== Numeric variables! ===
 +
So far, we've either printed strings, the entire line, or specific fields. However, awk also allows us to perform both integer and floating point math. Using mathematical expressions, it's very easy to write a script that counts the number of blank lines in a file. Here's one that does just that:
 +
<pre>
 +
BEGIN { x=0 }
 +
/^$/  { x=x+1 }
 +
END  { print "I found " x " blank lines. :)" }
 +
</pre>
 +
In the BEGIN block, we initialize our integer variable x to zero. Then, each time awk encounters a blank line, awk will execute the x=x+1 statement, incrementing x. After all the lines have been processed, the END block will execute, and awk will print out a final summary, specifying the number of blank lines it found.
  
Number  Start (sector)    End (sector)  Size      Code  Name
+
=== Stringy variables ===
  1           2048          514047  250.0 MiB  8300  Linux filesystem
+
One of the neat things about awk variables is that they are "simple and stringy." I consider awk variables "stringy" because all awk variables are stored internally as strings. At the same time, awk variables are "simple" because you can perform mathematical operations on a variable, and as long as it contains a valid numeric string, awk automatically takes care of the string-to-number conversion steps. To see what I mean, check out this example:
  2         514048          579583  32.0 MiB    EF02  BIOS boot partition
+
<pre>
  3          579584      1953525134  931.2 GiB  BF00  Solaris root
+
x="1.01"
 +
# We just set x to contain the *string* "1.01"
 +
x=x+1
 +
# We just added one to a *string*
 +
print x
 +
# Incidentally, these are comments :)
 +
</pre>
 +
Awk will output:
 +
<pre>
 +
2.01
 +
</pre>
 +
Interesting! Although we assigned the string value 1.01 to the variable x, we were still able to add one to it. We wouldn't be able to do this in bash or python. First of all, bash doesn't support floating point arithmetic. And, while bash has "stringy" variables, they aren't "simple"; to perform any mathematical operations, bash requires that we enclose our math in an ugly $( ) construct. If we were using python, we would have to explicitly convert our 1.01 string to a floating point value before performing any arithmetic on it. While this isn't difficult, it's still an additional step. With awk, it's all automatic, and that makes our code nice and clean. If we wanted to square and add one to the first field in each input line, we would use this script:
 +
<pre>
 +
{ print ($1^2)+1 }
 +
</pre>
 +
If you do a little experimenting, you'll find that if a particular variable doesn't contain a valid number, awk will treat that variable as a numerical zero when it evaluates your mathematical expression.
  
Command: ##i##w ↵
+
=== Lots of operators ===
</console>
+
Another nice thing about awk is its full complement of mathematical operators. In addition to standard addition, subtraction, multiplication, and division, awk allows us to use the previously demonstrated exponent operator "^", the modulo (remainder) operator "%", and a bunch of other handy assignment operators borrowed from C.
  
 +
These include pre- and post-increment/decrement ( i++, --foo ), add/sub/mult/div assign operators ( a+=3, b*=2, c/=2.2, d-=6.2 ). But that's not all -- we also get handy modulo/exponent assign ops as well ( a^=2, b%=4 ).
  
=== Format your boot volume ===
+
=== Field separators ===
Format your separate /boot partition:
+
Awk has its own complement of special variables. Some of them allow you to fine-tune how awk functions, while others can be read to glean valuable information about the input. We've already touched on one of these special variables, FS. As mentioned earlier, this variable allows you to set the character sequence that awk expects to find between fields. When we were using /etc/passwd as input, FS was set to ":". While this did the trick, FS allows us even more flexibility.
<console># ##i##mkfs.ext2 /dev/sda1</console>
+
  
=== Encryption (Optional) ===
+
The FS value is not limited to a single character; it can also be set to a regular expression, specifying a character pattern of any length. If you're processing fields separated by one or more tabs, you'll want to set FS like so:
If you want encryption, then create your encrypted vault(s) now by doing the following:
+
<pre>
 +
FS="\t+"
 +
</pre>
 +
Above, we use the special "+" regular expression character, which means "one or more of the previous character".
  
<console>
+
If your fields are separated by whitespace (one or more spaces or tabs), you may be tempted to set FS to the following regular expression:
# ##i##cryptsetup luksFormat /dev/sda3
+
<pre>
# ##i##cryptsetup luksOpen /dev/sda3 vault_1
+
FS="[[:space:]]+"
</console>
+
</pre>
 +
While this assignment will do the trick, it's not necessary. Why? Because by default, FS is set to a single space character, which awk interprets to mean "one or more spaces or tabs." In this particular example, the default FS setting was exactly what you wanted in the first place!
  
=== Create the zpool ===
+
Complex regular expressions are no problem. Even if your records are separated by the word "foo," followed by three digits, the following regular expression will allow your data to be parsed properly:
We will first create the pool. The pool will be named `tank` and the disk will be aligned to 4096 (using ashift=12)
+
<pre>
<console># ##i##zpool create -f -o ashift=12 -o cachefile= -O compression=on -m none -R /mnt/funtoo tank /dev/sda3</console>
+
FS="foo[0-9][0-9][0-9]"
 +
</pre>
  
{{fancyimportant|If you are using encrypted root, change '''/dev/sda3 to /dev/mapper/vault_1'''.}}
+
=== Number of fields ===
 
+
The next two variables we're going to cover are not normally intended to be written to, but are normally read and used to gain useful information about the input. The first is the NF variable, also called the "number of fields" variable. Awk will automatically set this variable to the number of fields in the current record. You can use the NF variable to display only certain input lines:
{{fancynote|'''ashift<nowiki>=</nowiki>12''' should be use if you have a newer, advanced format disk that has a sector size of 4096 bytes. If you have an older disk with 512 byte sectors, you should use '''ashift<nowiki>=</nowiki>9''' or don't add the option for auto detection}}
+
<pre>
 
+
NF == 3 { print "this particular record has three fields: " $0 }
{{fancynote|If you have a previous pool that you would like to import, you can do a: '''zpool import -f -R /mnt/funtoo <pool_name>'''}}
+
</pre>
 
+
Of course, you can also use the NF variable in conditional statements, as follows:
=== Create the zfs datasets ===
+
<pre>
We will now create some datasets. For this installation, we will create a small but future proof amount of datasets. We will have a dataset for the OS (/), and your swap. We will also show you how to create some optional datasets: /home, /var, /usr/src, and /usr/portage.
+
{
 
+
    if ( NF > 2 ) {
<console>
+
        print $1 " " $2 ":" $3
Create some empty containers for organization purposes, and make the dataset that will hold /
+
    }
# ##i##zfs create -p tank/os/funtoo
+
# ##i##zfs create -o mountpoint=/ tank/os/funtoo/root
+
 
+
Optional, but recommended datasets: /home
+
# ##i##zfs create -o mountpoint=/home tank/os/funtoo/home
+
 
+
Optional datasets: /usr/src, /usr/portage/{distfiles,packages}
+
# ##i##zfs create -o mountpoint=/usr/src tank/os/funtoo/src
+
# ##i##zfs create -o mountpoint=/usr/portage -o compression=off tank/os/funtoo/portage
+
# ##i##zfs create -o mountpoint=/usr/portage/distfiles tank/os/funtoo/portage/distfiles
+
# ##i##zfs create -o mountpoint=/usr/portage/packages tank/os/funtoo/portage/packages
+
</console>
+
 
+
=== Create your swap zvol ===
+
'''Make your swap +1G greater than your RAM. An 8G machine would have 9G of SWAP (This is kinda big though). For machines with this much memory, You could just make it 2G if you don't have any problems.'''
+
<console>
+
# ##i##zfs create -o sync=always -o primarycache=metadata -o secondarycache=none -o volblocksize=4K -V 1G tank/swap
+
</console>
+
 
+
=== Format your swap zvol ===
+
<console>
+
# ##i##mkswap -f /dev/zvol/tank/swap
+
# ##i##swapon /dev/zvol/tank/swap
+
</console>
+
 
+
Now we will continue to install funtoo.
+
 
+
== Installing Funtoo ==
+
[[Funtoo_Linux_Installation|Download and extract the Funtoo stage3 and continue installation as normal.]]
+
 
+
Then once you've extracted the stage3, chroot into your new funtoo environment:
+
<console>
+
Go into the directory that you will chroot into
+
# ##i##cd /mnt/funtoo
+
 
+
Mount your boot drive
+
# ##i##mount /dev/sda1 /mnt/funtoo/boot
+
 
+
Bind the kernel related directories
+
# ##i##mount -t proc none /mnt/funtoo/proc
+
# ##i##mount --rbind /dev /mnt/funtoo/dev
+
# ##i##mount --rbind /sys /mnt/funtoo/sys
+
 
+
Copy network settings
+
# ##i##cp /etc/resolv.conf /mnt/funtoo/etc/
+
 
+
chroot into your new funtoo environment
+
# ##i##env -i HOME=/root TERM=$TERM chroot /mnt/funtoo /bin/bash --login
+
 
+
Place your mountpoints into your /etc/mtab file
+
# ##i##cat /proc/mounts > /etc/mtab
+
 
+
Sync your tree
+
# ##i##emerge --sync
+
</console>
+
 
+
=== Add filesystems to /etc/fstab ===
+
 
+
Before we continue to compile and or install our kernel in the next step, we will edit the /etc/fstab file because if we decide to install our kernel through portage, portage will need to know where is your /boot so that it can place the files in there. We also need to update /etc/mtab so our system knows what is mounted
+
 
+
<console>
+
# ##i##nano /etc/fstab
+
 
+
# <fs>                  <mountpoint>    <type>          <opts>          <dump/pass>
+
 
+
/dev/sda1              /boot          ext2            defaults        0 2
+
/dev/zvol/tank/swap    none            swap            sw              0 0
+
</console>
+
 
+
== Kernel Configuration ==
+
To speed up this step, you can install "bliss-kernel" since it's already properly configured for ZFS and a lot of other configurations. The kernel is also compiled and ready to go. To install 'bliss-kernel' type the following:
+
 
+
<console>
+
# ##i##emerge bliss-kernel
+
</console>
+
 
+
Now make sure that your /usr/src/linux symlink is pointing to this kernel by typing the following:
+
 
+
<console>
+
# ##i##eselect kernel list
+
Available kernel symlink targets:
+
[1]  linux-3.10.10-FB.01 *
+
</console>
+
 
+
You should see a star next to the bliss-kernel version you installed. In this case it was 3.10.10-FB.01. If it's not set, you can type '''eselect kernel set #'''.
+
 
+
== Installing the ZFS userspace tools and kernel modules ==
+
Emerge {{Package|sys-fs/zfs}}, {{Package|sys-kernel/spl}}, and {{Package|sys-fs/zfs-kmod}}:
+
<console># ##i##emerge -av zfs spl zfs-kmod</console>
+
 
+
{{Note}}(spl = Solaris Porting Layer)
+
 
+
Check to make sure that the zfs tools are working, the <code>zpool.cache</code> file that you copied before should be displayed.
+
 
+
<console>
+
# ##i##zpool status
+
# ##i##zfs list
+
</console>
+
 
+
If everything worked, continue.
+
 
+
== Install the bootloader ==
+
=== GRUB 2 ===
+
Before you do this, make sure this checklist is followed:
+
* Installed kernel and kernel modules
+
* Installed zfs package from the tree
+
* <code>/dev</code>, <code>/proc</code>, <code>/sys</code> are mounted in the chroot environment
+
 
+
Once all this is checked, let's install grub2. First we need to enable the "libzfs" use flag so zfs support is compiled for grub2.
+
 
+
<console># ##i##echo "sys-boot/grub libzfs" >> /etc/portage/package.use</console>
+
 
+
Then we will compile grub2:
+
 
+
<console># ##i##emerge -av grub</console>
+
 
+
Once this is done, you can check that grub is version 2.00 by doing the following command:
+
<console>
+
# ##i##grub-install --version
+
grub-install (GRUB) 2.00
+
</console>
+
 
+
Now try to install {{Package|sys-boot/grub}}:
+
<console>
+
# ##i##grub-install --recheck /dev/sda
+
</console>
+
 
+
You should receive the following message:
+
<console>
+
Installation finished. No error reported.
+
</console>
+
 
+
If not, then go back to the above checklist.
+
 
+
=== LILO ===
+
Before you do this, make sure the following checklist is followed:
+
* <code>/dev</code>, <tt>/proc</tt> and <tt>/sys</tt> are mounted.
+
* Installed the {{Package|sys-fs/zfs}} package from the tree.
+
Once the above requirements are met, LILO can be installed.
+
 
+
Now we will install {{Package|sys-boot/lilo}}.
+
<console># ##i##emerge -av sys-boot/lilo</console>
+
Once the installation of LILO is complete we will need to edit the lilo.conf file.
+
{{File
+
|/etc/lilo.conf|<pre>
+
boot=/dev/sda
+
prompt
+
timeout=4
+
default=Funtoo
+
 
+
image=/boot/bzImage
+
      label=Funtoo
+
      read-only
+
      append="root=tank/os/funtoo/root"
+
      initrd=/boot/initramfs
+
</pre>}}
+
All that is left now is to install the bootcode to the MBR.
+
 
+
This can be accomplished by running:
+
<console># ##i##/sbin/lilo</console>
+
If it is successful you should see:
+
<console>
+
Warning: LBA32 addressing assumed
+
Added Funtoo + *
+
One warning was issued
+
</console>
+
 
+
== Create the initramfs ==
+
There are two ways to do this, you can use genkernel, or you can use my bliss initramfs creator. I will show you both.
+
 
+
=== genkernel ===
+
<console>
+
# ##i##emerge -av sys-kernel/genkernel
+
# You only need to add --luks if you used encryption
+
# ##i##genkernel --zfs --luks initramfs
+
</console>
+
 
+
=== Bliss Initramfs Creator ===
+
If you are encrypting your drives, then add the "luks" use flag to your package.use before emerging:
+
 
+
<console>
+
# ##i##echo "sys-kernel/bliss-initramfs luks" >> /etc/portage/package.use
+
</console>
+
 
+
Now install the creator:
+
 
+
<console>
+
# ##i##emerge bliss-initramfs
+
</console>
+
 
+
 
+
Then go into the install directory, run the script as root, and place it into /boot:
+
<console># ##i##cd /opt/bliss-initramfs
+
# ##i##./createInit
+
# ##i##mv initrd-<kernel_name> /boot
+
</console>
+
'''<kernel_name>''' is the name of what you selected in the initramfs creator, and the name of the outputted file.
+
 
+
== Using boot-update ==
+
=== /boot on separate partition ===
+
If you created a separate non-zfs partition for boot then configuring boot-update is almost exactly the same as a normal install except that auto detection for root does not work. You must tell boot-update what your root is.
+
==== Genkernel ====
+
If your using genkernel you must add 'real_root=ZFS=<root>' and 'dozfs' to your params.
+
Example entry for boot.conf:
+
<console>
+
"Funtoo ZFS" {
+
        kernel vmlinuz[-v]
+
        initrd initramfs-genkernel-x86_64[-v]
+
        params real_root=ZFS=tank/os/funtoo/root
+
        params += dozfs=force
+
        # Also add 'params += crypt_root=/dev/sda3' if you used encryption
+
        # Adjust the above setting to your system if needed
+
 
}
 
}
</console>
+
</pre>
  
==== Bliss Initramfs Creator ====
+
=== Record number ===
If you used the Bliss Initramfs Creator then all you need to do is add 'root=<root>' to your params.
+
The record number (NR) is another handy variable. It will always contain the number of the current record (awk counts the first record as record number 1). Up until now, we've been dealing with input files that contain one record per line. For these situations, NR will also tell you the current line number. However, when we start to process multi-line records later in the series, this will no longer be the case, so be careful! NR can be used like the NF variable to print only certain lines of the input:
Example entry for boot.conf:
+
<pre>
<console>
+
(NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" }
"Funtoo ZFS" {
+
</pre>
        kernel vmlinuz[-v]
+
<pre>
        initrd initrd[-v]
+
{
        params root=tank/os/funtoo/root quiet
+
    #skip header
        # If you have an encrypted device with a regular passphrase,
+
    if ( NR > 10 ) {
         # you can add the following line
+
         print "ok, now for the real information!"
        params += enc_root=/dev/sda3 enc_type=pass
+
    }
 
}
 
}
</console>
+
</pre>
 
+
Awk provides additional variables that can be used for a variety of purposes. We'll cover more of these variables in later articles.
After editing /etc/boot.conf, you just need to run boot-update to update grub.cfg
+
<console># ##i##boot-update</console>
+
 
+
=== /boot on ZFS ===
+
TBC - pending update to boot-update to support this
+
 
+
== Final configuration ==
+
=== Add the zfs tools to openrc ===
+
<console># ##i##rc-update add zfs boot</console>
+
 
+
=== Clean up and reboot ===
+
We are almost done, we are just going to clean up, '''set our root password''', and unmount whatever we mounted and get out.
+
 
+
<console>
+
Delete the stage3 tarball that you downloaded earlier so it doesn't take up space.
+
# ##i##cd /
+
# ##i##rm stage3-latest.tar.xz
+
 
+
Set your root password
+
# ##i##passwd
+
>> Enter your password, you won't see what you are writing (for security reasons), but it is there!
+
 
+
Get out of the chroot environment
+
# ##i##exit
+
 
+
Unmount all the kernel filesystem stuff and boot (if you have a separate /boot)
+
# ##i##umount -l proc dev sys boot
+
 
+
Turn off the swap
+
# ##i##swapoff /dev/zvol/tank/swap
+
 
+
Export the zpool
+
# ##i##cd /
+
# ##i##zpool export tank
+
 
+
Reboot
+
# ##i##reboot
+
</console>
+
 
+
{{fancyimportant|'''Don't forget to set your root password as stated above before exiting chroot and rebooting. If you don't set the root password, you won't be able to log into your new system.'''}}
+
 
+
and that should be enough to get your system to boot on ZFS.
+
 
+
== After reboot ==
+
=== Create initial ZFS Snapshot ===
+
Continue to set up anything you need in terms of /etc configurations. Once you have everything the way you like it, take a snapshot of your system. You will be using this snapshot to revert back to this state if anything ever happens to your system down the road. The snapshots are cheap, and almost instant.
+
 
+
To take the snapshot of your system, type the following:
+
<console># ##i##zfs snapshot -r tank@install</console>
+
 
+
To see if your snapshot was taken, type:
+
<console># ##i##zfs list -t snapshot</console>
+
 
+
If your machine ever fails and you need to get back to this state, just type (This will only revert your / dataset while keeping the rest of your data intact):
+
<console># ##i##zfs rollback tank/os/funtoo/root@install</console>
+
  
{{fancyimportant|'''For a detailed overview, presentation of ZFS' capabilities, as well as usage examples, please refer to the [[ZFS_Fun|ZFS Fun]] page.'''}}
+
We've come to the end of our initial exploration of awk. As the series continues, I'll demonstrate more advanced awk functionality, and we'll end the series with a real-world awk application.
  
[[Category:HOWTO]]
+
== Resources ==
[[Category:Filesystems]]
+
[[Category:Featured]]
+
  
__NOTITLE__
+
* Read Daniel's other awk articles on Funtoo: Awk By Example, [[Awk by example, Part2 |Part 2]] and [[Awk by example, Part3 |Part 3]].
 +
* If you'd like a good old-fashioned book, [http://www.oreilly.com/catalog/sed2/ O'Reilly's sed & awk, 2nd Edition] is a wonderful choice.
 +
* Be sure to check out the [http://www.faqs.org/faqs/computer-lang/awk/faq/ comp.lang.awk FAQ]. It also contains lots of additional awk links.
 +
* Patrick Hartigan's [http://sparky.rice.edu/~hartigan/awk.html awk tutorial] is packed with handy awk scripts.
 +
* [http://www.tasoft.com/tawk.html Thompson's TAWK Compiler] compiles awk scripts into fast binary executables. Versions are available for Windows, OS/2, DOS, and UNIX.
 +
* [http://www.gnu.org/software/gawk/manual/gawk.html The GNU Awk User's Guide] is available for online reference.
 +
* [http://www.folkstalk.com/2011/12/good-examples-of-awk-command-in-unix.html Awk Command] daily useful examples.
 +
[[Category:Linux Core Concepts]]
 +
[[Category:Articles]]
 +
{{ArticleFooter}}

Latest revision as of 15:54, January 5, 2015

An intro to the great language with the strange name

Support Funtoo and help us grow! Donate $15 per month and get a free SSD-based Funtoo Virtual Container.

In defense of awk

In this series of articles, I'm going to turn you into a proficient awk coder. I'll admit, awk doesn't have a very pretty or particularly "hip" name, and the GNU version of awk, called gawk, sounds downright weird. Those unfamiliar with the language may hear "awk" and think of a mess of code so backwards and antiquated that it's capable of driving even the most knowledgeable UNIX guru to the brink of insanity (causing him to repeatedly yelp "kill -9!" as he runs for coffee machine).

Sure, awk doesn't have a great name. But it is a great language. Awk is geared toward text processing and report generation, yet features many well-designed features that allow for serious programming. And, unlike some languages, awk's syntax is familiar, and borrows some of the best parts of languages like C, python, and bash (although, technically, awk was created before both python and bash). Awk is one of those languages that, once learned, will become a key part of your strategic coding arsenal.

The first awk

Let's go ahead and start playing around with awk to see how it works. At the command line, enter the following command:

$ awk '{ print }' /etc/passwd

You should see the contents of your /etc/passwd file appear before your eyes. Now, for an explanation of what awk did. When we called awk, we specified /etc/passwd as our input file. When we executed awk, it evaluated the print command for each line in /etc/passwd, in order. All output is sent to stdout, and we get a result identical to catting /etc/passwd.

Now, for an explanation of the { print } code block. In awk, curly braces are used to group blocks of code together, similar to C. Inside our block of code, we have a single print command. In awk, when a print command appears by itself, the full contents of the current line are printed.

Here is another awk example that does exactly the same thing:

$ awk '{ print $0 }' /etc/passwd

In awk, the $0 variable represents the entire current line, so print and print $0 do exactly the same thing. If you'd like, you can create an awk program that will output data totally unrelated to the input data. Here's an example:

$ awk '{ print "" }' /etc/passwd

Whenever you pass the "" string to the print command, it prints a blank line. If you test this script, you'll find that awk outputs one blank line for every line in your /etc/passwd file. Again, this is because awk executes your script for every line in the input file. Here's another example:

$ awk '{ print "hiya" }' /etc/passwd

Running this script will fill your screen with hiya's. :)

Multiple fields

Awk is really good at handling text that has been broken into multiple logical fields, and allows you to effortlessly reference each individual field from inside your awk script. The following script will print out a list of all user accounts on your system:

$ awk -F":" '{ print $1 }' /etc/passwd

Above, when we called awk, we use the -F option to specify ":" as the field separator. When awk processes the print $1 command, it will print out the first field that appears on each line in the input file. Here's another example:

$ awk -F":" '{ print $1 $3 }' /etc/passwd

Here's an excerpt of the output from this script:

halt7 
operator11 
root0 
shutdown6 
sync5 
bin1 
....etc. 

As you can see, awk prints out the first and third fields of the /etc/passwd file, which happen to be the username and uid fields respectively. Now, while the script did work, it's not perfect -- there aren't any spaces between the two output fields! If you're used to programming in bash or python, you may have expected the print $1 $3 command to insert a space between the two fields. However, when two strings appear next to each other in an awk program, awk concatenates them without adding an intermediate space. The following command will insert a space between both fields:

$ awk -F":" '{ print $1 " " $3 }' /etc/passwd

When you call print this way, it'll concatenate $1, " ", and $3, creating readable output. Of course, we can also insert some text labels if needed:

$ awk -F":" '{ print "username: " $1 "\t\tuid:" $3 }' /etc/passwd

This will cause the output to be:

username: halt     uid:7 
username: operator uid:11 
username: root     uid:0 
username: shutdown uid:6 
username: sync     uid:5 
username: bin      uid:1 
....etc. 

External Scripts

Passing your scripts to awk as a command line argument can be very handy for small one-liners, but when it comes to complex, multi-line programs, you'll definitely want to compose your script in an external file. Awk can then be told to source this script file by passing it the -f option:

$ awk -f myscript.awk myfile.in 

Putting your scripts in their own text files also allows you to take advantage of additional awk features. For example, this multi-line script does the same thing as one of our earlier one-liners, printing out the first field of each line in /etc/passwd:

BEGIN { 
        FS=":" 
} 
{ print $1 } 

The difference between these two methods has to do with how we set the field separator. In this script, the field separator is specified within the code itself (by setting the FS variable), while our previous example set FS by passing the -F":" option to awk on the command line. It's generally best to set the field separator inside the script itself, simply because it means you have one less command line argument to remember to type. We'll cover the FS variable in more detail later in this article.

It is also possible to make the script directly executable, by placing a "#!/usr/bin/awk -f" at the top of the file, as follows:

#!/usr/bin/awk -f
BEGIN {
	FS=":"
}
{ print $1 }

Next, the script must be made executable by setting the script file's execute bit:

$ chmod +x myscript.awk

Now, you should be able to execute the script as follows:

$ ./myscript.awk myfile.in

The BEGIN and END blocks

Normally, awk executes each block of your script's code once for each input line. However, there are many programming situations where you may need to execute initialization code before awk begins processing the text from the input file. For such situations, awk allows you to define a BEGIN block. We used a BEGIN block in the previous example. Because the BEGIN block is evaluated before awk starts processing the input file, it's an excellent place to initialize the FS (field separator) variable, print a heading, or initialize other global variables that you'll reference later in the program.

Awk also provides another special block, called the END block. Awk executes this block after all lines in the input file have been processed. Typically, the END block is used to perform final calculations or print summaries that should appear at the end of the output stream.

Regular expressions and blocks

Awk allows the use of regular expressions to selectively execute an individual block of code, depending on whether or not the regular expression matches the current line. Here's an example script that outputs only those lines that contain the character sequence foo:

/foo/ { print }

Of course, you can use more complicated regular expressions. Here's a script that will print only lines that contain a floating point number:

/[0-9]+\.[0-9]*/ { print }

Expressions and blocks

There are many other ways to selectively execute a block of code. We can place any kind of boolean expression before a code block to control when a particular block is executed. Awk will execute a code block only if the preceding boolean expression evaluates to true. The following example script will output the third field of all lines that have a first field equal to fred. If the first field of the current line is not equal to fred, awk will continue processing the file and will not execute the print statement for the current line:

$1 == "fred" { print $3 }

Awk offers a full selection of comparison operators, including the usual "==", "<", ">", "<=", ">=", and "!=". In addition, awk provides the "~" and "!~" operators, which mean "matches" and "does not match". They're used by specifying a variable on the left side of the operator, and a regular expression on the right side. Here's an example that will print only the third field on the line if the fifth field on the same line contains the character sequence root:

$5 ~ /root/ { print $3 }

Conditional statements

Awk also offers very nice C-like if statements. If you'd like, you could rewrite the previous script using an if statement:

{ 
    if ( $5 ~ /root/ ) { 
        print $3 
    }
}

Both scripts function identically. In the first example, the boolean expression is placed outside the block, while in the second example, the block is executed for every input line, and we selectively perform the print command by using an if statement. Both methods are available, and you can choose the one that best meshes with the other parts of your script.

Here's a more complicated example of an awk if statement. As you can see, even with complex, nested conditionals, if statements look identical to their C counterparts:

{
    if ( $1 == "foo" ) {
        if ( $2 == "foo" ) {
            print "uno"
        } else {
            print "one"
        }
    } else if ($1 == "bar" ) {
        print "two"
    } else {
        print "three"
    }
}

Using if statements, we can also transform this code:

! /matchme/ { print $1 $3 $4 }

to this:

{
    if ( $0 !~ /matchme/ ) {
        print $1 $3 $4
    }
}

Both scripts will output only those lines that don't contain a matchme character sequence. Again, you can choose the method that works best for your code. They both do the same thing.

Awk also allows the use of boolean operators "||" (for "logical or") and "&&"(for "logical and") to allow the creation of more complex boolean expressions:

( $1 == "foo" ) && ( $2 == "bar" ) { print } 

This example will print only those lines where field one equals foo and field two equals bar.

Numeric variables!

So far, we've either printed strings, the entire line, or specific fields. However, awk also allows us to perform both integer and floating point math. Using mathematical expressions, it's very easy to write a script that counts the number of blank lines in a file. Here's one that does just that:

BEGIN { x=0 } 
/^$/  { x=x+1 } 
END   { print "I found " x " blank lines. :)" } 

In the BEGIN block, we initialize our integer variable x to zero. Then, each time awk encounters a blank line, awk will execute the x=x+1 statement, incrementing x. After all the lines have been processed, the END block will execute, and awk will print out a final summary, specifying the number of blank lines it found.

Stringy variables

One of the neat things about awk variables is that they are "simple and stringy." I consider awk variables "stringy" because all awk variables are stored internally as strings. At the same time, awk variables are "simple" because you can perform mathematical operations on a variable, and as long as it contains a valid numeric string, awk automatically takes care of the string-to-number conversion steps. To see what I mean, check out this example:

x="1.01" 
# We just set x to contain the *string* "1.01" 
x=x+1 
# We just added one to a *string* 
print x 
# Incidentally, these are comments :) 

Awk will output:

2.01

Interesting! Although we assigned the string value 1.01 to the variable x, we were still able to add one to it. We wouldn't be able to do this in bash or python. First of all, bash doesn't support floating point arithmetic. And, while bash has "stringy" variables, they aren't "simple"; to perform any mathematical operations, bash requires that we enclose our math in an ugly $( ) construct. If we were using python, we would have to explicitly convert our 1.01 string to a floating point value before performing any arithmetic on it. While this isn't difficult, it's still an additional step. With awk, it's all automatic, and that makes our code nice and clean. If we wanted to square and add one to the first field in each input line, we would use this script:

{ print ($1^2)+1 }

If you do a little experimenting, you'll find that if a particular variable doesn't contain a valid number, awk will treat that variable as a numerical zero when it evaluates your mathematical expression.

Lots of operators

Another nice thing about awk is its full complement of mathematical operators. In addition to standard addition, subtraction, multiplication, and division, awk allows us to use the previously demonstrated exponent operator "^", the modulo (remainder) operator "%", and a bunch of other handy assignment operators borrowed from C.

These include pre- and post-increment/decrement ( i++, --foo ), add/sub/mult/div assign operators ( a+=3, b*=2, c/=2.2, d-=6.2 ). But that's not all -- we also get handy modulo/exponent assign ops as well ( a^=2, b%=4 ).

Field separators

Awk has its own complement of special variables. Some of them allow you to fine-tune how awk functions, while others can be read to glean valuable information about the input. We've already touched on one of these special variables, FS. As mentioned earlier, this variable allows you to set the character sequence that awk expects to find between fields. When we were using /etc/passwd as input, FS was set to ":". While this did the trick, FS allows us even more flexibility.

The FS value is not limited to a single character; it can also be set to a regular expression, specifying a character pattern of any length. If you're processing fields separated by one or more tabs, you'll want to set FS like so:

FS="\t+"

Above, we use the special "+" regular expression character, which means "one or more of the previous character".

If your fields are separated by whitespace (one or more spaces or tabs), you may be tempted to set FS to the following regular expression:

FS="[[:space:]]+"

While this assignment will do the trick, it's not necessary. Why? Because by default, FS is set to a single space character, which awk interprets to mean "one or more spaces or tabs." In this particular example, the default FS setting was exactly what you wanted in the first place!

Complex regular expressions are no problem. Even if your records are separated by the word "foo," followed by three digits, the following regular expression will allow your data to be parsed properly:

FS="foo[0-9][0-9][0-9]"

Number of fields

The next two variables we're going to cover are not normally intended to be written to, but are normally read and used to gain useful information about the input. The first is the NF variable, also called the "number of fields" variable. Awk will automatically set this variable to the number of fields in the current record. You can use the NF variable to display only certain input lines:

NF == 3 { print "this particular record has three fields: " $0 }

Of course, you can also use the NF variable in conditional statements, as follows:

{
    if ( NF > 2 ) {
        print $1 " " $2 ":" $3 
    }
}

Record number

The record number (NR) is another handy variable. It will always contain the number of the current record (awk counts the first record as record number 1). Up until now, we've been dealing with input files that contain one record per line. For these situations, NR will also tell you the current line number. However, when we start to process multi-line records later in the series, this will no longer be the case, so be careful! NR can be used like the NF variable to print only certain lines of the input:

(NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" }
{
    #skip header
    if ( NR > 10 ) {
        print "ok, now for the real information!"
    }
}

Awk provides additional variables that can be used for a variety of purposes. We'll cover more of these variables in later articles.

We've come to the end of our initial exploration of awk. As the series continues, I'll demonstrate more advanced awk functionality, and we'll end the series with a real-world awk application.

Resources

Next >>>

Read the next article in this series: Awk by Example, Part 2

Support Funtoo and help us grow! Donate $15 per month and get a free SSD-based Funtoo Virtual Container.

About the Author

Daniel Robbins is best known as the creator of Gentoo Linux and author of many IBM developerWorks articles about Linux. Daniel currently serves as Benevolent Dictator for Life (BDFL) of Funtoo Linux. Funtoo Linux is a Gentoo-based distribution and continuation of Daniel's original Gentoo vision.

Got Funtoo?

Have you installed Funtoo Linux yet? Discover the power of a from-source meta-distribution optimized for your hardware! See our installation instructions and browse our CPU-optimized builds.

Funtoo News

Drobbins

ARM Rebuild

ARM systems will use new stage3's that are not compatible with earlier versions.
2015-06-27 by Drobbins
Drobbins

ABI X86 64 and 32

Funtoo Linux has new 32-bit compatibility libraries inherited from Gentoo. Learn about them here.
2015-06-18 by Drobbins
Drobbins

Pre-built kernels!

Funtoo stage3's are now starting to offer pre-built kernels for ease of install. read more....
2015-05-12 by Drobbins
More...

More Articles

Browse all our Linux-related articles, below:

A

B

F

G

K

L

M

O

P

S

T

W

X