A Complete Guide For Installing TFTP Server In CentOS 7

Posted by Bo Yang at 2015-08-31 with tags Notes , Unix/Linux


Since CentOS 7(or RedHat 7) is quite different from CentOS 6.x, most notes online for installing TFTP server in CentOS are obsolete already. This post not only summarizes the procedure of installing & configuring TFTP server, but also introduces a general strategy of configuring network services in CentOS 7.

1. Install tftp-server

TFTP server can be installed using following command, where xinetd is necessary.

yum install tftp tftp-server* xinetd*

Then edit /etc/xinetd.d/tftp - set disable to no and add -c option into server_args if you need to upload files to TFTP server from client.

service tftp
{
	socket_type		= dgram
	protocol		= udp
	wait			= yes
	user			= root
	server			= /usr/sbin/in.tftpd
	server_args		= -c -s /tftpboot
	disable			= no
	per_source		= 11
	cps			    = 100 2
	flags			= IPv4
}

2. Enable TFTP Service

The CentOS 7 services( systemd ) can be configured from files under /usr/lib/systemd/system/ . Go to this dir, and edit tftp.service as follows:

[root@localhost system]# cat tftp.service
[Unit]
Description=Tftp Server

[Service]
ExecStart=/usr/sbin/in.tftpd -c -s /tftpboot
StandardInput=socket

[Install]
WantedBy=multi-user.target

The default tftp.service doesn’t have the [Install] unit, but it’s required by systemd . Besides, the tftpd options also need to be changed in the ExecStart entry.

Although service commands are deprecated in CentOS 7, they are still available but simply redirected to systemctl . So you still can use service xinetd start and service tftp start to start xinetd and TFTP.

However, to make them automatically start after boot, following commands are needed:

[root@localhost system]# systemctl enable xinetd
[root@localhost system]# systemctl enable tftp

After these two commands, permanent links will be made for xinetd and TFTP services.

3. Configure SELinux

In CentOS 7, the SELinux is not supposed to be disabled(the system will abort booting if you disable SELinux). So the TFTP read and write must be allowed in SELinux. By default, the SELinux uses enforcing policy, which does not accept any change. To make any change to SELinux, first modify /etc/selinux/config and change the policy to permissive :

[bo@ucs-c200 notes]$ cat /etc/selinux/config 

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=permissive
# SELINUXTYPE= can take one of three two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected. 
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted 

Then reboot the system. After system boot up, check SELinux status:

[root@localhost system]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   permissive
Mode from config file:          permissive
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

Then check the tftp permissions in SELinux:

[root@localhost bobyan]# getsebool -a | grep tftp
tftp_anon_write --> off
tftp_home_dir --> off

If the TFTP write is off as shown above, enable it with setsebool command:

[root@localhost bobyan]# setsebool -P tftp_anon_write 1
[root@localhost bobyan]# setsebool -P tftp_home_dir 1

Above changes to SELinux are permanent, so no need to change any SELinux config files any more.

4. Configure firewalld

Unlike CentOS 6.x, the firewalld is used to replace iptables as default firewall in CentOS 7. Fortunately, iptable config file /etc/sysconfig/iptables is also used by firewalld . So to allow TFTP services, following line should be added to /etc/sysconfig/iptables

-A INPUT -m state --state NEW -m udp -p udp -m udp --dport 69 -j ACCEPT

Then restart firewalld using command firewall-cmd --reload .

A more standard way to allow TFTP is to use firewall-cmd command:

firewall-cmd --zone=public --add-service=tftp --permanent

Where the --permanent option is used to permanently enable the TFTP port. Command firewall-cmd --reload is needed every time changing the firewall config.

To check the status or enable firewalld , following commands can be used:

systemctl status firewalld
systemctl enable firewalld
systemctl start firewalld

How To Use Local Facilities For Logging?

Posted by Bo Yang at 2015-08-20 with tags Notes , Unix/Linux


This post introduces how to configure and use syslogd-compatible syslog tools. These tips should be supported by rsyslog, but rsyslog-specific commands are not covered.

As documented in the man page, the Linux system log is configured in file /etc/syslog.conf by default.You can specify other config file with -f option for syslogd. The format of syslog config file is

<facility>.<priority>   [logfile]

The supported facilities and priorites are defined in syslog.h :

#define	LOG_EMERG	0	/* system is unusable */
#define	LOG_ALERT	1	/* action must be taken immediately */
#define	LOG_CRIT	2	/* critical conditions */
#define	LOG_ERR		3	/* error conditions */
#define	LOG_WARNING	4	/* warning conditions */
#define	LOG_NOTICE	5	/* normal but significant condition */
#define	LOG_INFO	6	/* informational */
#define	LOG_DEBUG	7	/* debug-level messages */

/* facility codes */
#define	LOG_KERN	(0<<3)	/* kernel messages */
#define	LOG_USER	(1<<3)	/* random user-level messages */
#define	LOG_MAIL	(2<<3)	/* mail system */
#define	LOG_DAEMON	(3<<3)	/* system daemons */
#define	LOG_AUTH	(4<<3)	/* security/authorization messages */
#define	LOG_SYSLOG	(5<<3)	/* messages generated internally by syslogd */
#define	LOG_LPR		(6<<3)	/* line printer subsystem */
#define	LOG_NEWS	(7<<3)	/* network news subsystem */
#define	LOG_UUCP	(8<<3)	/* UUCP subsystem */
#define	LOG_CRON	(9<<3)	/* clock daemon */
#define	LOG_AUTHPRIV	(10<<3)	/* security/authorization messages (private) */
#define	LOG_FTP		(11<<3)	/* ftp daemon */

/* other codes through 15 reserved for system use */
#define	LOG_LOCAL0	(16<<3)	/* reserved for local use */
#define	LOG_LOCAL1	(17<<3)	/* reserved for local use */
#define	LOG_LOCAL2	(18<<3)	/* reserved for local use */
#define	LOG_LOCAL3	(19<<3)	/* reserved for local use */
#define	LOG_LOCAL4	(20<<3)	/* reserved for local use */
#define	LOG_LOCAL5	(21<<3)	/* reserved for local use */
#define	LOG_LOCAL6	(22<<3)	/* reserved for local use */
#define	LOG_LOCAL7	(23<<3)	/* reserved for local use */

The local facilities can be used for redirecting/filtering the log of your own programs. For example, given a program foo , if you want to log all the non-critical messages in /var/log/foo.log , and make the critical logs go to system log file /var/log/messages , you can use the following config file

# use facility local1 for foo logs
local1.debug;local1.info;local1.notice;local1.warn   -/var/log/foo.log
local1.panic;local1.alert;local1.crit;local1.err   -/var/log/messages
*.*;local1.none   /var/log/messages 

The special priority none prevents those messages from being logged even though they would have been included in the . . In the above config, all facilities except for local1 will be logged to /var/log/messages .

However, the dash(-) in front of the log filename is not documented in the man page, but it turns out to mean “Don’t sync after every write to the file”. Except that rsyslogd won’t sync anyway, unless you add a special directive in the Global Directives section. Note that you might lose information if the system crashes right behind a write attempt. Nevertheless this might give you back some performance, especially if you run programs that use logging in a very verbose manner. So for most people, a dash makes no difference one way or the other – it will be ignored.

And in program foo , what you need to do is open log file by specifing LOG_LOCAL1 facility. The use of openlog() is mandatory here. Otherwise, it will automatically be called by syslog(), in which case facility will default to LOG_USER .

#include <syslog.h>
int main(int, char**)
{
    openlog("foo", 0, LOG_LOCAL1);
    syslog(LOG_INFO, "test info log"); /* go to /var/log/foo.log */
    syslog(LOG_ERR, "test error log"); /* go to /var/log/messages */
    closelog();
}

References

Retrieve Last Log After Crash

Posted by Bo Yang at 2015-05-27 with tags Notes , Unix/Linux


In Linux, there are two kinds of crashes - kernel panic/oom and user space core dump. For kernel panic, the standard config is rebooting the system. Unfortunately, the panic log can only be printed to console and will disappear after reboot if there is no additional device to record the console log - most kernel panic/oom logs won’t be written to system log, and even they do, there is no way to sync them to disk storage during panic.

As for user-sapce core dump, core files will be generated to /tmp/pid.core (core pattern can be changed in /proc/sys/kernel/core_pattern ) by default. It is up to the admin to decide if the system or process needs reboot after core dump. Although a script can be used to record more logs for the coredump, sometimes it’s still useful to retain some info in the persistent memory, like the backtrace of the coredumped process.

Modules Needed

phram is a Memory Technology Device(MTD) driver, which supports accessing non-system memory, i.e. from the PCI address space. This module can act as a special memory file to store important data during reboots.

ramoops is an kernel oops/panic logger that writes its logs to predefined memory area before the system crashes. It works by logging oopses and panics in a circular buffer. As long as the device has power supply during reboot, the content stored in ramoops won’t go away.

Both phram and ramoops can be compiled to .ko shared library, which can be configured using Linux menuconfig.

Loading Modules

phram can be dynamically loaded using following command:

insmod /lib/modules/phram.ko phram=<name>,<addr>,<len>

where <name> is the device(i.e. file) name under /dev/mtdchar/ , <addr> is the reserved starting memory address, and <len> is the predefined length of the memory area.

To load ramoops

insmod /lib/modules/ramoops.ko mem_address=<addr> mem_size=<len> [record_size=<chunks>]

where <addr> and <len> have the same meaning as above, and record_size is the chunks of reserved memory area.

Copy Log To phram Memory

For kernel panic, the ramoops will automatically copy the panic log into the reserved memory. The panic log begins with leading “====” followed by a timestamp and a new line.

If you need to store other info, dd command can be used to copy file to the memory, e.g.

dd if=/var/log/messages bs=1 count=65536 skip=<fsize - len> of=/dev/mtdchar/phram-oops

According to dd manual, if is the the input file stream, of is the output file, bs specifies the bytes to be read and write at a time, and skip configs the input blocks to be skipped from the input file. Here the size of block is defined by bs . In the above example, the block size is set to 1 byte, and only the last len bytes will be copied to phram memory.

Unfortunately, reading & writing byte-by-byte usaully is very slow. A faster way is setting bs to a larger number, like 128, 512, 1024, etc. In this case, the skip and count need to be calculated correspondingly: say the size of reserved memory is len , input file size is fsize , bs is set to bsize , then

// len: memory size
// fsize: input file size
bs = bsize
if (fsize <= bsize)
    skip =  0;
else
    skip = (fsize - len)/bsize + 1;
count = len/bsize + 1;

Dump Last Log

dd command can also be used for dumping data from reserved phram memory area, e.g.

dd if=/dev/mtdchar/phram-oops bs=<len> count=1

Other file operation commands/APIs can also be used for phram memory device.

Reset phram Memory

phram memory can be cleared by file operation coomands/APIS, e.g.

dd if=/dev/zero bs=<len> count=1 of=/dev/mtdchar/phram-oops

References

  1. Use Memory On Video Card As Swap
  2. Ramoops oops/panic logger
  3. dd manual

Customizing OpenWRT System Log Timestamp

Posted by Bo Yang at 2015-04-02 with tags Notes , Unix/Linux


As explained in previous post Linux System Log , threre is no way to directly change the default timestamps printed on Linux console. However, it is still possible to get human readable timestamp without using fancy syslog tools like syslog-ng .

A bash script of customizing Linux dmesg timestamp is also given in my post post Linux System Log . Unfortunately, that script won’t work if bash is not available in your Linux system. For example, the default shell in OpenWRT is Busybox ash, which lacks many powerful features of bash. The most significant shortcoming for ash is the lame arithmetic operations. In addition, many useful options for standard commands are not supported in Busybox ash. Fortunately, we have sed and awk installed in OpenWRT. So the arithmetic operations can be done by awk.

The basic idea of customizing console syslog timestamp is periodically calling dmesg -c , which clears system circular buffer after dumping the system log. In order to also store the log in syslog file(like /var/log/messages), the dumped message also needs to be redirected(or appending) to the syslog file. The timestamps of dmesg can be replaced with a human readable one. And the required addition/subtraction is implemented by awk in a very special way.

Instead of `dmesg`, someone may be tempted to use command `tail -f /var/log/messages`. Unfortunately, the `tail` command would automatically stop printing from `/var/log/messages` after a while. It may be caused by the implementation of Busybox `tail` or OpenWRT system log mechanism.

Although tail -f /var/log/messages would automatically stop after some time, the -F option works for ash tail command, i.e. tail -F /var/log/messages .

Following is the source code in ash :

#!/bin/ash

#
# This script is used to tail the latest syslogs to stdout,
# syslog file and specified log file.
#

SYSLOG=/var/log/messages
custom_log=
[ ! -z "$1" ] && custom_log=$1

base=$(cut -d" " -f1 /proc/uptime);
ut=$(date +%s);
# FIXME: Arithmetic operations are not fully supported in ash, use awk instead.
base=`date | awk "{now=$ut - $base; printf \"%d\", now}"`

dmesg -c >> $SYSLOG # clear circular syslog buffer
while true
do
    dmesg -c | sed "s/^\[[ ]*\?\([0-9.]*\)\] \(.*\)/\\1 \\2/" |
    while read ts msg; do
        now=`date | awk "{now=$base + $ts; printf \"%d\", now}"`
        newts=`date +"%m/%d/%Y %H:%M:%S" --date "@$now"` # human readable timestamp
        printf "[%s] %s\n" "$newts" "$msg";
    done | sed "s/$/$(printf '\r')/" | tee -a $SYSLOG $custom_log

    sleep 1
done

Debug Kernel Space Memory Leak

Posted by Bo Yang at 2015-03-30 with tags Notes , Unix/Linux


1. Detect Memory Leak

Memory leak can be detected by monitoring the free memory periodically. Command free can be used to show rough memory usage. A more detailed way to analyze memroy is cat /proc/meminfo and cat /proc/slabinfo . /proc/meminfo contains info about total memory, free memory, total highmem, free highmem, total lowmem, free lowmem and etc. Usually highmem is user-space memory, while lowmem is kernel-space memory. If free highmem(HighFree) or free lowmem(LowFree) is continuously decreasing, in most cases it means user space or kernel space memory leaking.

Once detected memory leaking, we need to determine which slab(s) is(are) leaking. This can be done by monitoring /proc/slabinfo . If the number of a slab’s active objects(column 2) or total objects(column 3) keep increasing, then this slab is very likely leaking memory.

Following script can be used to monitor both meminfo and slabinfo:

#!/bin/sh

MAX_SIZE=20000000
MON_FILE=/path/to/monitor_output_$(uname -n)
while true
do
    date >> $MON_FILE
    cat /proc/meminfo >> $MON_FILE
    echo "----------------------" >> $MON_FILE
    cat /proc/slabinfo >> $MON_FILE
    echo "----------------------" >> $MON_FILE
    ps -o pid,comm,stat,time,rss,vsz >> $MON_FILE
    echo "++++++++++++++++++++++" >> $MON_FILE
    fsize=`ls -l $MON_FILE | awk '{print $5}'`
    if [ $fsize -gt $MAX_SIZE ]; then
    	# upload file to TFTP server
        suffix=`cat /proc/uptime | cut -d" " -f1`
        mv $MON_FILE $MON_FILE.$suffix
        # upload to cloud
    fi
    sleep 300
done

After continuously monitoring for hours, simply grep some keywords(e.g. HighFree, LowFree, kmalloc-192, etc.) could find the trend of memory usage. Following data is extracted from a real memory leak monitoring log:

LowFree:          599392 kB
LowFree:          571072 kB
LowFree:          544484 kB
LowFree:          516832 kB
LowFree:          489232 kB
LowFree:          462280 kB
LowFree:          433680 kB
LowFree:          405244 kB
LowFree:          378572 kB
LowFree:          350136 kB
LowFree:          322648 kB
LowFree:          295824 kB
LowFree:          267532 kB
LowFree:          238272 kB
LowFree:          210856 kB
LowFree:          181148 kB
LowFree:          153652 kB
LowFree:          123148 kB
LowFree:          599392 kB
LowFree:          571072 kB
LowFree:          544484 kB
LowFree:          181148 kB
LowFree:          153652 kB
LowFree:          123148 kB
LowFree:           94548 kB
LowFree:           94548 kB

2. Debug Memory Leak

After finding out the leaking slab, more info could be detected by tracing that slab. If the kernel was built with option CONFIG_SLUB_DEBUG , the simplest way is to issue command echo 1 > /sys/kernel/slab/<leaking_slab>/trace . Then the memory allocation trace for this slab will be printed to the console:

[  375.201468] TRACE kmalloc-4096 alloc 0xe6be6000 inuse=8 fp=0x  (null)
[  375.207872] Backtrace:
[  375.210309] [<c0012378>] (dump_backtrace+0x0/0x114) from [<c03a0a5c>] (dump_stack+0x18/0x1c)
[  375.218712]  r6:ef300480 r5:e6be6000 r4:c0d40c00 r3:c07004c4
[  375.224367] [<c03a0a44>] (dump_stack+0x0/0x1c) from [<c03a2738>] (alloc_debug_processing+0xc8/0x164)
[  375.233489] [<c03a2670>] (alloc_debug_processing+0x0/0x164) from [<c03a2d0c>] (__slab_alloc.isra.50.constprop.56+0x538/0x5dc)
[  375.244767]  r7:80080008 r6:00080007 r5:e6be6000 r4:c0d40c00
[  375.250421] [<c03a27d4>] (__slab_alloc.isra.50.constprop.56+0x0/0x5dc) from [<c00e872c>] (__kmalloc_track_caller+0xbc/0x190)
[  375.261605] [<c00e8670>] (__kmalloc_track_caller+0x0/0x190) from [<c031f96c>] (__alloc_skb+0x58/0xf4)
[  375.270790] [<c031f914>] (__alloc_skb+0x0/0xf4) from [<c03200d4>] (dev_alloc_skb+0x40/0x64)
[  375.279131] [<c0320094>] (dev_alloc_skb+0x0/0x64) from [<bf6bb504>] (__adf_nbuf_alloc+0x24/0xa4 [adf])
[  375.288409]  r4:ea5c8600 r3:00000004
[  375.292158] [<bf6bb4e0>] (__adf_nbuf_alloc+0x0/0xa4 [adf]) from [<bf8d5f40>] (htt_rx_ring_fill_n+0x34/0x108 [umac])
[  375.302405]  r7:00000000 r6:000005b1 r5:ea5c8720 r4:ea5c8600
[  375.308372] [<bf8d5f0c>] (htt_rx_ring_fill_n+0x0/0x108 [umac]) from [<bf8d6888>] (htt_rx_msdu_buff_replenish+0x54/0x6c [umac])
[  375.319400]  r8:bf927c04 r7:eaaee9c0 r6:eb5b3c00 r5:ea5c8720 r4:ea5c8600
[  375.326429] [<bf8d6834>] (htt_rx_msdu_buff_replenish+0x0/0x6c [umac]) from [<bf8c6b24>] (ol_rx_indication_handler+0x7bc/0x8cc [umac])
[  375.338081]  r5:ea5c8600 r4:00000000
[  375.341955] [<bf8c6368>] (ol_rx_indication_handler+0x0/0x8cc [umac]) from [<bf8d770c>] (htt_t2h_msg_handler_fast+0xac/0x280 [umac])
[  375.353764] [<bf8d7660>] (htt_t2h_msg_handler_fast+0x0/0x280 [umac]) from [<bf8c02dc>] (CE_per_engine_service_each+0x178/0x4b4 [umac])
[  375.365823] [<bf8c0164>] (CE_per_engine_service_each+0x0/0x4b4 [umac]) from [<bf8c3634>] (ath_tasklet+0x68/0x128 [umac])
[  375.376507] [<bf8c35cc>] (ath_tasklet+0x0/0x128 [umac]) from [<c0064478>] (tasklet_action+0xa0/0x11c)
[  375.385567]  r6:e8b88000 r5:c435ef44 r4:c435ef40
[  375.390159] [<c00643d8>] (tasklet_action+0x0/0x11c) from [<c006495c>] (__do_softirq+0x140/0x34c)
[  375.398937] [<c006481c>] (__do_softirq+0x0/0x34c) from [<c0064d38>] (do_softirq+0x4c/0x58)
[  375.407185] [<c0064cec>] (do_softirq+0x0/0x58) from [<c0064dd0>] (local_bh_enable_ip+0x8c/0xcc)
[  375.415870]  r4:e8b88000 r3:0000004a
[  375.419431] [<c0064d44>] (local_bh_enable_ip+0x0/0xcc) from [<c03aa68c>] (_raw_spin_unlock_bh+0x54/0x58)
[  375.428865]  r5:00000304 r4:e9662c00
[  375.432427] [<c03aa638>] (_raw_spin_unlock_bh+0x0/0x58) from [<c038a47c>] (packet_poll+0xa4/0xe4)
[  375.441299] [<c038a3d8>] (packet_poll+0x0/0xe4) from [<c0316e34>] (sock_poll+0x24/0x28)
[  375.449265]  r7:ea9ffe40 r6:00000000 r5:e8b89c4c r4:e8b89c04
[  375.454920] [<c0316e10>] (sock_poll+0x0/0x28) from [<c00fc914>] (do_sys_poll+0x20c/0x3e8)
[  375.463074] [<c00fc708>] (do_sys_poll+0x0/0x3e8) from [<c00fcbb0>] (sys_poll+0x64/0xd0)
[  375.471071] [<c00fcb4c>] (sys_poll+0x0/0xd0) from [<c000e7c0>] (ret_fast_syscall+0x0/0x30)
[  375.479318]  r6:0007a120 r5:00000000 r4:6b3f60a0

The first line could only be “TRACE kmalloc-4096 alloc” or “free”, which logs the entry address of this slab. So if the memory leak is very fast, it is possible to monitor all of the alloc/free slabs before the system out of memory. Then find out addresses that never freed, analyze the call traces, and hopefull we could detect the problematic module or functions.

Linux System Log

Posted by Bo Yang at 2015-01-12 with tags Notes , Unix/Linux


  1. Overview
  2. printk
  3. klogd
  4. syslog
  5. dmesg
  6. syslog-ng
  7. Convert Timestamp

1. Overview

Linux adopts a ring buffer in kernel with a size of __LOG_BUF_LEN bytes to store system logs, where __LOG_BUF_LEN equals ( 1 << CONFIG_LOG_BUF_SHIFT ) (see kernel/printk.c for details). Using a ring buffer implies that older messages get overwritten once the buffer fills up, but this is only a minor drawback compared to the robustness of this solution (i.e. minimum memory footprint, callable from every context, not many resources wasted if nobody reads the buffer, no filling up of disk space/ram when some kernel process goes wild and spams the buffer, …). Using a reasonably large buffer size should give you enough time to read your important messages before they are overwritten.

The kernel log buffer is accessible for reading from userspace by /proc/kmsg . /proc/kmsg behaves more or less like a FIFO and blocks until new messages appear. Please note, reading from /proc/kmsg consumes the messages in the ring buffer so they may not be available for other programs. It is usually a good idea to let klogd or syslog do this job and read the content of the buffer via dmesg.

Linux Kernel Log

2. printk

printk is the kernel function to classify messages according to their severity by loglevels and write them to the circular system message buffer. The function then wakes any process that is waiting for messages, that is, any process that is sleeping in the syslog system call or that is reading /proc/kmsg . printk can be invoked from anywhere, even from an interrupt handler, with no limit on how much data can be printed.

printk( KERN_CRIT "Error code %08x.\n", val );

There are eight possible loglevel strings, defined in the header <linux/kernel.h>; we list them in order of decreasing severity:

Name String Meaning alias function
KERN_EMERG “0” Emergency messages, system is about to crash or is unstable pr_emerg
KERN_ALERT “1” Something bad happened and action must be taken immediately pr_alert
KERN_CRIT “2” A critical condition occurred like a serious hardware/software failure pr_crit
KERN_ERR “3” An error condition, often used by drivers to indicate difficulties with the hardware pr_err
KERN_WARNING “4” A warning, meaning nothing serious by itself but might indicate problems pr_warning
KERN_NOTICE “5” Nothing serious, but notably nevertheless. Often used to report security events. pr_notice
KERN_INFO “6” Informational message e.g. startup information at driver initialization pr_info
KERN_DEBUG “7” Debug messages pr_debug, pr_devel if DEBUG is defined
KERN_DEFAULT “d” The default kernel loglevel
KERN_CONT ”” “continued” line of log printout (only done after a line that had no enclosing \n ) pr_cont

Each string (in the macro expansion) represents an integer in angle brackets. Integers range from 0 to 7, with smaller values representing higher priorities.

A printk statement with no specified priority defaults to DEFAULT_MESSAGE_LOGLEVEL , specified in kernel/printk.c as an integer. For this the kernel compares the log level of the message to the console_loglevel (a kernel variable) and if the priority is higher (i.e. a lower value) than the console_loglevel the message will be printed to the current console. The console_loglevel can be checked by

# cat /proc/sys/kernel/printk
7       4       1       7

The first integer shows you your current console_loglevel ; the second is the DEFAULT_MESSAGE_LOGLEVEL .

Kernel log timestamp is added by vprintk() , in kernel/printk.c :

#if defined(CONFIG_PRINTK_TIME)
static bool printk_time = 1;
#else
static bool printk_time = 0;
#endif

if (printk_time) {
		/* Add the current time stamp */
		char tbuf[50], *tp;
		unsigned tlen;
		unsigned long long t;
		unsigned long nanosec_rem;

		t = cpu_clock(printk_cpu);
		nanosec_rem = do_div(t, 1000000000);
		tlen = sprintf(tbuf, "[%5lu.%06lu] ",
				(unsigned long) t,
				nanosec_rem / 1000);

		for (tp = tbuf; tp < tbuf + tlen; tp++)
			emit_log_char(*tp);
		printed_len += tlen;
}

3. klogd

If the klogd process is running, it retrieves kernel messages and dispatches them to syslogd , which in turn checks /etc/syslog.conf to find out how to deal with them. syslogd differentiates between messages according to a facility and a priority; allowable values for both the facility and the priority are defined in <sys/syslog.h> . Kernel messages are logged by the LOG_KERN facility at a priority corresponding to the one used in printk (for example, LOG_ERR is used for KERN_ERR messages). If klogd isn’t running, data remains in the circular buffer until someone reads it or the buffer overflows.

If you want to avoid clobbering your system log with the monitoring messages from your driver, you can either specify the (file) option to klogd to instruct it to save messages to a specific file, or customize /etc/syslog.conf to suit your needs. Yet another possibility is to take the brute-force approach: kill klogd and verbosely print messages on an unused virtual terminal, or issue the command cat /proc/kmsg from an unused xterm.

4. syslog

Accessing to the log buffer is provided at the core through the multi-purpose syslog system call. The prototype for the syslog system call is defined in ./linux/include/linux/syslog.h ; its implementation is in ./linux/kernel/printk.c .

The syslog call serves as the input/output (I/O) and control interface to the kernel’s log message ring buffer. From the syslog call, an application can read log messages (partial, in their entirety, or only new messages) as well as control the behavior of the ring buffer (clear contents, set the level of messages to be logged, enable or disable console, and so on).

Although reading from /proc/kmsg consumes the data from the log buffer, the syslog system call can optionally return log data while leaving it for other processes as well.

Kernel space syslog API:

#include <syslog.h>

void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);

#include <stdarg.h>

void vsyslog(int priority, const char *format, va_list ap);
  • closelog() closes the descriptor being used to write to the system logger.
  • openlog() opens a connection to the system logger for a program.
  • syslog() generates a log message, which will be distributed by syslogd . It does this by writing to the Unix domain socket /dev/log .
  • vsyslog() is functionally identical to syslog() , with the BSD style variable length argument.

User space syslog API(glibc wrapper):

int syslog( int type, char *bufp, int len );
int klogctl( int type, char *bufp, int len );

klogctl() is the glibc wrapper to control the kernel printk() buffer.

5. dmesg

The dmesg command is used to print and control the kernel ring buffer. This command uses the klogctl system call to read the kernel ring buffer and emit it to standard output (stdout). The command can also be used to clear the kernel ring buffer (using the -c option), set the level for logging to the console (the -n option), and define the size of the buffer used to read the kernel log messages (the -s option).

dmesg reads by default a buffer of max 16392 bytes, so if you use a larger log buffer you have to invoke dmesg with the -s parameter e.g.:

### CONFIG_LOG_BUF_SHIFT 17 = 128k
$ dmesg -s 128000

6. syslog-ng

The syslog-ng application is a flexible and highly scalable system logging application that is ideal for creating centralized and trusted logging solutions. It extends the original syslogd model with content-based filtering, rich filtering capabilities, flexible configuration options and adds important features to syslog.

syslog-ng also supports ISO/RFC timestamp for system logs. For more info about this powerful log system, please refer to the manual .

7. Converting Timestamps

By default the time stamps are printed in “seconds since boot” (this is the way the kernel is programmed to print the time stamps in vprintk() , and it can not be changed to print the time stamps in a human readable format). The system uptime can be helpful to calculate an absolute time stamp if needed (run the uptime command).

Given timestamp:

[196149.728085] hello world

The algorithm below converts the printed time stamps to a human readable format:

1. Take the log's time stamp in seconds: 

196149.728085 seconds (round the number down/up if needed) 

2. Divide the time stamp in seconds by 60 to get the total amount of minutes: 

196150 : 60 = 3269.1667 minutes (round the number down/up if needed) 

3. Divide the time stamp in minutes by 60 to get the total amount of hours: 

3269.1667 : 60 = 54.486111666666666666666666666667 hours 

4. Break the decimal number into 2 parts: 

54.486111666666666666666666666667 hours = (54 hours) + (0.486111666666666666666666666667 decimal hours) 

5. Use the time conversion charts below to convert decimal hours to minutes: 

0.486111666666666666666666666667 decimal hours ~ 0.48 decimal hours ~ 29 minutes 

6. Note: For more precise conversion (down to seconds), you can use various time converters available on the Internet. Just search for 'convert decimal time' in any search engine. 

7. Hence, we get that the log was created this amount of time since boot: 

196149.728085 seconds ~ 54 hours 29 minutes 

8. Check the current system's uptime: 

[Expert@HostName]# uptime 

9. To get the log's real time stamp, subtract the log's time stamp in dmesg kernel ring buffer from the current system's uptime.

Following is a Shell script to transform uptime timestamp to human-readable timestamp:

#!/bin/bash
# Translate dmesg timestamps to human readable format

# desired date format
date_format="%a %b %d %T %Y"

# uptime in seconds
uptime=$(cut -d " " -f 1 /proc/uptime)

# run only if timestamps are enabled
if [ "Y" = "$(cat /sys/module/printk/parameters/time)" ]; then
  dmesg | sed "s/^\[[ ]*\?\([0-9.]*\)\] \(.*\)/\\1 \\2/" | while read timestamp message; do
    #date +"%s" -d "1970-01-01 00:00:00"
    #awk '{printf("%d:%02d:%02d\n", ($1/3600), ($1%3600/60),($1%60))}' /proc/uptime
    printf "[%s] %s\n" "$(date --date "now - $uptime seconds + $timestamp seconds" +"${date_format}")" "$message"
  done
else
  echo "Timestamps are disabled (/sys/module/printk/parameters/time)"
fi

References

Click Notes II - Click Script Language

Posted by Bo Yang at 2015-01-07 with tags Notes , Click , Network


The Click programming language was developed to configure Click routers, but nowadays you also can use it to write test cases for Click elements.

  1. Basic Syntax
  2. Element Group
  3. Compound Element
  4. Script
  5. Testie

Basic Syntax

The Click Script Language defines a configuration graph, which consists of connected elements. Each element has an element class specified by class name. Elements are connected through their input and output ports. Input and output ports are distinguished by number, while elements are distinguished by name.

Click configuration strings are comma-separated lists of arguments delimited by parentheses. The fundamental syntax of Click Script Language is:

name :: class(config-string);    // declare element object
name1, name2, ..., nameN :: class(config); // declaration shorhand
name1[port1] -> [port2]name2;    // connect two elements
name1[port1] -> [port2a]name2[port2b] -> [port3]name3;    // piggyback connections
name1 -> name2 :: class(config-string) -> name3;  // declaring elements inside connections is allowed
name1 -> class(config-string) -> name3;  // anonymous element
require(requirement[, requirement …]);   // list config requirements

n1, n2 :: class -> n3;  // many-to-one connections

// many-to-many connections:
// A many-to-many connection matches output ports to input ports. 
// There must be as many ports on the left as on the right.
// '=>' is the many-to-many connector.
c[0], c[1], c[2] => Paint(0), Paint(1), Paint(2) -> next;
c => Paint(0), Paint(1), Paint(2) -> next;

Element Group

An element group is one or more Click statements enclosed in parentheses. Within the parentheses, the special pseudoelements input and output refer to connections from outside the group. Click expands the group at parse time, so connections through input and output have no run-time overhead. The following five lines are equivalent:

x -> y;
x -> ( input -> output ) -> y;
x -> ( [0] -> [0] ) -> y;
x -> (->) -> y;
x -> ( [0]->[0]; [1]->[1] ) => ( [0]->[0]; [1]->[1] ) -> y;

Line five uses the fact that connections may be repeated without error (the line expands to x -> y; x -> y ), where the explicit semicolons are used to avoid ambiguity.

Element groups have implicit, overridable port specifications that list all their ports in sequential order. For example, these three lines are equivalent:

x => ( [0]->[0]; [1]->[1] ) -> y;
x => [0,1] ( [0]->[0]; [1]->[1] ) [0,1] -> y;
x -> y; x [1] -> y;

An element group does not define a new scope. Its contents may refer to elements declared outside of the group, and declarations inside the group are visible after the group closes. This differs from compound elements, described next, which have a related syntax but additionally introduce a new scope.

Compound Element

A compound element is a scoped collection of elements that behaves like a single element. A compound element can be used anywhere an element class is expected (that is, in a declaration or connection). Syntactically, a compound element is a set of Click statements enclosed in braces { } . Inside the braces, the special names input and output represent connections from or to the outside.

Compound element classes are router configuration fragments consisted by Click elements and statements that are treated like element classes. For compound elements, only components remain in the final configuration graph, and all compound element structure is compiled away. This process is called flattening , during which compound element components are given names that reflect their origin. For example, a component named e of a compound element compound is named compound/e in the flattened configuration.

elementclass Name {    // compound elements
    ... Click Statements …  // defined by click statements but not C++ class
}
e :: {    // anonymous compound element class
    ... Click Statements … 
}
elementclass MyQueue Queue;   // define an alias for an existing element class

Like any element, compound elements may have input and output ports. Each connection to or from a compound element port is transformed by flattening into a connection to or from one of its components’ ports. Inside a compound element class, the special pseudoelements input and output specify how this transformation proceeds. Given a compound element e. If e/input connects to component e/c through port i , then every connection to e ’s input port i will flatten into a connection to its component e/c . For example,

elementclass Example {
    s1 :: InfiniteSource; s2 :: RatedSource;
    s1 -> [0]output; s2 -> [0]output;
}
e :: Example -> d :: Discard;

above code will be flattened to

e/s1 :: InfiniteSource; e/s2 :: RatedSource; d :: Discard;
e/s1 -> d; e/s2 -> d;

Compound element classes also can take varying number configuration arguments, input ports, and output ports. Formal parameters define the arguments that a compound element class should take. A formal parameter is a sequence of alphanumeric characters preceded by a dollar sign, such as $var . A compound element class may begin with a list of parameters.

While formal parameters let a compound element class support a fixed number of positional arguments, overloading lets several compound element definitions with different numbers of arguments or ports share a single name. As a side-effect, overloading also support different behaviors in a compound element based on numbers of input/output ports. Every compound element class can correspond to a number of definitions, which are textually separated by || , which are distinguished by the numbers of formal parameters, input ports or output ports. Given a declaration of compound element, the interpreter checks the number of arguments and search for a matching definition. If found one, then it is expanded; otherwise, an error will occur.

// Example 1: argument overloading
elementclass ShapedQueue {				
    input -> Queue -> Shaper(10000) -> output;
    ||
    $cap | input -> Queue($cap) -> Shaper(10000) -> output; 
}				
q1 :: ShapedQueue;    // OK; uses first definition
q2 :: ShapedQueue(1024);    // OK; uses second definition
q3 :: ShapedQueue(1024, 10000);   // error—no matching definition 

// Example 1: port overloading - additional output port				
elementclass VerboseCheckIPHeader {
    input -> c :: CheckIPHeader -> output; c[1] -> Print(CheckIPHeader) -> Discard; 
    ||
    input -> c :: CheckIPHeader -> output; c[1] -> Print(CheckIPHeader) -> [1]output;	
} 

Overloading also allows user to add new definitions to an existing element class. For example, following definition will add a two-argument version of Queue :

elementclass Queue {
  ... ||  // 'dot dot dot' is part of the syntax 
$capacity, $rate | input -> Queue($capacity)
                         -> Shaper($rate) -> output;
}
q1 :: Queue;        // built-in Queue
q2 :: Queue(1024);  // built-in Queue
q3 :: Queue(1024, 10000); // overloaded Queue definition above

When choosing the definition that corresponds to a given compound element declaration, Click only considers the definitions that were lexically visible at the point of declaration.

Compound element configuration arguments are only meaningful inside the configuration strings of components. So that you cannot change the element class of a given component, or cause components to be added to or subtracted from the compound. However, it is possible to build a compound element that sends packets through different sets of components based on the value of its configuration string. Following example uses StaticSwitch to implement a selective checksum check.(The CheckIPHeader element checks an IP header’s length and checksum for sanity; CheckIPHeader2 does the work of CheckIPHeader except for the checksum check.)

elementclass MaybeChecksum { $checksum_p |
    input -> sw :: StaticSwitch($checksum_p);
    sw[0] -> CheckIPHeader2 -> output;
    sw[1] -> CheckIPHeader -> output;
};
c1 :: MaybeChecksum(0);    // uses CheckIPHeader2, skips checksum
c2 :: MaybeChecksum(1);    // uses CheckIPHeader, checks checksum

Script

The Script element implements a simple scripting language interpreter useful for controlling Click configurations. Scripts can set variables, call handlers, wait for prodding from other elements, and stop the router. Script element is defined in click/elements/standard/Script.hh . For details of instructions and handlers, please refer to http://www.read.cs.ucla.edu/click/elements/script .

In the Script element, each keyword is handled by a corresponding handler:

void
Script::add_handlers()
{
    set_handler("step", Handler::OP_WRITE | Handler::h_nonconst, step_handler, 0, ST_STEP);
    set_handler("goto", Handler::OP_WRITE | Handler::h_nonconst, step_handler, 0, ST_GOTO);
    set_handler("run", Handler::OP_READ | Handler::READ_PARAM | Handler::OP_WRITE | Handler::h_nonconst, step_handler, 0, ST_RUN);
    set_handler("add", Handler::OP_READ | Handler::READ_PARAM, arithmetic_handler, ar_add, 0);
    set_handler("sub", Handler::OP_READ | Handler::READ_PARAM, arithmetic_handler, ar_sub, 0);
    set_handler("min", Handler::OP_READ | Handler::READ_PARAM, arithmetic_handler, ar_min, 0);
    set_handler("max", Handler::OP_READ | Handler::READ_PARAM, arithmetic_handler, ar_max, 0);
        set_handler("length", Handler::OP_READ | Handler::READ_PARAM, basic_handler, ar_length, 0);
    set_handler("unquote", Handler::OP_READ | Handler::READ_PARAM, basic_handler, ar_unquote, 0);
…….
}
All of the handlers defined in the Script element need to parse the script language, which is implemented in click/lib/confparse.[hh cc]. In this way, the Script element could serve as an interpreter to the script instructions and handlers.

Testie

Testie is a simple test tool for Click elements, which enables the Test-Driven-Development of Click. Each testie file is written mainly in Click script language and incorporates a shell script to be run. Tool click/test/testie runs the Click script, and checks the expected error or output. A testie file may have following layout:

%info 
// a short description of the test.

%require [-q]
// prerequisites that must be satisfied before the test can run.

%include FILENAME
// interpolate the contents of another testie file.

%script
// Shell scripts that controls the test. Testie will run each command in sequence.
// Command “click” need to be specified to interpret Click scripts.

%file [-d] [+LENGTH] FILENAME...
// Create an input file for the script. FILENAME can be `stdin`, which sets the script's standard input.

%expectv [-ad] [+LENGTH] FILENAME...
%expect [-adiw] [+LENGTH] FILENAME…
%expectx [-adiw] [+LENGTH] FILENAME...
// An expected output file for the script. FILENAME can be 'stdout' or 'stderr'. 
// Testie will run the script, then compare the file generated by script with the provided data. The files are compared line-by-line. 
// The -a flag marks this expected output as an alternate. Testie will compare the script's output file with each provided alternate; the test succeeds if any of the alternates match. 
// The -d flag behaves as in %file. 
// The -i flag makes any regular expressions case-insensitive (text outside of regular expressions must match case), and the -w flag ignores any differences in amount of whitespace within a line.

%ignorex [-di] [+LENGTH] [FILENAME]
%ignore, %ignorev
// lines to be ignored.

In addition to above sections, you also can define one or multiple Script elements in the testie file, which group a set of Click Script instructions.

click/test/testie is the Perl tool to run testie files. Given a testie file, the testie interpreter will first read and parse each section defined in the testie file: %require files will be expanded, files defined in section %file will be created in a temporary directory, commands listed under %script will be recorded in a hash, and so on. After preprocessing, the commands will be executed by shell or interpreted by click. click/test/testie is a very good template of simple interpreter.

References

Configuring SELinux Permissions for Apache Custom Log Paths

Apache on RHEL-derived distributions ships with a hardened security policy that dictates exactly where the httpd process may read, write, and execute. When administrators in Sydney hosting providers or Melbourne-based enterprise stacks move Apache logs away from /var/log/httpd to a network-attached directory or a structured logging partition, SELinux steps in and blocks the write attempt with an AVC denial. The result is empty log files, silent services, and a support ticket that nobody enjoys filing.

The typical Australian deployment now involves logging shipped to a central syslog receiver under the Notifiable Data Breaches scheme, or stored on block devices attached to a hypervisor in a Canberra data centre. Each of these scenarios rewrites the default /var/log/httpd contract and forces a deliberate conversation about SELinux policy. Rather than disabling enforcement, which would also weaken compliance posture against the Essential Eight, the right answer is to teach SELinux that the new path is legitimate.

This guide walks through the practical steps needed to let httpd write to a custom log directory while keeping the targeted policy intact. It covers directory creation, fcontext labelling, boolean tuning, and how to read audit logs when something still refuses to cooperate. The commands assume CentOS Stream or Rocky Linux 9, though they apply broadly to any distribution shipping the selinux-policy-targeted package.

Before changing anything, it helps to remember that SELinux evaluates every operation through a Type Enforcement model. A mislabelled directory will be rejected even if standard POSIX permissions are wide open, so the fix almost always lives in the policy layer rather than in chmod.

Understanding SELinux Contexts for the HTTPD Process

Every file, directory, and process on a system with SELinux enabled carries a security context made up of a user, role, type, and sensitivity level. The type field is what matters most for the httpd daemon, because the targeted policy uses type transitions to decide whether a process may perform a given action on an object. By default, httpd runs in the httpd_t domain, and the directories it may write to are labelled httpd_log_t.

When a custom log path lives outside the standard hierarchy, the inherited type from the parent directory is usually something innocuous like var_t or admin_home_t. The kernel sees the mismatch and returns EACCES through the AVC subsystem, which auditd records. From httpd's point of view the directory looks perfectly readable, but from SELinux's point of view, the type is wrong.

Default Path Typical SELinux Type Read by httpd Write by httpd
/var/log/httpd/ httpd_log_t Yes Yes
/var/www/html/ httpd_sys_content_t Yes No
/etc/httpd/conf/ httpd_config_t Yes No
/var/lib/php/ httpd_var_lib_t Yes Limited
Custom log dir (default label) var_t Yes No
Custom log dir (labelled httpd_log_t) httpd_log_t Yes Yes

The table shows how the type alone governs write access. POSIX permissions matter, but they are evaluated after the SELinux decision, so an open mode bit cannot rescue a denied context.

Audit Logs and Locating the Denial Source

The fastest way to diagnose a silent log failure is to read the audit log. Most CentOS and Rocky installations route AVC messages to /var/log/audit/audit.log, where each denial includes the source context, target context, and the requested permission. Administrators running a centralised logging stack in Adelaide or Brisbane often forward these events to a SIEM, but the local file remains the authoritative source during a firefight.

A useful one-liner filters the audit log for httpd-related denials and renders them in human-readable form. The ausearch tool with the -m avc flag, combined with a recent timestamp window, surfaces only the relevant records. Each entry lists the scontext (httpd_t) and tcontext (the mislabelled path), which together identify exactly which file context needs correction.

For teams that prefer a graphical view, the sealert utility reads the same audit data and explains the rejection in plain language. It also suggests candidate fixes, although those suggestions should be reviewed against the organisation's security baseline before being applied automatically. Australian hosts bound by the Australian Government Information Security Manual often restrict which booleans may be flipped, so a manual review is worth the few extra minutes.

Creating the Custom Log Directory Structure

The directory itself should be created before any SELinux changes, because the kernel reads the current type when applying labels. A common pattern is /var/log/httpd-custom, owned by root and grouped under root, with mode 0750. Ownership is less important than the label, since SELinux will override POSIX rules when the two disagree.

If the directory lives on a separate filesystem, such as an XFS volume mounted from a local SSD tier used for high-throughput logging, the mount options may also matter. The context mount option can apply a default type to everything beneath the mount point, which is convenient but coarse. For finer control, mount with defcontext and apply specific fcontext rules to subdirectories as needed.

Avoid symlinks pointing back into the standard /var/log tree. SELinux follows symlinks and applies the target's context, which can produce confusing denials when the link crosses filesystem boundaries. A real directory, owned by the right service account and labelled explicitly, behaves predictably across reboots and policy reloads.

Setting File Contexts With Semanage Fcontext

The semanage fcontext command writes a new rule into the policy database that instructs the kernel how to label matching paths. For a single directory, the rule typically uses a fully qualified path followed by the desired type. The restorecon utility then applies the saved rule to the filesystem, reading from the database rather than inferring from parent directories.

The exact syntax matters. The path must end with a regular expression that describes what should be labelled, usually (/.+)? to match the directory and everything beneath it. Without that suffix, only the directory itself receives the new label, and files created later inherit var_t again from the parent. A correctly formed rule survives file creation, because restorecon walks the directory tree whenever it is invoked.

For Australian administrators managing multiple hosts through Ansible or Puppet, the equivalent manifest or playbook task calls semanage and restorecon in the same module. Keeping the fcontext rule and the apply step in one place prevents drift between what the policy database says and what the live filesystem shows.

Applying Labels and Verifying the Configuration

After running restorecon, the ls -Z command on the custom directory should display httpd_log_t in the type column. If the output still shows var_t, the most likely culprit is a typo in the fcontext rule or a missed restorecon pass. The -v flag on restorecon prints every file it relabels, which is invaluable when chasing a stubborn mislabel across nested directories.

A quick sanity check is to reload httpd and tail the custom log file while issuing a request. The access log entry should appear within a second or two. If it does not, audit.log will hold the next clue, and the cycle repeats. Many operators script this loop: trigger a request, wait two seconds, grep audit.log, and alert on any new AVC record containing httpd_t.

For a deployment that must satisfy the ACSC's Essential Eight maturity targets, automated verification is worth building into the configuration management pipeline. A simple test that asserts the log directory's type matches httpd_log_t catches regressions before they reach production, particularly after a major policy update or a system upgrade that ships a new selinux-policy package.

Adjusting Booleans for Broader HTTPD Permissions

Sometimes the custom log path is only part of the story. If Apache also needs to write to a directory used for runtime caching, session storage, or generated reports, a single fcontext rule may not be enough. SELinux booleans offer a way to grant broader capabilities to the httpd domain without writing custom policy modules.

The getsebool and setsebool commands list and toggle these switches. To make a change survive reboots, use the -P flag. Common booleans for logging scenarios include httpd_log_io, which permits httpd to perform general I/O on log files, and httpd_read_user_content, which lets the daemon read user home directories if logs happen to live under /home.

Boolean changes should be documented and reviewed. Each one is a small expansion of the httpd domain's reach, and over time a stack of toggled booleans can quietly widen the attack surface. A periodic audit of getsebool -a | grep httpd keeps the configuration honest and provides evidence for compliance reporting under the Privacy Act 1988.

Troubleshooting Persistent AVC Denials

When the standard approach does not resolve the denial, the next step is to install the setroubleshoot-server package and run sealert -a against a recent audit log snapshot. The tool correlates related denials and proposes candidate fixes, which often include a custom policy module generated by audit2allow.

The audit2allow utility reads AVC records and produces a type enforcement rule that, if accepted, would have prevented the denial. Generating a module is straightforward: pipe the relevant ausearch output through audit2allow -M to create a .te and a .pp file, then load it with semodule -i. This should be a last resort, because each custom module increases the maintenance burden of policy upgrades and can mask underlying configuration mistakes.

If the issue persists after fcontext, booleans, and a custom module, double-check the inode transition rules by listing the parent directory's context. Sometimes a directory created during a kickstart or cloud-init run inherits a non-standard type that overrides the fcontext rule's match. The fix in that case is usually a targeted restorecon with the -F flag to force relabelling across the whole subtree.

Once the logs flow as expected, commit the fcontext rule, the boolean settings, and any custom module to the configuration management repository. Document the reasoning in a runbook so the next administrator, perhaps a colleague in Perth picking up the pager during an AEDT overnight incident, understands why each line exists. For a broader look at how policy decisions interact with network services in mixed environments, the emi-cbr-notes-1 write-up offers useful background on labelling conventions that complement the steps above.

Try the configuration on a non-production host first, then roll it out through your usual automation pipeline once the AVC log stays quiet under load.

Building Remote+Local *nix Develop Environment(II)

Posted by Bo Yang at 2014-12-19 with tags Unix/Linux , Notes


This is the second article(collection) on how to build a *nix development environment by integrating remote servers and local Linux/Mac clients. For the previous article on this topic, please refer to Building Remote+Local *nix Develop Environment .

1. Vim Tips & Plugins

1.1 Highlight All Instances of Word Under Cursor

Add following line into your $HOME/.vimrc

autocmd CursorMoved * exe printf('match IncSearch /\V\<%s\>/', escape(expand('<cword>'), '/\'))

Or use a more complicated one in the .vimrc :

" Highlight all instances of word under cursor, when idle.
" Useful when studying strange source code.
" Type z/ to toggle highlighting on/off.

nnoremap z/ :if AutoHighlightToggle()<Bar>set hls<Bar>endif<CR>
function! AutoHighlightToggle()
  let @/ = ''
  if exists('#auto_highlight')
    au! auto_highlight
    augroup! auto_highlight
    setl updatetime=4000
    echo 'Highlight current word: off'
    return 0
  else
    augroup auto_highlight
      au!
      au CursorHold * let @/ = '\V\<'.escape(expand('<cword>'), '\').'\>'
    augroup end
    setl updatetime=500
    echo 'Highlight current word: ON'
    return 1
  endif
endfunction

1.2 Automatically Load Ctags

If you have generated ctags file, then you can automatically load it by:

  • export CTAGS_TAG in $HOME/.bashrc by export CTAGS_TAG=/path/to/your/tags .

  • add following lines into your $HOME/.vimrc

```Shell

if filereadable($CTAGS_TAG)
    set tags=$CTAGS_TAG
endif

```

1.3 Most Recently Used(MRU) Files

If you want to access the most recently used files in Vim, you need plugin MRU . The :MRU command will show you all the recently used files, and you can choose a file and press <Enter> to open it in current window. In addition

  • To open a file from the MRU window in a new tab, press the t key.
  • You can open multiple files from the MRU window by specifying a count before pressing <Enter> or v or o or t .
  • You can close the MRU window by pressing the q key or the <Esc> key or using one of the Vim window commands.
  • You can specify a pattern to the :MRU command, such as :MRU <pattern> .

1.4 Pathogen

Vim runtimepath manager, widely used by many plugins. Adding call pathogen#infect() to your .vimrc , then any plugins you wish to install can be extracted to a subdirectory under ~/.vim/bundle . And they will be added to the runtimepath .

1.5 NERDTree & NERDTree Tabs

The NERD tree allows you to explore your filesystem and to open files and directories, and NERDTree Tabs can make NERDTree available for all Vim tabs(sometimes it is useful). After installing the these plugins, you can add the following lines to .vimrc .

" Nerd Tree
" let g:NERDTreeDirArrows=0 " Do not use new arrows for directories
map <C-n> :NERDTreeToggle<CR>
let g:nerdtree_tabs_open_on_gui_startup=0 " no nerdtree_tabs by default

For some Linux distributions, the NERDTree could not show arrows for directories, then you need to uncomment the line let g:NERDTreeDirArrows=0 in your .vimrc .

1.6 Supertab

Supertab is a vim plugin which allows you to use <Tab> for all your insert completion needs (:help ins-completion).

1.7 CtrlP & Command-T

These two plugins are used for searching/opening files(even not in ctags) in Vim. CtrlP is written in pure Vimscript, so it is very slow. Although Command-T is faster, it relies on Ruby, which makes it difficult to install. Actually, I rarely use them in daily work.

My vimrc can be found at https://github.com/bo-yang/misc/blob/master/vimrc .

2. Cscope

Cscope is a tool for browsing source code. You can either run cscope standalone or use it with Vim . No matter in which way, you need to generate cscope database first. And the cscope DB depends on the source files you specified. General steps of using cscope are:

find /my/project/dir -name '*.c' -o -name '*.h' > /foo/cscope.files
cd /foo
cscope -b
CSCOPE_DB=/foo/cscope.out; export CSCOPE_DB

In Vim, you can load cscope DB by command :cs add <path_to_cscope_db> . For more cscope operations in Vim, please run command :cs help . To automatically load cscope into Vim, you can export CSCOPE_DB in $HOME/.bashrc , such as

export CSCOPE_DB=/path/to/cscope.out

Then the CSCOPE_DB will be automatically loaded every time you run Vim.

To save the effort of building cscope DB, I wrote a cross-platform(Linux & Mac OS X) wrapper script, which can be found in my GitHub channel .

3. sshfs Wrapper

Since sshfs command requires too much parameters, and things will be worse when the network is not stable. Following script will ease your pain.

#!/bin/sh

USER=<your_name>
SERVER=<your_server>
remote_dir=/nobackup/$USER
local_dir=$HOME/Documents/VMs

if [ ! -d ${local_dir} -o ! -s ${local_dir} ]
then
	sudo umount -f $local_dir
fi

cd ${local_dir}
sshfs $USER@${SERVER}:${remote_dir} ${local_dir}

Be careful to pthread_exit() in main()

Posted by Bo Yang at 2014-11-20 with tags Unix/Linux , Multithreading , Notes


When using pthread for multithreading, most threads call pthread_exit() implicitly on return from the thread start routine. Besides, pthread_exit() also can be used to terminate the initial process thread in main() , leaving other threads to continue operation. The process will go away automatically when the last thread terminates. If you don’t care about the process exit status, or if is difficult to know the created thread IDs(e.g. created by third party APIs), you can call

pthread_detach(pthread_self());
pthread_exit(NULL);

at the end of the main() function.

However, you must be carefull when using pthread_exit() in the main thread. Because after calling pthread_exit() and before the process really terminate, the process becomes “zombie” - it still exists even though it is “dead”, just like a Unix/Linux process that’s terminated but hasn’t yet been “reaped” by a wait operation. The zombie process may retain most or all of the system resources that it used when running, so it is not a good idea to leave threads in this state for longer than necessary. And obviously, zombie process cannot save you resources! So also don’t try pthread_exit() for saving CPU and memory.

I also noticed a undocumented problem caused by pthread_exit() - it may lead to failure of open procfs( /proc/ ) files! If one of your threads would open /proc/mounts ( currently I only find this file will go wrong, and other procfs files like /proc/cpuinfo or /proc/uptime can be successfully opened ) during its life, and pthread_exit() is called after creating these threads in the main thread, you will meet the “Invalid argument” error because of functions like open("/proc/mounts",'r') .

Following program demonstrates this problem:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS	5

void *PrintHello(void *threadid)
{
   long tid;
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);

   sleep(10); // sleep sometime

   /* Read file */
   FILE* fp=fopen("/proc/mounts","r"); // would fail
   //FILE* fp=fopen("/proc/cpuinfo","r");  // would succeed
   if(fp==NULL) {
	   fprintf(stderr,"Failed to open file!\n");
   } else {
	   char line[80];
	   if(fgets(line,80,fp)==NULL)
		   fprintf(stderr,"Failed to read file!\n");
	   else
		   fprintf(stdout,"%s\n",line);
	   fclose(fp);
   }

   sleep(1200);
   pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0;t<NUM_THREADS;t++){
     printf("In main: creating thread %ld\n", t);
     rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
     if (rc){
       printf("ERROR; return code from pthread_create() is %d\n", rc);
       exit(-1);
       }
     }

   /* Last thing that main() should do */
   pthread_detach(pthread_self());
   pthread_exit(NULL);
}

After building above code, say pthread_exit_test , run this program and find the PID of it. Then cat /proc/<PID>/status , you will find the process status like

Name:	pthread_exit_te
State:	Z (zombie)
Tgid:	3091
Pid:	3091
PPid:	2849
TracerPid:	0
Uid:	370845	370845	370845	370845
Gid:	25	25	25	25
Utrace:	0
FDSize:	0
Groups:	25 1000000312 
Threads:	6
SigQ:	0/78966
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000000000004
SigCgt:	0000000180000000
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	ffffffffffffffff
Cpus_allowed:	3f
Cpus_allowed_list:	0-5
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	14
nonvoluntary_ctxt_switches:	1

For the above program, if replace pthread_exit() with pthread_join() or while(1) {sleep(120); } , it would work well. The while loop is especially usefull when you don’t know the thread id to be joined.

Until now I am still don’t know why openning /proc/mounts would fail and why openning /proc/cpuinfo could succeed in above code. I also tried other system file or link, and found all of them could be successively readed.

References

  1. Should pthread_exit() be used in main()?
  2. Programming with POSIX Threads