Monday, February 9, 2009

OpenSolaris 2008.11 - What is right, and what needs work

A friend of mine was trying to compile a simple C program on his university shell account. It turns out that the server hosting his shell account runs some version of Solaris. Not being experienced with anything Sun, I was not as helpful as I would like to have been with his questions about invoking the compiler. Since I had often thought that I would like to give OpenSolaris a whirl, now seemed like a good time to try. This is a short account of my experience doing just that, triple booting Linux, FreeBSD, and OpenSolaris.

First of all, to boot OpenSolaris after installation, it is not just a matter of editing your grub.conf with an entry for OpenSolaris. The version of grub supplied with OpenSolaris is special, and needs to be installed in order to boot into that OS. This works ok, and you can either add your other entries to it, or you can do like I did and leave the original grub on /dev/hda, and then use an entry like this to get to either the FreeBSD bootloader or the OpenSolaris grub on /dev/hdb:


# For booting GNU/Linux
title GNU/Linux 2.6.27-gentoo-r8
root (hd0,0)
kernel /boot/kernel-2.6.27-gentoo-r8 root=/dev/hda3 \
video=uvesafb:1280x1024-24,mtrr:3,ywrap \

#For booting FreeBSD
title FreeBSD
root (hd1,0)
savedefault
makeactive
chainloader +1

#For booting OpenSolaris
title OpenSolaris
root (hd1,1)
savedefault
makeactive
chainloader +1

The installation went very well. I popped in the livecd, and once the desktop came up, clicked on the icon there to install the OS. It made me fill in some info, like username/password stuff, as well as timezone etc., and partitions. Then it did the install. After that, all I had to do was reboot.

The boot into the full install was impressive. The screen resolution just worked, although nvidia twinview was not working immediately. All that was needed for that was to open a shell and enter 'pfexec nvidia-config' and then use the sweet utility to change the settings. You can read more about that here.

There were a few issues with drivers. I run an old Asus A8V-Deluxe motherboard, which is well supported under Linux and FreeBSD, but apparently not as well with OpenSolaris.
  • First off, it did not automatically do something intelligent with the Marvell 88E8001 Ethernet Controller. That was not too much trouble though, as I was able to boot back into Linux, download a driver, and then boot back into Solaris and install it. It worked well then, and the network automagically worked too.
  • The second problem was with the Sata Raid controller - no driver for that either. Not a problem for me, as I still am running a pair of ATA disks; for those of you with Sata, this could be trouble though.
  • The final problems were sound-card related. The Creative Sound Blaster X-Fi Extreme did not surprise me - it is hardly supported under Linux. But my old C-Media card was not picked up either. Oh well - Windows 7 had some trouble with that one too.

Over all, my impression of the install was favourable. I think that a lot of work has gone into the installer and sysconfig utilities, to make the experience a pleasant one, and it shows. Kudos to the people who are working on that end of OpenSolaris. I am also fairly impressed with the package management system which has a nice graphical user interface. Nice and easy to use, and from my experience, takes care of all the dependencies. It is on par with modern Linux distributions, if nothing else.

Now for the parts that I didn't like so much..

The first point is a minor one; though I was impressed with the initial boot, this had to do more with the fact that things worked, and not so much to do with the presentation. What I mean is that upon booting to the grub menu, the user is given two choices, either a regular boot (which happens to be a graphical frame-buffer deal with a 'sort of' progress bar. Basically it just keeps moving so you get the idea that something is going on, I guess.) or a text boot from the console. I selected this (text-boot) once, just to see how it looked and, as a Gentoo user, I can say that I was totally disappointed. There was hardly any information there at all. So if you are into a descriptive (and possibly interactive) boot process, forget about it with OpenSolaris.

The other thing I did not like was the fact that the default desktop is Gnome. Linus doesn't like Gnome, [1] and neither do I. And here is the kicker, KDE is not really offered. Sure, you can get an old version (i.e., 3.4x), or you can go for the super new 4.2x stuff, but no (easy) option for getting 3.5.9 on there. In my mind, this is a huge mistake. I have never really been able to get into Gnome, because it seems so hard to customise. All this said, I gather that it is not easy to get the KDE 3.x stuff running on OpenSolaris, and that the 4.x series promises to be good when it is ready. Until then, I guess I will stick with my Linux, FreeBSD and KDE.

[1]. Although he has been using it lately, and even developed a patch for it at some point, which the developers rejected.
Yes, you read that right; they rejected it. !!?? Hey guys,
Linus sent you a patch! Put it in!

Sunday, February 8, 2009

Building C and C++ Programs

For a beginning programmer, sometimes the biggest problem is just getting a program to compile, as opposed to actually writing the program itself. That is why it is often helpful to do a little 'Hello, world!' program, just to get a feel for the system, how the compiler is invoked, and so on. So in this post, the reader will learn how to compile a 'Hello, world!' program for three different systems, in two different languages. The systems will be Linux, FreeBSD, and OpenSolaris. The first two will be similar in that they both (normally) employ gcc (GNU Compiler Collection, but formerly GNU C Compiler) as the standard system compiler. OpenSolaris is more interesting, as it makes the Sun C and C++ compilers available, as well as gcc. As for the languages, if you haven't already guessed, they will be C and C++. So let's begin with a C program, hello.c:

#include <stdio.h>

int main(void)
{
printf("Hello, world!\n");
return 0;
}



A simple program, one that lets the programmer concentrate on getting the compiler working, without having to worry about fixing source errors. Now then, the first thing to know is that on many (if not all) Unix like systems, the C compiler can generally be invoked with the command 'cc'. This may be the actual C compiler, named 'cc,' or the command may be a symlink to another compiler, such as gcc. In the latter case, the symlink is added for the sake of not breaking things. cc was the standard command for invoking the C compiler, and to take it away would, if nothing else, break tradition, not to mention some Makefiles as well. You can determine some basic information about where your C compiler resides (and if it is even installed!) by issuing the following command from the shell:

which cc


In the case of my OpenSolaris machine, this produces the following:

/usr/bin/cc


If I do an ls -l on /usr/bin/cc, I see that it is really just a symlink to another location on disk:

ls -l /usr/bin/cc
lrwxrwxrwx 1 root root 33 2009-02-08 22:52 /usr/bin/cc -> ../../opt/SunStudioExpress/bin/cc


All very interesting stuff. You could also do something like this:

cc -V


Which produces:

cc: Sun Ceres C 5.10 SunOS_i386 2008/10/22
usage: cc [ options] files.  Use 'cc -flags' for details


So in this case, at least, we know that when we use the command 'cc', we are invoking the Sun C compiler. So why don't we do that then - copy the program hello.c (above) into your favourite text editor, and save it as hello.c -- then, from the directory that you save the hello.c file, invoke the C compiler with the following command:

cc -o hello hello.c


and hit Enter. You should get your shell prompt back almost immediately. What you have done with the above command is to run the C compiler (cc), with the compiled output being called 'hello' (-o hello -- -o stands for output name. If you don't do the -o option, the program will still compile, with the executable being named a.out -- this is also a tradition in Unix like systems. If you run a.out, it will print 'Hello, world!' to the standard output. a.out stands for 'assembler output,' which is a fallback to older systems and procedures.) and the input source file to the compiler being hello.c

As you can see, there is not much to it. If you do an ls -l in the directory where you ran the compiler from, you should see the executable file, 'hello' You can run it by typing in:

./hello


The ./ part of ./hello means 'Run the program 'hello' which is found in this directory.
With OpenSolaris, you have the option to install another compiler as well, namely, gcc. You can issue the same commands to find out where gcc exists and which version you are using:
which gcc
/usr/bin/gcc

ls -l /usr/bin/gcc
lrwxrwxrwx 1 root root 14 2009-02-08 23:20 /usr/bin/gcc -> ../sfw/bin/gcc


Keep in mind that determining the version of gcc is slightly different than the way you do it for cc:
gcc -v


Note that it is a small v as opposed to a large V for cc. You can read more about the compiler command switches by doing man cc or man gcc to access the manual page for each compiler.

If you decide to use gcc, you ought to use two flags every time you compile. They are the -Wall (Warnings All) and the -Wextra (Warnings extra) flags. By using them, the compiler will give you lots of extra info with any problems your source code might have. Their use is indispensable, in my opinion. Use them like this:

gcc -Wall -Wextra -o hello hello.c


And here is the added bonus - if you can compile like this on OpenSolaris (Sun), then you can do it under Linux or FreeBSD, which come with gcc as the standard compiler.

Now then, on to compiling C++ programs. First, we need a suitable 'Hello, world!' program:

#include <iostream>

using namespace std;

int main()
{
cout << "Hello, world!" << endl;
return 0;
}
Now the interesting thing about C++ programs is that there are several standard ways of naming with a suffix. With C, a file is foo.c, such as hello.c -- but with C++ it might be hello.C (note the capital C), or hello.cc, or hello.cpp, or hello.cxx For our purposes, we will stick to hello.cc So how do we compile it? Well, if you want to go the traditional route, you would invoke the C++ compiler like this:
CC -o hello hello.cc
Note the capital CC; pretty imaginative, huh? That is simple, and easy to remember though. If you want to use the GNU C++ compiler, you would do:
g++ -o hello hello.cc
Both of these methods would produce an executable named 'hello,' which could then be invoked as with './hello' at the command prompt. If you use g++, once again, you can (and should) use the -Wall -Wextra flags. Also, you can use g++ in the same fashion on any other system which has the GNU Compiler Collection available, such as Linux, or FreeBSD.

Sunday, February 1, 2009

Getting a simple directory listing in Linux

There are often many ways to do the same things under Linux; getting a listing of a given directory falls under that category. The easiest way is to issue the command, 'ls -l', in the shell. Alternatively, if you don't mind a bit of work, you could write your own program.

Lately, I have been reading Steve D. Pate's UNIX Filesystems -- Evolution, Design, and Implementation. I am at the section in the book (pp.21-22) where the author implements a simple version of the Unix style ls command. The book itself was published in 2003 and, as things stand in 2009, the code example he lists will not even compile. Here is the code:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dirent.h>
#include <sys/unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

#define BUFSZ 1024

main()
{
    struct dirent *dir;
    struct stat st;
    struct passwd *pw;
    struct group *grp;
    char buf[BUFSZ], *bp, *ftime;
    int dfd, fd, nread;

    dfd = open(".", O_RDONLY);
    bzero(buf, BUFSZ);
    while (nread = getdents(dfd, (struct dirent *) &buf, BUFSZ) != 0) {
        bp = buf;
        dir = (struct dirent *) buf;
        do {
            if (dir->d_reclen != 0) {
                stat(dir->d_name, &st);
                ftime = ctime(&st.st_mtime);
                ftime[16] = '\0';
                ftime += 4;
                pw = getpwuid(st.st_uid);
                grp = getgrgid(st.st_gid);
                perms(st.st_mode);
                printf("%3d %-8s %-7s %9d %s %s\n",
                       st.st_nlink, pw->pw_name, grp->gr_name,
                       st.st_size, ftime, dir->d_name);
            }
            bp = bp + dir->d_reclen;
            dir = (struct dirent *) (bp);
        } while (dir->d_ino != 0);
        bzero(buf, BUFSZ);
    }
}
Here is what I get when I try to compile it on my machine:

kermit@fastbox ~/cprogs/fs $ gcc -Wall -o pate_ls pate_ls.c
pate_ls.c:3:24: error: sys/dirent.h: No such file or directory
pate_ls.c:14: warning: return type defaults to ‘int’
pate_ls.c: In function ‘main’:
pate_ls.c:23: warning: implicit declaration of function ‘bzero’
pate_ls.c:23: warning: incompatible implicit declaration of built-in function ‘bzero’
pate_ls.c:24: warning: implicit declaration of function ‘getdents’
pate_ls.c:24: warning: suggest parentheses around assignment used as truth value
pate_ls.c:28: error: dereferencing pointer to incomplete type
pate_ls.c:29: error: dereferencing pointer to incomplete type
pate_ls.c:30: warning: implicit declaration of function ‘ctime’
pate_ls.c:30: warning: assignment makes pointer from integer without a cast
pate_ls.c:35: warning: implicit declaration of function ‘perms’
pate_ls.c:36: warning: implicit declaration of function ‘printf’
pate_ls.c:36: warning: incompatible implicit declaration of built-in function ‘printf’
pate_ls.c:38: error: dereferencing pointer to incomplete type
pate_ls.c:38: warning: format ‘%3d’ expects type ‘int’, but argument 2 has type ‘__nlink_t’
pate_ls.c:38: warning: format ‘%9d’ expects type ‘int’, but argument 5 has type ‘__off_t’
pate_ls.c:40: error: dereferencing pointer to incomplete type
pate_ls.c:42: error: dereferencing pointer to incomplete type
pate_ls.c:20: warning: unused variable ‘fd’
pate_ls.c:45: warning: control reaches end of non-void function
kermit@fastbox ~/cprogs/fs/tmp $


That's a lot to look at! What we need to do is make some changes, so that the code will compile cleanly. Before dealing with the errors and warnings though, we will first look at the only 'major' change to the code, which will have a bonus side effect of eliminating some of the other problems. What we are going to do, is get rid of the call to getdents. If you look at your man page for getdents, you will notice the following:

"This is not the function you are interested in. Look at readdir(3) for the POSIX conforming C library interface. This page documents the bare kernel system call interface."


I am not sure if getdents was ever able to be used under Linux, such as is demonstrated above, but it certainly is not now. Instead, if we want to read a directory, we can use the readdir system call. Furthermore, instead of using the open system call on a directory, we can use the opendir system call that is provided under Linux. By using readdir, we can get rid of a few variables, notably the buf array, which also eliminates the need for the call to bzero. (Incidentally, if you read the man page for bzero, you will notice that it is being deprecated in favour of the memset function). Let's have a look at the original code again, with some comments annotating some of the problems with regards to building it under Linux:
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/dirent.h>         /* Does not exist under some   
                                 * (all?) Linux distributions 
                                 */ 
#include <sys/unistd.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <errno.h> 
#include <pwd.h> 
#include <grp.h> 
 
#define BUFSZ 1024 
 
main() 
{                               /* Main returns an int */ 
    struct dirent *dir; 
    struct stat st; 
    struct passwd *pw; 
    struct group *grp; 
    char buf[BUFSZ], *bp, *ftime; 
    int dfd, fd, nread; 
 
    dfd = open(".", O_RDONLY); 
    bzero(buf, BUFSZ);          /* #include <strings.h> for bzero, 
                                 * but we don't need to use 
                                 * bzero (or memset) if we use 
                                 * readdir instead of getdents 
                                 */ 
    while (nread = getdents(dfd, (struct dirent *) &buf, BUFSZ) != 0) { 
        /* We can use readdir instead 
         * of getdents.  Example will 
         * follow later. 
         */ 
        bp = buf; 
        dir = (struct dirent *) buf; 
        do { 
            if (dir->d_reclen != 0) { 
                stat(dir->d_name, &st); 
                ftime = ctime(&st.st_mtime);    /* #include <time.h> 
                                                 * for ctime()  
                                                 */ 
                ftime[16] = '\0'; 
                ftime += 4; 
                pw = getpwuid(st.st_uid); 
                grp = getgrgid(st.st_gid); 
                perms(st.st_mode); 
                printf("%3d %-8s %-7s %9d %s %s\n", 
                       /* #include <stdio.h> 
                        * for printf()  
                        */ 
                       st.st_nlink, pw->pw_name, grp->gr_name, 
                       st.st_size, ftime, dir->d_name); 
            } 
            bp = bp + dir->d_reclen; 
            dir = (struct dirent *) (bp); 
        } while (dir->d_ino != 0); 
        bzero(buf, BUFSZ); 
    } 
    /* Should return something, as main returns an int */ 
} 
And finally, have a look at what the new program, with the changes, looks like:
#include <sys/types.h>          /* opendir, stat, closedir */
#include <dirent.h>             /* opendir, readdir, closedir */
#include <errno.h>              /* perror */
#include <stdlib.h>             /* exit */
#include <sys/stat.h>           /* stat */
#include <unistd.h>             /* stat */
#include <time.h>               /* ctime */
#include <pwd.h>                /* getpwuid */
#include <grp.h>                /* getgrgid */
#include <stdio.h>              /* printf */

int main(void)
{
    DIR *dp;
    struct dirent *dirp;
    struct stat st;
    struct passwd *pw;
    struct group *grp;
    char *ftime;

    if ((dp = opendir(".")) == NULL) {
        printf("Cannot open this directory\n");
        perror("opendir");
        exit(EXIT_FAILURE);
    }

    while ((dirp = readdir(dp)) != NULL) {
        if (dirp->d_reclen != 0) {
            stat(dirp->d_name, &st);
            ftime = ctime(&st.st_mtime);
            ftime[16] = '\0';
            ftime += 4;
            pw = getpwuid(st.st_uid);
            grp = getgrgid(st.st_gid);
            printf("%3zu %-8s %-7s %9zu %s %s\n",
                   st.st_nlink, pw->pw_name, grp->gr_name,
                   st.st_size, ftime, dirp->d_name);
        }
    }

    closedir(dp);
    return 0;
}
It compiles cleanly on my system (kernel 2.6.27, gcc 4.1.2). Note that this implementation is very 'bare bones.' It lists everything in the directory where it is executed (provided the directory permission settings allows it to do so). This includes 'hidden' dot files (.somefile), as well as the current directory, dot (.), and the parent directory, dot-dot (..). Now that you have a working version of the program, I would encourage you to experiment with it, and do the other exercises such as making it accept user input so as to be able to perform long and short listings (and even the -a option to show hidden files) etc. Finally, I have included a diff of the two files, in order to give an idea of what has been changed:

1,9c1,10
< #include <sys/types.h> 
< #include <sys/stat.h> 
< #include <sys/dirent.h> 
< #include <sys/unistd.h> 
< #include <fcntl.h> 
< #include <unistd.h> 
< #include <errno.h> 
< #include <pwd.h> 
< #include <grp.h> 
--- 
> #include <sys/types.h>         /* opendir, stat, closedir */
> #include <dirent.h>            /* opendir, readdir, closedir */
> #include <errno.h>             /* perror */
> #include <stdlib.h>            /* exit */
> #include <sys/stat.h>          /* stat */
> #include <unistd.h>            /* stat */
> #include <time.h>              /* ctime */
> #include <pwd.h>               /* getpwuid */
> #include <grp.h>               /* getgrgid */
> #include <stdio.h>             /* printf */
---
< #define BUFSZ 1024
---
13c14
< main()
---
> int main(void)
15c16,17
<     struct dirent *dir;
---
>     DIR *dp;
>     struct dirent *dirp;
19,20c21
<     char buf[BUFSZ], *bp, *ftime;
<     int dfd, fd, nread;
---
>     char *ftime;
22,43c23,26
<     dfd = open(".", O_RDONLY);
<     bzero(buf, BUFSZ);
<     while (nread = getdents(dfd, (struct dirent *) &buf, BUFSZ) != 0) {
<         bp = buf;
<         dir = (struct dirent *) buf;
<         do {
<             if (dir->d_reclen != 0) {
<                 stat(dir->d_name, &st);
<                 ftime = ctime(&st.st_mtime);
<                 ftime[16] = '\0';
<                 ftime += 4;
<                 pw = getpwuid(st.st_uid);
<                 grp = getgrgid(st.st_gid);
<                 perms(st.st_mode);
<                 printf("%3d %-8s %-7s %9d %s %s\n",
<                        st.st_nlink, pw->pw_name, grp->gr_name,
<                        st.st_size, ftime, dir->d_name);
<             }
<             bp = bp + dir->d_reclen;
<             dir = (struct dirent *) (bp);
<         } while (dir->d_ino != 0);
<         bzero(buf, BUFSZ);
---
>     if ((dp = opendir(".")) == NULL) {
>         printf("Cannot open this directory\n");
>         perror("opendir");
>         exit(EXIT_FAILURE);
44a28,44
>
>     while ((dirp = readdir(dp)) != NULL) {
>         if (dirp->d_reclen != 0) {
>             stat(dirp->d_name, &st);
>             ftime = ctime(&st.st_mtime);
>             ftime[16] = '\0';
>             ftime += 4;
>             pw = getpwuid(st.st_uid);
>             grp = getgrgid(st.st_gid);
>             printf("%3zu %-8s %-7s %9zu %s %s\n",
>                    st.st_nlink, pw->pw_name, grp->gr_name,
>                    st.st_size, ftime, dirp->d_name);
>         }
>     }
>
>     closedir(dp);
>     return 0;