5 Useful Unix DD Command Examples

dd is a common Unix program whose primary purpose is the low-level copying and conversion of raw data. You can backup whole hard drives, create a large file filled with only zeros, create and modify image files at specific points, and even do conversions to upper case.

To display dd‘s help simply enter:

erik@debian:~$dd –help

Alright, lets get to the juicy stuff.

1. Make an ISO of a your favourite CD just for backing up purposes with dd:

dd if=/dev/cdrom of=/home/erik/myCD.iso bs=2048 conv=sync

Breaking down the commands:

  • if is “input file”, so in this case our cdrom drive at /dev/cdrom
  • of is “output file”, in this case myCD.iso
  • bs is “block size”, in this case 2048 bytes per block
  • conv is for conversion, in this case we are using “sync” which tells DD to execute synchronized input and output, this is needed for the CD-ROM as we want to read a whole block to ensure no data loss occurs.

2. Duplicate one hard disk partition to another hard disk with dd:

dd if=/dev/sda1 of=/dev/sdb1 bs=4096 conv=noerror

In this case everything is the same as example 1 but our conversion methods states that noerror should be executed, this tells DD to continue after read errors.

3. Fill a file with 1MB of random bytes with dd:

erik@debian:~$dd if=/dev/urandom bs=1024 count=1000 of=fun.bin 1000+0 records in 1000+0 records out 1024000 bytes (1.0 MB) copied, 0.198349 s, 5.2 MB/s

This time I stated that our block size is 1024 bytes, and we are going to make 1000 of them sequentially. I also used the built-in kernel device urandom which provides random bytes.

4. Skip first 128K of input file then write remaining with dd:

dd if=/home/erik/fun.bin skip=128k bs=1 of=/home/erik/fun2.bin

The skip command tells DD to move passed the (in this case) 128k of data in fun.bin then write the rest to fun2.bin. This can be handy if you have a large file that needs to be written across more than one partition. For instance, if you had 3 partitions each 128k. You wouldn’t want to write the same 128k to each partition, you would want to write the first 128k to partition 1, then from 128k-256k of the file to partition 2 and so on.

5. Using dd to convert a file to uppercase:

dd if=erik.txt of=erik_up.txt conv=ucase

Finally, we use conv again to do a conversion. In this case we convert with the specifier of ucase.

What is your favourite use of dd?

Bitwise Operations In C

Bitwise operations in C are used to implement masking of variables, produce logical AND’s, logical OR’s, XOR operations on two variables, the occasional NOT, and even some bit shifting. The operations are called bitwise operations because each operation is done bit by bit for each variable.

Here are the most common and supported bitwise operations that C provides.

Bitwise AND Operation In C

The AND operation, also known as a conjunction is true if and only if both values are true. The operator is a single & ampersand sign in C. So for example:

a & b

To produce a compound assignment where ‘a’ receives the value of “a & b” we can do:

a &= b

An example of how the bitwise operation takes place:

1 2 30101 (5) AND 0011 (3) = 0001 (1)

Working from right to left we produce each compare vertically, as if we were adding two large numbers. So, 1 & 1 = 1, 0 & 1 = 0, 1 & 0 = 0, 0 & 0 = 0. Thus 0001.

Bitwise OR Operation in C

The OR operation, aka the disjunction is true if at least one of its operands is true. In C its symbol is a single bar | or affectionately known as a pipe in Linux/Unix circles.

a | b

To produce a compound assignment where ‘a’ receives the value of “a | b” we can do:

a |= b

An example of how the OR operation takes place:

1 2 30111 (7) OR 0101 (5) = 0111 (7)

Execution occurs in the same manner mentioned under the AND operation.

Bitwise NOT Operation In C

The bitwise not symbol in C is the tilde, ~ symbol.

a = ~b

The not just inverses each bit within your variable:

1 2NOT 0011 (2) = 1100 (12)

So in this case, 2 becomes twelve as we flip the bits.

Bitwise XOR Operation In C

An exclusive disjunction or XOR is true if one but not both of its operands is true. This is sometimes used for basic encryption.

To produce an XOR in C we use the ^ symbol, also known as a carat.

a ^ b

To produce a compound assignment where ‘a’ receives the value of “a ^ b” we can do:

a ^= b

For example:

1 2 30101 (5) XOR 0011 (3) = 0110 (6)

So following the same methodology above, we XOR 5 and 3 and receive 6.

Bit-shifting Operations In C

The bit shifting that occurs in C is known as a logical shift because the bits that are shifted out are lost, rather than circulating to the front of that variable. To execute a shift in C, we use << or >> for left and right shift respectively.

The programmer may specify how many bits to shift by providing a number, for example:

a = b << 2;

This example will shift ‘b’ two bits to left, and assign that value to ‘a’. To shift to the right instead, use the >> symbols.

What will happen is, if our variable contained 2 in it:

1 2 30010 (2) // We execute the shift two places and notice 1 moved left two spots 1000 (8)

Bitwise operations on older systems were faster for addition and subtraction operations and largely faster than multiplication and division, however, on today’s architectures bitwise operations are essentially as efficient, with multiplication and division still requiring more operations.

Creating a Pipe in C

What is a pipe in C?

A pipe in C is a unidirectional data channel that can be used for interprocess communication. One process may write to the pipe, while another process may read from it. This was you can communicate from one process to another through this channel.

How do I use a pipe in C?

Here is a quick example from the pipe linux man page of setting up a pipe in C, with a parent process writing to the pipe and a child process reading from it.

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
int
main(int argc, char *argv[])
{
	int pipefd[2];
	pid_t cpid;
	char buf;
 
	if (argc != 2) {
		fprintf(stderr, "Usage: %s \n", argv[0]);
		exit(EXIT_FAILURE);
	}
 
	if (pipe(pipefd) == -1) {
		perror("pipe");
		exit(EXIT_FAILURE);
	}
 
	cpid = fork();
	if (cpid == -1) {
		perror("fork");
		exit(EXIT_FAILURE);
	}
 
	if (cpid == 0) {    /* Child reads from pipe */
		close(pipefd[1]);          /* Close unused write end */
 
		while (read(pipefd[0], &amp;buf, 1) &gt; 0)
			write(STDOUT_FILENO, &amp;buf, 1);
 
		write(STDOUT_FILENO, "\n", 1);
		close(pipefd[0]);
		_exit(EXIT_SUCCESS);
 
	} else {            /* Parent writes argv[1] to pipe */
		close(pipefd[0]);          /* Close unused read end */
		write(pipefd[1], argv[1], strlen(argv[1]));
		close(pipefd[1]);          /* Reader will see EOF */
		wait(NULL);                /* Wait for child */
		exit(EXIT_SUCCESS);
	}
}

PLC5 – CSPv4 Wireshark Dissector

A long time ago I had been looking at PCAP traces of a PLC5 communicating with RsLinx. Wireshark just saw it as a blob of data on top of the TCP header. Well this just would not do. Wireshark provides a nice interface for using LUA to write your own dissector. This is what I ended up doing for the CSPv4 data (which is actually CSPv4 Header + LSAP + PCCC or PC cubed). An added bonus of writing a dissector for an unknown protocol is that the protocol filter will also register the bytes you define, so you can easily filter a packet stream with your newly defined byte fields.

A big thanks to these two articles from Lynn’s Iatips, specifically:

As well as the Rockwell document that provided valuable PCCC format information (Chapter 6,7):

Wireshark Dissector for PLC5 – CSPv4 + LSAP + PCCC

Without further ado – the LUA code for the Wireshark dissector. Following this code include is a screenshot and instructions of how to include this parser within Wireshark.

The code:

-- CSPv4 Parser --------------------------------
-- 
-- Date: July 25, 2012
-- Author: Erik Schweigert
-- E-mail: erik@linuxtips.ca
--
-- Purpose: To decode the CSPv4 Packet
-- 		CSPv4 + LSAP + PCCC
------------------------------------------------
p_cspv4 = Proto("cspv4","CSPv4")
p_lsap = Proto("lsap","LSAP")
p_pccc = Proto("pccc","PCCC")
 
-- ----------------- CSPv4 Header ------------
local f_mode = ProtoField.uint8("cspv4.mode", "Mode", base.HEX)
local f_submode = ProtoField.uint8("cspv4.submode", "Submode", base.HEX)
local f_data_length = ProtoField.uint16("cspv4.data_length", "Data Length", base.HEX)
local f_conn_id = ProtoField.uint32("cspv4.conn_id", "Connection ID [slave/server]", base.HEX)
local f_status = ProtoField.uint32("cspv4.status", "Status", base.HEX)
local f_context = ProtoField.bytes("cspv4.context", "Context", base.HEX)
-- ---------------- End CSPv4 Header -----------
 
-- ------------------ LSAP ---------------------
-- Local 
local f_dest = ProtoField.uint8("cspv4.dst", "Destination Byte", base.HEX)
local f_res5 = ProtoField.uint8("cspv4.res5", "Control Byte", base.HEX)
local f_src = ProtoField.uint8("cspv4.src", "Source Byte [Master Address]", base.HEX)
local f_lsap = ProtoField.uint8("cspv4.lsap", "LSAP", base.HEX)
 
-- Remote
local f_resX = ProtoField.uint8("cspv4.resX", "Mystery Byte", base.HEX)
local f_dst_link = ProtoField.uint16("cspv4.dst_link","Destination Link Address", base.HEX)
local f_dst_station = ProtoField.uint16("cspv4.dst_station", "Destination Station Address", base.HEX)
local f_resY = ProtoField.uint8("cspv4.resY", "Mystery Byte 2", base.HEX)
local f_src_link = ProtoField.uint16("cspv4.src_link", "Source Link Address", base.HEX)
local f_src_station = ProtoField.uint16("cspv4.src_station", "Source Station Address", base.HEX)
local f_resZ = ProtoField.uint8("cspv4.resZ", "Mystery Byte 3", base.HEX)
-- ------------------ End LSAP ------------------
 
-- ------------------ PCCC ----------------------
local f_pccc_command = ProtoField.uint8("cspv4.pccc_command", "Command Code", base.HEX)
local f_pccc_sts = ProtoField.uint8("cspv4.pccc_sts", "Status Code", base.HEX)
local f_pccc_tns = ProtoField.uint16("cspv4.pccc_tns", "Transaction Number", base.HEX)
local f_pccc_fnc = ProtoField.uint8("cspv4.pccc_fnc", "Function Code", base.HEX)
local f_pccc_addr = ProtoField.uint16("cspv4.pccc_addr", "Address of Memory Location", base.HEX)
local f_pccc_size = ProtoField.uint8("cspv4.pccc_size", "Size", base.HEX)
local f_pccc_data = ProtoField.bytes("cspv4.pccc_data", "Data", base.HEX)
-- ------------------ End PCCC -------------------
 
-- CSPv4 Fields
p_cspv4.fields = {f_mode}
p_cspv4.fields = {f_submode}
p_cspv4.fields = {f_data_length}
p_cspv4.fields = {f_conn_id}
p_cspv4.fields = {f_status}
p_cspv4.fields = {f_context}
p_cspv4.fields = {f_dest}
p_cspv4.fields = {f_res5}
p_cspv4.fields = {f_src}
p_cspv4.fields = {f_lsap}
 
-- Remote LSAP Fields
p_cspv4.fields = {f_resX}
p_cspv4.fields = {f_dst_link}
p_cspv4.fields = {f_dst_station} 
p_cspv4.fields = {f_resY}
p_cspv4.fields = {f_src_link}
p_cspv4.fields = {f_src_station}
p_cspv4.fields = {f_resZ}
 
-- PCCC Fields
p_cspv4.fields = {f_pccc_command}
p_cspv4.fields = {f_pccc_sts}
p_cspv4.fields = {f_pccc_tns}
p_cspv4.fields = {f_pccc_fnc}
p_cspv4.fields = {f_pccc_addr}
p_cspv4.fields = {f_pccc_size} 
p_cspv4.fields = {f_pccc_data}
 
function build_cspv4_header(buf)
	build_request(buf)
	build_submode(buf)
 
	subtree:add(f_data_length, buf(2,2))
	subtree:add(f_conn_id, buf(4,4))
	subtree:add(f_status, buf(8,4))
	subtree:add(f_context, buf(12,16))
end
 
function build_request(buf)
	if buf(0,1):uint() == 1 then
		subtree:add(f_mode, buf(0,1)):append_text(" (Request)")
	elseif buf(0,1):uint() == 2 then
		subtree:add(f_mode, buf(0,1)):append_text(" (Response)")
	else
		subtree:add(f_mode, buf(0,1))
	end
end
 
function build_submode(buf)
	if buf(1,1):uint() == 1 then 
		subtree:add(f_submode, buf(1,1)):append_text(" (Connection)")
	elseif buf(1,1):uint() == 7 then
		subtree:add(f_submode, buf(1,1)):append_text(" (PCCC)")
	else
		subtree:add(f_submode, buf(1,1))
	end  
end
 
function build_lsap(buf, root)
 
	lsap_tree = root:add(p_lsap, buf(28))
 
	lsap_tree:add(f_dest, buf(28,1))
	lsap_tree:add(f_res5, buf(29,1))
	lsap_tree:add(f_src, buf(30,1))
 
	if buf(31,1):uint() == 0 then
		lsap_tree:add(f_lsap, buf(31,1)):append_text(" (Local Form)")
	elseif buf(31,1):uint() == 1 then
		lsap_tree:add(f_lsap, buf(31,1)):append_text(" (Remote Form)")
		build_lsap_remote(buf, lsap_tree)
	else
		lsap_tree:add(f_lsap, buf(31,1))
	end
end
 
function build_lsap_remote(buf, lsap_tree)	
	lsap_tree:add(f_resX, buf(32,1))
	lsap_tree:add(f_dst_link, buf(33,2))
	lsap_tree:add(f_dst_station, buf(35,2))
	lsap_tree:add(f_resY, buf(37,1))
	lsap_tree:add(f_src_link, buf(38,2))
	lsap_tree:add(f_src_station, buf(40,2))
	lsap_tree:add(f_resZ, buf(42,1))
end
 
function build_pccc(buf, root)
 
	pccc_tree = root:add(p_pccc, buf(32))
 
	-- Ensure its PCCCC
	if buf(1,1):uint() ~= 7 then end
 
	if buf(31,1):uint() == 1 then
		offset = 11
	else
		offset = 0
	end	
 
	pccc_tree:add(f_pccc_command, buf(32 + offset, 1))
	pccc_tree:add(f_pccc_sts, buf(33 + offset, 1))
	pccc_tree:add(f_pccc_tns, buf(34 + offset, 2))
	pccc_tree:add(f_pccc_fnc, buf(36 + offset, 1))
	pccc_tree:add(f_pccc_addr, buf(37 + offset, 2))
	pccc_tree:add(f_pccc_size, buf(39 + offset, 1))	
	pccc_tree:add(f_pccc_data, buf(40 + offset, buf:len() - (40 + offset)))
end
 
-- cspv4 dissector function
function p_cspv4.dissector (buf, pkt, root)
	-- validate packet length is adequate, otherwise quit
	if buf:len() == 0 then return end
 
	pkt.cols.protocol = p_cspv4.name
 
	-- create subtree for cspv4
	subtree = root:add(p_cspv4, buf(0))
	-- add protocol fields to subtree
 
	build_cspv4_header(buf)
	build_lsap(buf, root)
	build_pccc(buf, root)
 
	-- description of payload
	subtree:set_text("CSPv4, CSPv4 Header Information")  
 
	-- add debug info if debug field is not nil
	if f_debug then
		-- write debug values
		subtree:add(f_debug, buf:len())
	end
end
 
-- Initialization routine
function p_cspv4.init()
end
 
-- register a chained dissector for port 2222
local tcp_dissector_table = DissectorTable.get("tcp.port")
dissector = tcp_dissector_table:get_dissector(2222)
  -- you can call dissector from function p_cspv4.dissector above
  -- so that the previous dissector gets called
tcp_dissector_table:add(2222, p_cspv4)

As you can see there is nothing ground breaking in this parser, and the code itself is quite rudimentary. A great enhancement would be to add the textual value of what the PCCC command vs function code actually equates to (read bit, write bit, etc).

Installing LUA Dissector to Wireshark

Now you have enhanced Wireshark to properly dissect your PLC5 packets – at least if they are CSPv4 with PCCC.

  • Save the lua script above to any folder and call the file cspv4.lua
  • Open init.lua in the Wireshark installation directory for editing. In Linux it can be found in /etc/wireshark/init.lua.  You will need Admin privileges on Windows Vista and 7.
  • Comment out the following line in init.lua (single line comments begin with --):
disable_lua = true; do return end;
  • Add the following lines to init.lua (at the very end):
dofile("/path/to/the/file/cspv4.lua")
  • Run Wireshark
  • Load a capture file that has the packets of your custom protocol or start a live capture.

Now you have enhanced Wireshark to properly dissect your PLC5 packets – at least if they are CSPv4 with PCCC.

Checking Endianness of your Operating System

Sometimes when trying to debug oddities in your C programs, especially when doing cross-compilation to other architectures it is good to know when going from an x86 (Little Endian) to say an ARM processor running in Big Endian.  This is doubly useful when dealing with encryption keys or network packets.  If you have an encryption key on a desktop machine running in Little Endian, and the decryption key on a Big Endian system, you have to take into account the differing byte order.  There is a great Wikipedia article about Endianness if you would like to learn more. There is also a nifty C program you can compile to check the Endianess of your OS.

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int x = 1;
 
    if (*(char *)&x == 1)
        fprintf(stderr, "Little Endian\n");
    else
        fprintf(stderr, "Big Endian\n");
 
    return 0;
}

To compile:

gcc -o endian endian.c

Then run:

erik@debian:~$ ./endian
Little Endian

Voila, now you know the Endianness of your Operating System.

Change File Owner and Group With chown

Every file on a Linux system has an owner and is associated with a group. Yesterday I talked about setting file permissions on your Linux system, I will now discuss the basics of owner and group permissions. These modifications can be done using the chown command while running as the root user.

To view a file’s current owner and group you can use the ls -l command.

$ ls -l taps.sh 
-rwxr-xr-x 1 erik users 1014 2010-10-28 13:30 taps.sh

In the above example, ‘erik’ is the owner and ‘users’ is the group that the file is currently within. This means that any other user that is part of the group ‘users’ will also have r-x access.

What Does chown Do?

Excerpt from the chown manual page:
chown changes the user and/or group ownership of each given file. If only an owner (a user name or numeric user ID) is given, that user is made the owner of each given file, and the files’ group is not changed. If the owner is followed by a colon and a group name (or numeric group ID), with no spaces between them, the group owner‐ship of the files is changed as well. If a colon but no group name follows the user name, that user is made the owner of the files and the group of the files is changed to that user’s login group. If the colon and group are given, but the owner is omitted, only the group of the files is changed; in this case, chown performs the same function as chgrp. If only a colon is given, or if the entire operand is empty, neither the owner nor the group is changed.

To better understand that here are a few examples:

# chown erik:cdrom taps.sh 
# ls -l taps.sh 
-rwxr-xr-x 1 erik cdrom 1014 2010-10-28 13:30 taps.sh
# chown erik:erik taps.sh 
# ls -l taps.sh 
-rwxr-xr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh

As you can see I changed the group to ‘cdrom’ then changed it back to ‘erik’. In doing so, none of my file permissions changed, but I did become part of the cdrom group, which means I may have gained access to other files that were also part of the ‘cdrom’ group.

If I only specify the colon after the user setting, then the file will automatically take use the users login group as the default. For example:

# ls -l taps.sh 
-rwxr-xr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh
# chown erik:cdrom taps.sh 
# ls -l taps.sh 
-rwxr-xr-x 1 erik cdrom 1014 2010-10-28 13:30 taps.sh
# chown erik: taps.sh 
# ls -l taps.sh 
-rwxr-xr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh

You can also set the file to a group that does not even necessarily exist.

# chown erik:1234 taps.sh 
# ls -l taps.sh 
-rwxr-xr-x 1 erik 1234 1014 2010-10-28 13:30 taps.sh
# chown erik: taps.sh 
# ls -l taps.sh 
-rwxr-xr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh

As you can see the 1234 in yellow identifying my group.

Recursive Group Change

To recursively change all files within a directory you may use the -R before setting the user:group option.

# chown -R erik:1234 /home/erik/

This will change all files within my home directory to now become part of the 1234 group. Yay.

The purpose for group association permits certain users the ability to use devices or see certain files on a computer. For instance, I am currently associated with a set of groups:

$ groups
erik adm dialout cdrom plugdev lpadmin admin sambashare

Thus, I have access to certain files or devices because I am now part of that group.

How To: Modify Bash Environment in C

This article will outline how to modify environment variables from a running C process. It should be noted that setting variables from your C process into the environment will only persist during process lifetime. Environment variables are propagated forward to a child program, but not backward to the parent.

Earlier I wrote about creating, removing, changing environment variables directing in Bash, now I will show you how to do these modifications using C.

Creating An Environment Variable In C process

Creating the environment variable can easily be achieved using the C function int setenv(const char *name, const char *value, int overwrite);. It takes two parameters, the first parameter being the name of the environment variable, and the second being a flag outlining whether to overwrite a pre-existing variable of the same name.

#include<stdio.h>
#include <stdlib.h>
 
int main()
{
	char *name = "DATA";
	char *value = "erik";
 
	if(setenv(name, value, 1) < 0){
		fprintf(stderr, "Could not create environment variable.\n");
		return -1;
	}
 
	fprintf(stdout, "-%s-\n", getenv("DATA"));
	return 0;
}

The variable will persist for any children you may fork, or if you execute a system call to grab the variables.

Removing An Environment Variable Using unsetenv

To remove an environment variable using your C process, you may call int unsetenv(const char *name);. Where char *name is the name from the environment. If name does not exist in the environment, then the function succeeds, and the environment is unchanged.

unsetenv(name);

Clear The Environment Using clearenv

To clear the entire environment you can:

clearenv();

Or

environ = NULL;

Reading An Environment Variable Using getenv

To just read an environment variable there is the C call, char *getenv(const char *name);. This will return the value of the environment variable.

getenv(“data”);

Those are the basic functions you can execute from your C process to set, remove, create those environment variables.

C getchar() Usage and Examples

The getchar() function is used to grab character by character inputs from the standard input stream. getchar() is special in that it takes a void as its argument, i.e. nothing and it returns the next character from standard input. This can be used for basic input into any c program.

I will outline a basic example here.

How To Use getchar()

The getchar() function, as I mentioned above is quite basic, I will show you how to read input from standard in and print it back out to the terminal as standard out.

#include <stdio.h>
 
int main()
{
        char c;
        for(;;){
                c=getchar();
                if(c == 'q') // Compare input to 'q' character
                        break;
                fprintf(stdout, "%c\n", c);
        }
        return 0;
}

To compile this program:

erik@debian:~/getchar_ex$ gcc -o getchar_test getchar_test.c

Using this function:

erik@debian:~/getchar_ex$ ./getchar_test
a
a
 
 
b
b
 
 
c
c
 
 
q
q
erik@debian:~/getchar_ex$

As you can see, we read in each character then the process prints it back out. Nothing to it really.

getchar() Return Values

getchar() will return an unsigned char that is internally cast to an int. If there is an error or end of line the function will return an EOF (end of file).

C Debugging Macros

Any experienced programmer can relate to sprinkling their code with printf statements to try and figure out a NULL pointer, or perhaps whether a function has been reached or not. This is the first step towards debugging code.   Further down the line can include using GDB or Valgrind to check code entry paths, modify variables during runtime or check memory usage or look for memory leaks from non-free malloc’d data upon program exit.  These tools are usually pulled out when the going gets tough!  As an initial pass during basic unit testing debug information is extremely handy.  What I usually attempt to do is create a header file (*.h) that can be included in any (*.c) files I happen to be working on.  This way, if I modify my macro it only requires a change in one place to complete.  For example:

#include <stdio.h>
#include <stdlib.h>
 
#ifdef DEBUG
#define DEBUGP(x, args ...) fprintf(stderr, " [%s(), %s:%u]\n" \
x, __FUNCTION__, __FILE__,__LINE__, ## args)
#else
#define DEBUGP(x, args ...)
#endif
 
void calculation() {
	DEBUGP("data: %u\n",5 * 5);
}
 
int main()
{
	DEBUGP("Program started.\n");
	calculation();
	return 0;
}

Now every time we want to output any debug information we can call DEBUGP(..data..);.  In doing so, we also get the file name, the function it was called in, and what line it occurred at.  This is extremely helpful for debugging purposes.  This macro utilizes the built-in C macros of __LINE____FUNCTION__, and __FILE__.   Another piece to note is, I have #ifdef DEBUG, this means that if this variable is defined then we can reference DEBUGP, if the DEBUG variable is not defined then we call our “#define DEBUGP(x, args …)“, which does not print out any debug data.  This means during compilation we can pass the -DDEBUG flag to the compiler, meaning we can turn on/off our debugging flag effortlessly.

So putting this all together, lets compile the program with the flag enabled.

erik@debian:/debug$ gcc -DDEBUG -o debug debug.c

Now that it is compiled lets run it.

erik@debian:/debug$./debug
[main(), debug.c:18] Program started.
[calculation(), debug.c:12] data: 25

So as we can see, with a little bit of organization and taking advantage of C macros we can make debugging a lot easier for ourselves!

Utilize Bash Aliases for Quicker SSH Connects

If you find yourself constantly connecting and disconnecting to different computers using SSH it is useful to setup aliases to speed up this process. There are a few ways of doing this, including creating a config file within your .ssh directory and using the .bashrc file to create aliases on boot-up. Okay, lets get down to the nitty – gritty.

1. Setup A ‘config’ File In Your .ssh Directory

For those computers that you plan to connect to quite often, create a config file in ~/.ssh. Otherwise known as your home directories .ssh folder.

:~/.ssh$ cat config 
Host data
    User erik
    HostName 192.168.12.45
    Port 22
    ForwardX11 yes
 
Host htpc
    User erik
    HostName 123.17.51.92
    Port 22
    ForwardX11 yes
 
Host orion
   User gregor
   HostName 192.16.22.134
   Port 22
   ForwardX11 yes

Take note of the Host name. These will be used as aliases to the HostName IP address, so rather than typing the IP address you can simply type the name.

2. Add Bash Code To Create Aliases on Boot

Now lets head over to your .bashrc file and add a simple line which will use the config file above to create aliases for quick ssh usage. At the end of your .bashrc file add this line:

for name in `sed -n "/^Host/s/^Host //p" ~/.ssh/config`; do alias $name="ssh $name"; done

Breaking this down, we cycle through the config file from the .ssh directory and create an alias for each pairing. So each alias will be orion=ssh orion. Lets check the aliases:

alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias data='ssh data'
alias grep='grep --color=auto'
alias htpc='ssh htpc'
alias ls='ls --color=auto'
alias orion='ssh orion

This simple substitution will replace orion with the ssh orion. In doing so, SSH will check the config file within the .ssh directory and substitute the HostName with the actual IP address, and voila.

3. Execute SSH

Now executing the command:

$ htpc erik@123.17.51.92’s password:

If I had already copied my authorized key to the server, then I would not even be prompted for the password. This simple setup saves a lot of time, especially if you find yourself constantly connecting and disconnecting from remote servers, or have to copy files back and forth a lot. You can simple run commands like:

$ scp data.txt htpc:/tmp/

This is a lot nicer than having to remember the IP address and which username is for that server.