Processing math: 100%

2016년 8월 27일 토요일

Finding PID for programs - Why 'pidof foo' is less trustworthy than 'ps -eF | grep foo'

I often use pidof to find the PID of a running program. It works well with dhclient, dnsmasq and other executable binaries. But pidof runs into problems when trying to find the PID of script files that in turn invoke other programs. Take, for example, the Python 2 program deluge-gtk BitTorrent client.

[archjun@pinkS310 bin]$ pidof deluge-gtk
[archjun@pinkS310 bin]$ ps -eF | grep "deluge*" | grep -v grep
archjun  25862     1  3 289160 89272  1 16:47 ?        00:01:30 /usr/bin/python2 /usr/bin/deluge-gtk /MULTIMEDIA/Torrents/CentOS-5.5-x86_64-bin-DVD.torrent

In the first case, pidof fails to return any PID for the deluge-gtk executable file. In the second case, grepping for deluge-gtk in the output of ps -eF (all processes, extra full format) correctly returns the PID of the BitTorrent client which is executed by Python 2.

Let's take a look at the contents of the deluge-gtk executable file:

[archjun@pinkS310 bin]$ cat /usr/bin/deluge-gtk
#!/usr/bin/python2
# EASY-INSTALL-ENTRY-SCRIPT: 'deluge==1.3.13.dev0','gui_scripts','deluge-gtk'
__requires__ = 'deluge==1.3.13.dev0'
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.exit(
        load_entry_point('deluge==1.3.13.dev0', 'gui_scripts', 'deluge-gtk')()
    )

ps -eF is more useful because it can follow an execution chain to the final PID.

2016년 8월 20일 토요일

Web scraping using lynx and shell utilities

In 2016, many people would probably think of using Python modules such as BeautifulSoup, urllib, or requests for scraping and parsing web pages. While this is a good choice, in some cases it can be quicker to scrape web pages using the text browser lynx and parsing the results using grep, awk, and sed.

My use case is as follows: I want to programatically generate a list of rpm packages from Fedora's EPEL X (5, 6, 7), CentOS vault, CentOS mirror, and HP DL server firmware sites. I want this list to be comparable to the output of rpm -qa on RHEL machines. Here are some sample URL's for sites showing rpm package lists:

http://vault.centos.org/5.7/updates/x86_64/RPMS/
http://mirror.centos.org/centos-5/5.11/os/x86_64/CentOS/
https://dl.fedoraproject.org/pub/epel/6/x86_64/
http://mirror.centos.org/centos-7/7.2.1511/updates/x86_64/Packages/
http://downloads.linux.hpe.com/repo/spp/rhel/6/x86_64/2016.04.0_supspp_rhel6.8_x86_64/

If you visit any of these links you will find that the basic format is the same -- from the left, the first field is an icon, the second field is the rpm filename, the third field is the date in YYYY-MM-DD, the fourth field is time in HH:MM, and the fifth field is file size.

Here is my bash script which parses file list html pages into a simple text file:

#!/bin/bash
# http-rpmlist-parser.sh
# Copyright (C) 2016 Jun Go
# 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 3 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, see <http://www.gnu.org/licenses/>.
# Jun Go gojun077@gmail.com
# Last Updated: 2016-08-18
# This script uses lynx to render an html page containing a list
# of rpm filenames and output the raw text without html tags to
# a file. Then the raw text will be parsed using grep, awk, and
# sed to return a list of filenames that can be directly compared
# with the output of the RHEL command 'rpm - qa'
# USAGE: ./http-rpmlist-parser.sh [URL] [output file]
# EXAMPLE:
# /http-rpmlist-parser.sh \
# http://vault.centos.org/6.6/updates/x86_64/Packages/ \
# cent66-errata-list-clean.txt
F0="lynx-temp0.txt"
F1="lynx-temp1.txt"
F2="lynx-temp2.txt"
F3="lynx-temp3.txt"
TEMP=("${F0}"
"${F1}"
"${F2}"
"${F3}"
)
########################################
### Function for removing temp files ###
cleanup()
{
for i in ${TEMP[*]}; do
if [ -f "$i" ]; then
rm "$i"
else
echo "Cannot find temp file $i"
fi
done
}
########################################
if [ -z "$1" ]; then
echo "Please enter a URL to parse"
exit 1
elif [ -z "$2" ]; then
echo "Please specify an output file name"
exit 1
fi
# Check that lynx is installed on the system
if ! which lynx > /dev/null 2>&1; then
echo "This script requires lynx. Please install lynx and try again"
exit 1
fi
# Parse html into tagless text using lynx browser
lynx -dump -dont_wrap_pre -width=990 -nolist "$1" > "${F0}"
# Return lines containing the string '.rpm'
grep ".rpm" "${F0}" > "${F1}"
# replace all tabs with 4 spaces b/c
# awk will interpret [:space:] as FS
sed "s:\t: :g" "${F1}" > "${F2}"
# Extract the third field containing the filename
# Note that html pages containing file lists from EPEL, CentOS Vault,
# and HP all use the same format which consists of square brackets,
# package name, date, and file size (optional)
# [ ] fibreutils-3.2-6.x86_64.rpm 07-Jun-20
awk '{ print $3 }' "${F2}" > "${F3}"
# Remove the ".rpm" extension from each filename so that the file
# list is directly comparable to the output of 'rpm -qa'
sed "s:\.rpm::g" "${F3}" > "$2"
# remove temp files
cleanup

You can see that lynx renders the page from HTML into regular text and dumps this output to a file if you pass the -dump option. But this is not enough, because lynx by default inserts a newline character in lines greater than 79 characters. To avoid this problem, you must manually set the line width to something larger. The maximum width in lynx is 990 characters, so I specified this value through the option -width=990. Finally the -nolist option removes the list of links that lynx inserts at the bottom of the page.

Using grep I then extract just the lines containing the string ".rpm". Next I replace all tabs with 4 spaces using sed and then use awk to print just the filename field. Finally I use sed to remove the ".rpm" extension from the filenames to make the output identical to the format of rpm -qa. Note that the last sed statement might not render correctly in your browser because I use mathjax on my blog. Unfortunately, the characters I am trying to express are also the tags for a mathjax expression; The sed snippet should appear as follows:

sed "s:\openparens\.rpm\closeparens::g" "${F3}" > "$2"

I have replaced '(' and ')' with openparens and closeparens, respectively due to my blog's mathjax plugin incorrectly interpreting the above expression as a mathjax statement.

If you don't escape ".rpm" with backslashes, '.' will be interpreted as a regex "match any character" which would match strings like "-rpm", ".rpm", "redhat-rpm-config", etc. This is undesirable.

BTW this script is for informational and educational purposes only. It would actually be easier to just invoke lynx with lynx -dump -listonly ... and skip the data munging steps of replacing tabs with spaces using sed. If you do it this way you will get just the links to rpm files from EPEL, CentOS mirror, etc. Then you can return just the filename from each link's path with awk:

awk -F'/' '{ print $NF }'




2016년 8월 13일 토요일

Differences in binary file sizes between RHEL and CentOS

CentOS maintains binary compatibility with Red Hat Enterprise Linux, so applications which run on certain versions of RHEL should be able to run without changes on analogous versions of CentOS. Recently, however, a client asked me why executable binaries from the initscripts package (which contains /bin/ipcalc, /bin/usleep, etc) on RHEL 6.X have slightly different file sizes with those from the CentOS 6.X initscripts package.

First, I needed to verify that the source code in the initscripts srpm's for RHEL and CentOS were identical.

I downloaded initscripts-9.03.46-1.el6.src.rpm for RHEL 6.6 from the Redhat partner site and I downloaded initscripts-9.03.46-1.el6.centos.src.rpm from CentOS vault at the following url:

http://vault.centos.org/6.6/os/Source/SPackages/initscripts-9.03.46-1.el6.centos.src.rpm

I then unpacked the RHEL 6.6 initscripts source rpm's as follows:

[root@localhost srpm]# rpm2cpio initscripts-9.03.46-1.el6.src.rpm | cpio -idmv
initscripts-9.03.46.tar.bz2
initscripts.spec
3146 blocks
[root@localhost srpm]# ls
initscripts-9.03.46-1.el6.src.rpm  initscripts-9.03.46.tar.bz2  initscripts.spec
[root@localhost srpm]# tar -xvf initscripts-9.03.46.tar.bz2
initscripts-9.03.46/
initscripts-9.03.46/.gitignore
initscripts-9.03.46/.tx/
initscripts-9.03.46/.tx/config
initscripts-9.03.46/COPYING
initscripts-9.03.46/Makefile
...

I also did the same for the CentOS 6.6 initscripts package. I then renamed the directories for the extracted srpm's and then used the meld GUI diff tool to compare the .../src as well as the entire extracted initscripts srpm directories for RHEL 6.6 and CentOS 6.6.

As you can see below, the contents of the srpm's are identical:



Compiler options are contained within .../src/Makefile and the options are identical, as you can see from the diff results above. So the binary size differences are not due to differences in the source code, compiler options, or rpm Specfile between RHEL and CentOS.

Next, I did a simple C program compilation test of my own using gcc on a stock installation of RHEL 6.6 and CentOS 6.6.

Here is a simple hello world one-liner I have named hello.c:

#include

int main(void)
{
  printf("hello world!\n");
}

If I compile it with gcc with the following options

gcc hello.c -O0 -std=c99 -Wall -Werror -o hello

I still get slightly different file sizes on RHEL and CentOS:

RHEL 6.6
[root@localhost pset1]# ls -al hello
-rwxr-xr-x. 1 root root 6473 Aug  9 08:31 hello

CentOS 6.6
[root@localhost pset1]# ls -al hello
-rwxr-xr-x. 1 root root 6425 Aug 11 06:15 hello

This is a difference of 48 bytes.

I then used objdump from bintools to examine the assembly code in the compile hello object files. I renamed each object file as hello_rhel66 and hello_cent66, respectively. I am using the -s option with objdump so I can see full contents that also converts hex strings to ASCII.

[fedjun@u36jcFedora Downloads]$ objdump -s hello_rhel66 > hello_rhel66.dump
[fedjun@u36jcFedora Downloads]$ objdump -s hello_cent66 > hello_cent66.dump
[fedjun@u36jcFedora Downloads]$ diff -u hello_rhel66.dump hello_cent66
hello_cent66       hello_cent66.dump  
[fedjun@u36jcFedora Downloads]$ diff -u hello_rhel66.dump hello_cent66.dump
--- hello_rhel66.dump 2016-08-13 10:07:48.893239117 +0900
+++ hello_cent66.dump 2016-08-13 10:08:02.078435160 +0900
@@ -1,5 +1,5 @@

-hello_rhel66:     file format elf64-x86-64
+hello_cent66:     file format elf64-x86-64

 Contents of section .interp:
  400200 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
@@ -9,8 +9,8 @@
  40022c 00000000 02000000 06000000 12000000  ................
 Contents of section .note.gnu.build-id:
  40023c 04000000 14000000 03000000 474e5500  ............GNU.
- 40024c 4cc6b3fd d6ec9bb6 e4540da0 aba4807f  L........T......
- 40025c 0f84997f                             ....            
+ 40024c 69320cbb e7408021 2c646e86 8344b173  i2...@.!,dn..D.s
+ 40025c 5e478671                             ^G.q            
 Contents of section .gnu.hash:
  400260 01000000 01000000 01000000 00000000  ................
  400270 00000000 00000000 00000000           ............    
@@ -137,7 +137,4 @@
 Contents of section .comment:
  0000 4743433a 2028474e 55292034 2e342e37  GCC: (GNU) 4.4.7
  0010 20323031 32303331 33202852 65642048   20120313 (Red H
- 0020 61742034 2e342e37 2d313029 00474343  at 4.4.7-10).GCC
- 0030 3a202847 4e552920 342e342e 37203230  : (GNU) 4.4.7 20
- 0040 31323033 31332028 52656420 48617420  120313 (Red Hat 
- 0050 342e342e 372d3131 2900               4.4.7-11).      
+ 0020 61742034 2e342e37 2d313129 00        at 4.4.7-11).

Apparently the contents of the .interp and .comments section differ between the two binaries. I believe the same holds true for each of the individual binaries from the initscripts package on RHEL 6.6 and CentOS 6.6. Each of the object files may contain different comments and time stamps which will lead to different binary file sizes.


2016년 8월 6일 토요일

RHEL 5.X, 6.X - Converting RHEL to CentOS using shell scripts and Python 2

A client recently asked my company to help them convert Red Hat Enterprise Linux 5.11 and 6.8 to the comparable versions of CentOS. Before I present the scripts I wrote to automate this process, I believe that there is a lot of value in a RHEL subscription and better yet, Red Hat employs full-time kernel developers who submit patches and updates to the Linux open source project. In effect, supporting Red Hat indirectly supports the development of Linux as an operating system.

That being said, CentOS has full binary compatibility with RHEL and the only difference is that RHEL-specific logos and artwork are not present in CentOS. On the Internet there are lots of blog posts on converting from RHEL to CentOS but many of these guides are incomplete or provide incorrect information. In order to save others the trial-and-error required to fine-tune this process, I am sharing the scripts I wrote to automate the conversion process. For the purposes of this post, I will be converting RHEL 5.11 and 6.8 to Cent 5.11 and 6.8 but the process applies to all 5.X and 6.X versions.

Step 1
Generate a baseline of all packages from the RHEL installation DVD. The script I wrote takes one parameter, the path to the installation iso mount point and outputs a text file that can be compared with the local output of rpm -qa (which lists all packages currently installed on a RHEL system).
#!/bin/bash
# create-baseline-pkg-list.sh
# Jun Go gojun077@gmail.com
# Last updated: 2016-08-03
# This script will create a baseline list of all the rpm
# files inside a RHEL installation ISO for RHEL 5.X and
# 6.X.
# In order to make the file list directly comparable to
# the output of 'rpm -qa', the file extenstion '.rpm' will
# be removed from all filenames.
# NOTE: On RHEL 5.X 'rpm -qa' does not by default print the
# package architecture. This must be enabled by editing
# /usr/lib/rpm/macros '%_query_all_fmt' value to
# %_query_all_fmt %%{name}-%%{version}-%%{release}.%%{arch}
# USAGE:
# ./create-baseline-pkg-list.sh <path to RHEL iso mnt>
# ex: ./create-baseline-pkg-list.sh /mnt/iso (no trailing backslash)
# ex: ./create-baseline-pkg-list.sh /media
FIVE=('Cluster'
'ClusterStorage'
'Server'
'VT'
)
FIVEOUT='rhel5.11-dvd-pkg-list.txt'
FIVECLEAN='rhel5.11-dvd-pkg-list-clean.txt'
SIX=('HighAvailability'
'LoadBalancer'
'Packages'
'ResilientStorage'
'ScalableFileSystem'
'Server'
)
SIXOUT='rhel6.8-dvd-pkg-list.txt'
SIXCLEAN='rhel6.8-dvd-pkg-list-clean.txt'
if [ -z "$1" ]; then
echo "Must enter the path to the iso mount point"
exit 1
else
if [ -f "$1/RELEASE-NOTES-en" ]; then
if grep --silent "Red Hat Enterprise Linux 5" "$1/RELEASE-NOTES-en"; then
for i in ${FIVE[*]}; do
ls "${1}/${i}" >> $FIVEOUT
done
fi
# remove non-rpm files and directory names, sort, remove dups
grep ".rpm" $FIVEOUT | sort -u > $FIVECLEAN
# remove '.rpm' from all filenames to conform with 'rpm -qa' output
sed -i "s:.rpm::g" "$FIVECLEAN"
elif [ -f "$1/README" ]; then
if grep --silent "Red Hat Enterprise Linux 6" "$1/README"; then
for j in ${SIX[*]}; do
ls "${1}/${j}" >> $SIXOUT
done
fi
# remove non-rpm files and directory names, sort, remove dups
grep ".rpm" $SIXOUT | sort -u > $SIXCLEAN
# remove '.rpm' from all filenames to conform with 'rpm -qa' output
sed -i "s:.rpm::g" "$SIXCLEAN"
else
echo "Please mount a valid RHEL 5.X or 6.X image!"
exit 1
fi
fi
# delete FIVEOUT/SIXOUT temp files
if [ -f "$FIVEOUT" ]; then
rm "$FIVEOUT"
elif [ -f "$SIXOUT" ]; then
rm "$SIXOUT"
else
echo "No temp files exist"
exit 1
fi


Step 2
Generate a list of packages which differ from the baseline packages on the RHEL installation ISO. This step is necessary because when you do the RHEL to CentOS conversion, packages will be updated to stock CentOS package versions. If you have some errata packages installed on your RHEL system, these will be updated to stock CentOS package versions. Therefore you need to generate a list of local packages which differ from the rpm's on the RHEL installation DVD/iso.

RHEL 5.X only supports Python 2.4.3, so this is the Python version I wrote the script in. 2.4 was a new experience for me as I am accustomed to using the argparse module from Python 2.7.X instead of optparse. Also 2.4 doesn't support the file opening idiom with open(filename, 'r') as foo: ... instead you have to do something like

f = open(filename 'r')
...
f.close()

and remember to manually close file objects after opening them. Fortunately, Python 2.6.X which is used on RHEL 6.X, and Python 2.7.X which is used on RHEL 7.X are backwards-compatible with 2.4.X, so my script works fine on RHEL 6.X, too.

#!/usr/bin/env python
# rhel-baseline-diff.py
# Jun Go gojun077@gmail.com
# Last updated: 2016-08-03
# Given a baseline file that contains a list of rpm's
# from a RHEL installation DVD, compare this rpm list with
# the list of rpm's currently installed on a RHEL 5.X or
# RHEL 6.X machine and write any differences to a file.
# Note that the Python 2 version on RHEL 5.11 is 2.4.3
# while the version on RHEL 6.8 is 2.6.6
# This python script should work in both python 2 and 3
# as it does not use any 2-or-3-specific syntax or libraries
# and only uses language features available up to Python 2.4
# USAGE: ./rhel-baseline-diff.py [baseline file]
# This script will send output to the file 'rhel-diff.txt'
import optparse
import subprocess
import re
# Although 'optparse' has been deprecated in favor of 'argparse'
# starting from Python 2.7.X, the RHEL machines this script
# will be executed on use Python 2.6.X and 2.4.X
def chk_rhel_rel():
"""
-> string
Search /etc/redat-release for 'Tikanga' (RHEL 5),
'Santiago' (RHEL 6), or 'Maipo' (RHEL 7) and return
string with release codename
"""
rhel5 = r"Tikanga"
rhel6 = r"Santiago"
rhel7 = r"Maipo"
rhel5_obj = re.compile(rhel5)
rhel6_obj = re.compile(rhel6)
rhel7_obj = re.compile(rhel7)
rhrel = open("/etc/redhat-release", 'r')
try:
for line in rhrel:
match5 = rhel5_obj.search(line)
match6 = rhel6_obj.search(line)
match7 = rhel7_obj.search(line)
if match5:
return "Tikanga"
elif match6:
return "Santiago"
elif match7:
return "Maipo"
finally:
rhrel.close()
def check_rpm_macro():
"""
-> string
Look for %query_all_fmt line in /usr/lib/rpm/macros and verify
that 'architecture' is printed. The format for this setting
differs between RHEL 5.X or 6.X, however, so the search patterns
are different. If the pattern is not found, the function returns
the strings '5' or '6'. If the pattern is found, the function
returns blank string ''.
TODO: need to handle edge case in which search pattern is found
but the entire line is commented out with '#'
"""
# /usr/lib/rpm/macros pattern for RHEL5.X
pat1 = r'%%{name}-%%{version}-%%{release}.%%{arch}'
# /usr/lib/rpm/macros pattern for RHEL6.X
pat2 = r'%%{nvra}'
pat1_obj = re.compile(pat1)
pat2_obj = re.compile(pat2)
write_flag = ''
match_count = 0
# RHEL version string
rhel_ver = chk_rhel_rel()
f = open("/usr/lib/rpm/macros", 'r')
try:
if rhel_ver == "Tikanga":
for line in f:
match5 = pat1_obj.search(line)
if match5:
match_count += 1
if match_count == 0:
write_flag = '5'
return write_flag
elif rhel_ver == "Santiago":
for line in f:
match6 = pat2_obj.search(line)
if match6:
match_count += 1
if match_count == 0:
write_flag = '6'
return write_flag
finally:
f.close()
def write_rpm_macro(write_flag):
"""
String -> Write to file
Depending on the string 'write_flag', the proper '%query_all_fmt'
string for RHEL5.X / RHEL6.X will be appended to
/usr/lib/rpm/macros or not written at all
"""
f = open("/usr/lib/rpm/macros", 'a')
try:
if write_flag == '5':
f.write(
"\n%_query_all_fmt %%{name}-%%{version}-%%{release}.%%{arch}\n"
)
elif write_flag == '6':
f.write("\n%%{nvra}\n")
finally:
f.close
def load_baseline(filename):
"""
file -> LoS
Given a LF-delimited file containing rpm package names, this
function will read each line into a list and strip out newlines
"""
f = open(filename, 'r')
try:
baselineL = [line.strip() for line in f]
return baselineL
finally:
f.close()
def write_file(los, filename):
"""
ListOfString, text -> file
Given a list of strings and a filename, write each list element
to 'filename' and add a newline char with each element.
"""
g = open(filename, 'w')
try:
for elem in los:
g.write(elem + "\n")
finally:
g.close()
def current_rpmlist():
"""
No input -> LoS
This function will execute 'rpm -qa' on the target system
(which must be a RHEL variant) and insert the output into
a ListOfString while stripping out `\n' chars.
"""
rpmqa = subprocess.Popen("rpm -qa", shell=True, stdout=subprocess.PIPE)
out = rpmqa.stdout.readlines()
currentL = [line.strip() for line in out]
return currentL
def main():
p = optparse.OptionParser(description="",
prog="rhel-baseline-diff.py",
version="0.1",
usage="%prog [baseline-file]")
options, arguments = p.parse_args()
if len(arguments) == 1:
# Check if /usr/lib/rpm/macros is correctly set up for rpm -q
write_rpm_macro(check_rpm_macro())
# Filename of rpm baseline pkg list
filename = arguments[0]
# List of baseline rpm pkg's
baseL = load_baseline(filename)
# List of rpm pkg's currently installed on machine
currL = current_rpmlist()
# List of rpm's on machine that are identical to baseline
intersectL = list(set(currL).intersection(baseL))
# List of rpm's on machine that are different from baseline
diffL = list(set(currL) - set(intersectL))
print("No. of local pkgs identical to baseline:\n %s"
% len(intersectL))
print("No. of local pkgs different from baseline:\n %s"
% len(diffL))
# Sanity check - the number of installed pkg's must equal
# no. of identical pkg's + no. of different pkg's
assert len(intersectL) + len(diffL) == len(currL)
# print("Local pkgs identical to baseline:\n %s" % intersectL)
# print("Local packages different from baseline: \n %s" % diffL)
write_file(intersectL, "pkg-intersect.txt")
write_file(diffL, "pkg-diff.txt")
# print help message if no arguments are passed
else:
p.print_help()
if __name__ == '__main__':
main()
One issue I encountered when writing the Python script above is that rpm -qa on RHEL 5.X by default does not return the package architecture (i.e. i386, x86_64, i686). To also see package architecture you have to edit /usr/lib/rpm/macros and add the architecture field in the following format:

%_query_all_fmt %%{name}-%%{version}-%%{release}.%%{arch}

Fortunately, this is the default on RHEL 6.X so this setting only needs to be changed for RHEL 5.X so you can compare the baseline rpm package list generated in Step 1 with rpm -qa in Step 2.

Step 3

Run the conversion scripts for RHEL 5.11 and RHEL 6.8. Note that before running the scripts you must have mounted the appropriate CentOS installation iso on a mount point which you will specify to the script as a parameter.

Here is the script for RHEL 5.X:

#!/bin/bash
# rhel511-to-cent511.sh
# This script converts a RHEL 5.11 install into CentOS 5.11
# Copyright (C) 2016 Jun Go
# 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 3 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, see <http://www.gnu.org/licenses/>.
# Jun Go gojun077@gmail.com
# Last Updated 2016-08-04
# This script should be executed as root.
# USAGE:
# ./rhel511-to-cent511.sh [mountpt for CentOS 5.11 iso]
# ex: ./rhel511-to-cent511.sh /media
# no trailing slash in the path!
if [ -z "$1" ]; then
echo "You must specify the path to the CentOS 5.11 iso mountpoint"
exit 1
fi
yum clean all
if grep --silent "Red Hat" /etc/redhat-release; then
cp /etc/redhat-release /etc/redhat-release.old
fi
DELETE=(redhat-release
yum-rhn-plugin
redhat-logos
rhn-client-tools
rhn-setup
rhn-check
rhnsd
rhel-instnum
)
for i in ${DELETE[*]}; do
if rpm -q "$i" > /dev/null; then
echo -e "### Now deleting $i ###\n"
rpm -e --nodeps "$i"
fi
done
INSTALL=(centos-release-notes-5.11-0.x86_64.rpm
centos-release-5-11.el5.centos.x86_64.rpm
redhat-logos-4.9.99-11.el5.centos.noarch.rpm
yum-fastestmirror-1.1.16-21.el5.centos.noarch.rpm
yum-3.2.22-40.el5.centos.noarch.rpm
)
if grep --silent "CentOS-5.11" "$1/RELEASE-NOTES-en"; then
cd "$1"/CentOS || exit 1
for j in ${INSTALL[*]}; do
echo -e "### Now installing $j ###\n"
rpm -Uvh --force "$j"
done
else
echo -e "Please mount the proper ISO file\n" 1>&2
exit 1
fi
sed -i "s:Red Hat Enterprise Linux Server:CentOS release 5.11 (Final):g" /boot/grub/grub.conf
# Rename all the repos under /etc/yum.repos.d/ so that only
# the local repo is used
echo -e "### Rename CentOS Repo files ###"
find /etc/yum.repos.d/ -type f -name "CentOS*" -exec mv {} {}.old \;
# check for local.repo file and create it if it doesn't
# exist
if [ -f /etc/yum.repos.d/local.repo ]; then
echo -e "### Local yum repo file exists ###\n"
echo -e "### Changing baseurl for local.repo (to CentOS DVD iso) ###\n"
# Check if the existing local.repo uses mountpoint '/media/Server'
if grep --silent "/media/Server" /etc/yum.repos.d/local.repo; then
# change file path to CentOS path
sed -i "s%file:///media/Server%file://$1%g" \
/etc/yum.repos.d/local.repo
fi
else
cat << XYZ > /etc/yum.repos.d/local.repo
[CentOS]
name=CentOS
baseurl=file://\$1
enabled=1
gpgkey=file://\$1/RPM-GPG-KEY-CentOS-5
XYZ
fi
yum clean all
yum update -y
and here is the script for RHEL 6.X:

#!/bin/bash
# rhel68-to-cent68.sh
# This script converts a RHEL 6.8 install into CentOS 6.8
# Copyright (C) 2016 Jun Go
# 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 3 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, see <http://www.gnu.org/licenses/>.
# Jun Go gojun077@gmail.com
# Last Updated 2016-08-04
# This script must be executed as root
# USAGE:
# ./rhel68-to-cent68.sh [mountpt for CentOS 6.8 iso]
# ex: ./rhel68-to-cent68.sh /media
# no trailing slash in the path!
if [ -z "$1" ]; then
echo "You must specify the path to the CentOS 6.8 iso mountpoint"
exit 1
fi
yum clean all
if grep --silent "Red Hat" /etc/redhat-release; then
cp /etc/redhat-release /etc/redhat-release.old
fi
DELETE=(rhnlib
redhat-release-server-6Server-6.8.0.5.el6.x86_64
redhat-indexhtml
plymouth-0.8.3-27.el6_5.1.x86_64
plymouth-scripts-0.8.3-27.el6_5.1.x86_64
plymouth-core-libs-0.8.3-27.el6_5.1.x86_64
dracut-004-409.el6.noarch
dracut-kernel-004-409.el6.noarch
)
for i in ${DELETE[*]}; do
if rpm -q "$i" > /dev/null; then
echo -e "### Now deleting $i ###\n"
rpm -e --nodeps "$i"
fi
done
# Remove RHEL subscription info
subscription-manager clean
yum remove -y subscription-manager rhn-client-tools
if grep --silent "CentOS-6.8" "$1"/RELEASE-NOTES-en-US.html; then
cd "$1"/Packages || exit 1
echo -e "### Force updating packages with rpm -Uvh --force ###"
rpm -Uvh --force centos-release-6-8.el6.centos.12.3.x86_64.rpm \
centos-indexhtml-6-2.el6.centos.noarch.rpm \
yum-3.2.29-73.el6.centos.noarch.rpm \
yum-plugin-fastestmirror-1.1.30-37.el6.noarch.rpm
# Rename CentOS online .repo files so they won't be used
echo -e "### Rename CentOS Repo files ###"
find /etc/yum.repos.d/ -type f -name "CentOS*" -exec mv {} {}.old \;
# check for local.repo file and create it if it doesn't
# exist
if [ -f /etc/yum.repos.d/local.repo ]; then
echo -e "### Local yum repo file exists ###\n"
echo -e "### Changing baseurl for local.repo (to CentOS DVD iso) ###\n"
# Check if the existing local.repo uses mountpoint '/media/Server'
if grep --silent "/media/Server" /etc/yum.repos.d/local.repo; then
# change file path to CentOS path
sed -i "s%file:///media/Server%file://$1%g" \
/etc/yum.repos.d/local.repo
fi
else
cat << XYZ > /etc/yum.repos.d/local.repo
[CentOS]
name=CentOS
baseurl=file://\$1
enabled=1
gpgkey=file://\$1/RPM-GPG-KEY-CentOS-6
XYZ
fi
yum clean all
yum update -y
yum install -y plymouth-0.8.3-27.el6.centos.1.x86_64.rpm \
plymouth-core-libs-0.8.3-27.el6.centos.1.x86_64.rpm \
plymouth-graphics-libs-0.8.3-27.el6.centos.1.x86_64.rpm \
plymouth-scripts-0.8.3-27.el6.centos.1.x86_64.rpm \
dracut-004-409.el6.noarch.rpm \
dracut-kernel-004-409.el6.noarch.rpm
#dracut-fips-004-409.el6.noarch.rpm \
#dracut-network-004-409.el6.noarch.rpm
else
echo -e "Please mount the proper ISO file\n" 1>&2
exit 1
fi
# Edit grub.conf to remove references to RHEL
sed -i "s:Red Hat Enterprise Linux 6:CentOS release 6.8:g" /boot/grub/grub.conf
# Edit grub.conf to remove references to RHEL errata kernel
sed -i "s:Red Hat Enterprise Linux Server:CentOS errata:g" /boot/grub/grub.conf
# specify that plymouth boot use VESA framebuffer
#sed -i "s:rhgb quiet:rhgb quiet vga=0x317:g" /boot/grub/grub.conf
echo -e "### Rebuilding initrd to remove RHEL text ###"
# Disable the RHEL text mode boot bar by rebuilding initramd
plymouth-set-default-theme -R details
The big difference between RHEL 5.X and 6.X is that for 6.X you need to rebuild the initial ramdisk image to remove the RHEL progress bar at boot.

Note that the script edits entries in /boot/grub/grub.conf so that there will be no references to RHEL in the grub boot menu.


Step 4

Reboot the system and manually upgrade the packages highlighted by rhel-baseline-diff.py in the output file pkg-diff.txt (upgrading errata rpm's to equivalent CentOS versions will require you to separately download and install the relevant packages).

Questions and comments (especially about how to improve the scripts) are welcome!