Tuesday, March 29, 2016

What is git?

According to git's own manual:
GIT(1)                            Git Manual                            GIT(1)

NAME
       git - the stupid content tracker
...

Creating noarch RPM with minimal spec file

Suppose you have some files that should be packaged and distributed as a small noarch RPM, but you do not want to depend on ~/.rpmmacros or rpm build tree somewhere. Idea is that one can clone the repo with your files, run something like make and he will get the ready runme-1.1-noarch.rpm that contains /opt/runme, without additional actions.
README.txt
runme.sh
runscripts.d/
runscripts.d/001_script.sh
runscripts.d/002_another_script.sh
 
README.txt and runme.sh are the only files that exist in the top directory; contents of runscripts.d changes very often, and you don't want to carefully mention each file. To satisfy all our requirements, we will do the following:

1. RPM buildtree will be created on the fly in the current dir in the folder named _build

2. The only .rpmmacros directive that we really need (%_topdir) can be specified as an argument for rpmbuild: rpmbuild --define="%_topdir `pwd`/_build"

3. We will also override %_sourcedir macros to avoid copying sources to _build/SOURCE dir

4. %prep stage can be skipped, everything will be extracted directly to _build/BUILDROOT

5. We have to list all files in runscripts.d in the Source tag in spec, so we just archive them

6. We do set of actions and provide complex arguments to rpmbuild, Makefile is an obvious way to do all the thing.

7. %define __jar_repack %{nil} is a useful trick to avoid searching and repacking jar files. In our case, it just a small speedup, but when you are repacking something like apache-tomcat, it saves a lot of time and does not touch original files.
Do not forget to install rpm-build package that contains /usr/bin/rpmbuild and friends: yum install rpm-build. Firstly, create the Makefile for building the RPM.
all: clean
        mkdir -p _build && \
        tar czf runscripts.tgz runscripts.d && \
        rpmbuild --define="%_topdir `pwd`/_build"  --define "%_sourcedir `pwd`" -bb runme.spec && \
        cp _build/RPMS/noarch/*rpm .

clean:
        rm -rf _build runscripts.tgz *rpm
Now, the specfile for our noarch RPM. Pre- and post-install scriptlets are omitted:
%define __jar_repack %{nil}
Name: runme
Version: 1
Release: 1
Summary: Example set of files
BuildArch: noarch
License: GPL
URL: http://plastilinux.blogspot.com
Source0: runscripts.tgz
Source1: README.txt
Source2: runme.sh

%description
Example noarch rpm

%install
mkdir -p %{buildroot}/opt/runme
tar -C %{buildroot}/opt/runme -xf %SOURCE0
install -m 644 %SOURCE1 %{buildroot}/opt/runme
install -m 755 %SOURCE2 %{buildroot}/opt/runme

%files
%defattr(-, root, root)
%dir /opt/runme
%dir /opt/runme/runscripts.d
/opt/runme/README.txt
%attr(755,root,root) /opt/runme/runme.sh
%attr(755,root,root) /opt/runme/runscripts.d/*

Get all the files from the repo, run "make" and get the ready rpm in two seconds.

Friday, October 10, 2014

Print all lines after a match up to the end of the file

Very useful thing in __DATA__ analog in shell scripts, e.g.:
#!/bin/sh

# do something with data after exit 0
exit 0
# list of things:
thing 1
thing 2
thing 3
To iterate over things, one could use known size of the list:
tail -3 $0 | (while read name size; do 
    echo $name $size
done)
But if this list changes from time to time, you need to adjust tail and once you can forget it. I use unique marker that indicates that everything after it can be used as data. To show everything after match to the end of file, I use the following construction:
#!/bin/sh

sed '0,/^# list of things:$/d' | (while read name size; do 
    echo $name $size
done)
exit 0
# list of things:
thing 1
thing 2
thing 3
It works with GNU sed. You can also use Perl one-liner that works in the same way:
#!/bin/sh

perl -ne 'print unless 1.../^# list of things:$/' $0 | (while read name size; do 
    echo $name $size
done)
exit 0
# list of things:
thing 1
thing 2
thing 3
But experiments show that sed works a bit faster.
From my answer.

Monday, November 9, 2009

How to Get the Version of a Kernel Without Booting It

Sometimes you need to get version from a kernel file lying on filesystem and the compiler that was used to compile it, but there is no opportunity to boot from this kernel. For example, once I recompiled my own kernel with some options in .config changed, and I wanted to be sure that new kernel's vermagic string doesn't differ from modules' one. This string is usually being printed by Linux kernel in the beginning of boot, e.g.:
Linux version 2.6.24-19-generic (buildd@terranova) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) #1 SMP Fri Jul 11 23:41:49 UTC 2008 (Ubuntu 2.6.24-19.36-generic)


To know more about vermagic please read chapter 2.8 of The Linux Kernel Module Programming Guide and description of --force-vermagic option in modprobe (8).

To get the vermagic string of some module on a disk, use modinfo module.ko (even if this module was built for other arch, e.g. we work in i386 and modules is for x86_64, and vice versa). But you can not use this trick (or invent a similar one) with a kernel file because modinfo doesn't work with vmlinuz.

But it's not so hard as you can imagine :)

vmlinuz consists of some parts: loader, decompressor and the compressed (zlib) kernel itself. Gzipped content usually begins with byte sequence 1f 8b 08 00. We can use od utility to find them:
od -A d -t x1 vmlinuz|grep "1f 8b 08 00"

But often these bytes may occur in the different lines, like in the following fragment (was taken from Ubuntu Hardy's generic kernel):
0011376 00 fd f3 a4 fc 5e 81 c5 ff ff 0f 00 81 e5 00 00
0011392 f0 ff 8d 83 90 01 1d 00 ff e0 01 01 1d 00 1f 8b
0011408 08 00 26 a1 ac 48 02 03 ec 3a 6d 74 14 55 96 af
0011424 3b d5 49 77 e8 58 15 a7 5b 5b b6 19 1a 2d 30 19


So we will use an alternate method.

Kernel source includes utility named scripts/binoffset.c that does exactly what we need: find the first occurrence of given bytes in the bytes stream. This utility's source is included in the Debian package linux-headers, so you don't need to grab and extract full kernel source.

Let's use it:
$ gcc -o binoffset /usr/src/linux/scripts/binoffset.c
$ ./binoffset /vmlinuz 0x1f 0x8b 0x08 00 2>/dev/null
11406

It means that gzip header begins at offset 11406 from the beginning of the file.

Now extract the compressed kernel from vmlinuz and decompress it:
dd if=/vmlinuz skip=11406 bs=1|gzip -d >vmlinux

And find our string there, using strings + grep:
$ strings vmlinux |grep gcc
Linux version 2.6.24-19-generic (buildd@terranova) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) #1 SMP Wed Aug 20 22:56:21 UTC 2008 (Ubuntu 2.6.24-19.41-generic)
%s version %s (buildd@terranova) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) %s

So, we have got what we want :) Let's concatenate all this stuff to one line:
(dd if=/vmlinuz skip=`./binoffset /vmlinuz 0x1f 0x8b 0x08 0x00` bs=1|gzip -d |strings|grep -n 1"^Linux version") 2>/dev/null

P.S. /usr/src/linux/scripts/extract-ikconfig works like described here, but it needs some modifications to make it do the thing we want... and I like to reinvent the bicycles :)

Wednesday, June 10, 2009

Using command_not_found_handle to VERY quick access hosts via ssh.

Some time ago, I had noticed that my .bash_history contains a lot of similar records like 'ssh 192.168.0.10', 'ssh 192.168.0.24' etc. So I thought that this is not the True Unix Way and attempted to change this situation.

First of all, I generated a list of bash aliases with entries like the following:

...
alias 123='ssh user@192.168.0.123'
alias 125='ssh user@192.168.0.125'
...


But this solution looked too ugly. Another solution was to add lots of ssh_config entries as described here; it was ugly too.

Another thing that made these two hacks useless was that I needed to connect to hosts in other networks, like 192.168.1.x, 192.168.20.x etc, and it wasn't possible to add all of them to .bash_aliases or ~/.ssh/config.

So I invented the wheel.

In Debian (and Ubuntu too) there is a valuable addendum to bash: function command_not_found_handle that accepts the name of non-existing command and decides what to do: call another program instead this command or something else. By default, it searches the command name in apt package cache and advices user to install the appropriate package.

I wrote my own function and placed it to ~/.bashrc:
command_not_found_handle () {
if [[ ! "$1" ]] ; then
return 127
fi

n="$1"

if echo $n| perl -ne 'exit(/^([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/ ? 0:1)' ; then
ip=192.168.20.$n
elif echo $n| perl -ne '
exit (/^([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])
$/x ? 0:1)' ; then
ip=192.168.$n
elif echo $n| perl -ne '
exit (/^([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.
([1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])
$/x ? 0:1)' ; then
ip=$n
else
return 127
fi

ssh $ip
}


How does it work. The function analyzes its argument, and if this argument is a number from 0 to 255, it calls "ssh 192.168.0.xx" (where xx is the argument passed to this function); if the argument passes contains two dot-separated numbers like xx.xx (each is from 0 to 255), it calls ssh 192.168.xx.xx. If the argument validates as a correct IP-address, "ssh ip" is called. In any other cases, exit code 127 is returned and bash handles "command not found" error by itself.

So, I write only the last number of the IP address and it automatically being translated to the full IP:
bvk@host ~$ 15
# becomes ssh 192.168.0.15
bvk@host ~$ 29
# becomes ssh 192.168.0.29
bvk@host ~$ 10.51
# becomes ssh 192.168.10.51
bvk@host ~$ 172.19.4.10
# becomes ssh 172.19.4.10

Also, I wrote some entries to ~/.ssh/config to specify different parameters for different hosts:

Host 192.168.20.251
User special_user1

Host 192.168.20.252
User special_user2

Host 192.168.20.254
User special_user3

Host *
User ordinary_user


This allows me to connect to the hosts 192.168.20.251, 192.168.20.252 and 192.168.20.253 as respective users, and to all other hosts as ordinary_user.

P.S. It's often impossible to give names to all the hosts in our networks, because hosts in our test environment go up and down frequently. So we have to connect to them via their IPs.