While interviewing for Canonical, I got to spend a bit of time with a very talented programmer name Seth Arnold. As with (seemingly) everyone on the Canonical team, he was next-level-talented. When I got the chance to ask questions, I asked him if he could give me a starting point to get from where I am to where he is. His suggestion was
Advanced Programming in the Unix Environment.
So, I bought the book and found a free course hosted by Stevens Institute of Technology. I will slowly work through the course and keep notes here. For anyone who wants to follow along, Let’s get started!
Once you get yourself setup with NetBSD as instructed on the course page, you can attempt the first exercise.
#include <stdio.h>
int
main(int argc, char **argv) {
printf("Welcome to CS631 Advanced Programming in the UNIX Environment, %s!\n", getlogin())
}
Our task is to find and debug the errors.
So, let’s compile our code
$ cc welcome.c -o welcome
Doing so will induce an error in our terminal stating:
welcome1.c:22:1: error: expected ';' before '}' token } ^
Oh No! What happened?
The code didn’t compile because there was a syntax error. If we open the file to check it out
$ vim welcome.c
We can see that there is no statement terminator (semicolon) for our statement. Let’s add that to the end of our line (shift+A for appending data to the end of a line). Exit input mode (esc) and save the file (:wq).
Now we can try compiling again…
$ cc welcome.c -o welcome
Now there are more errors!
welcome_start.c:20:81: error: implicit declaration of function 'getlogin'; did you mean 'getline'? [-Werror=implicit-function-declaration]
printf("Welcome to CS631 Advanced Programming in the UNIX Environment, %s!\n", getlogin());
^~~~~~~~
getline
welcome_start.c:20:74: error: format '%s' expects argument of type 'char *', but argument 2 has type 'int' [-Werror=format=]
printf("Welcome to CS631 Advanced Programming in the UNIX Environment, %s!\n", getlogin());
~^ ~~~~~~~~~~
%d
Well, as we can see there are two errors. The first is a reference to an unknown getlogin() function. And the second is a string format error whereby we used a format code representing a string (a pointer to a null terminated character array) when the compiler thinks we should have used a format code representing an integer.
Why does the compiler think we should have use an int format code?
According to the output error, it believes that the function getlogin() returns an integer.
Well that’s strange… Because directly above that, it just said that it has no idea what getlogin() is.
My best guess is that, since it thinks that we meant to use the function getline() instead of getlogin(), it checked the return type for getline() and used that as the function’s expected return value.
This brings up a very valuable point when debugging a stack trace, you should always address the top most error first. I think that is worth repeating.
When debugging a stack trace, you should always address the top most error first.
Why? Because you are likely to find that subsequent errors aren’t actually errors at all should the top most error have been properly resolved. Otherwise, you can spend wasted effort chasing down “issues” that don’t actually exist.
So, how do we address the fact that our compiler doesn’t know what getlogin() is?
Let’s check and see if getlogin() is a system call or C library function. Easiest way to do that is to see if it has a man page.
$ man getlogin
Low and behold.. we have a man page. (note: if you followed everything on the course page to setup this environment, you could have moved your cursor to the function name and hit shift+k to view the man page from within the vim coding environment)
GETLOGIN(2) System Calls Manual GETLOGIN(2) netbsd-unix$
NAME
getlogin, getlogin_r, setlogin - get/set login name
LIBRARY
Standard C Library (libc, -lc)
SYNOPSIS
#include <unistd.h>
char *
getlogin(void);
int
getlogin_r(char *name, size_t len);
int
setlogin(const char *name);
DESCRIPTION
The getlogin() routine returns the login name of the user associated with
the current session, as previously set by setlogin(). The name is
normally associated with a login shell at the time a session is created,
and is inherited by all processes descended from the login shell. (This
is true even if some of those pr
Looking at the man page, we can see that the expected output from the getlogin() function is indeed a char pointer. So error #2 is bogus. All we need to do is resolve error #1.
Fortunately, the man page also tells us how to resolve that issue. It says we just need to include the unistd.h header into our code.
So, let’s do it.
#include <stdio.h>
#include <unistd.h>
int
main() {
printf("Welcome to CS631 Advanced Programming in the UNIX Environment, %s!\n", getlogin());
return 0;
}
I made two changes to the code. I included the required header on line two, and I added a return value to match the return type declared for the main() function on line seven. The return value isn’t strictly necessary and the program would work fine without it, but I like to be thorough and I value code consistency so I threw it in.
Now, let’s try compiling again.
$ cc welcome.c -o welcome
No errors. That’s a good sign. But will it run?
$ ./welcome
It does indeed.
Welcome to CS631 Advanced Programming in the UNIX Environment, apue!
Congratulations! You just debugged a faulty C program. Maybe it was even your very first… Good for you!
The sky’s the limit from here. Hope to see you around as I continue to make my way through the course.
And Seth… if it’s really you, you are awesome man. Thanks so much for the time you spent with me in our interview. Having had the chance to speak with you made the gauntlet of the canonical application process worth every minute of my effort, even if I don’t end up getting the job.