C Brain Teaser

Dear Gurus,
I have encountered a C question, which I thought of sharing with you.
This question was asked by one of my technical training staff...Though my training was over I'm still thinking of a solution for this..

Write a C program to do a small task(lets say just simply printing a "Hello World" or so) such that the program should only give output or the desired result ony once after it got compiled and should never give the output nor do anything else. The program should not make use of files or any extrnal means of storing some data.

Please suggest me some ways of achieving this...

Regards
RK

You did not place any restrictions on what not to do. One way could be to remove yourself after the program execution.

[/tmp]$ cat try.cpp
#include <stdio.h>
#include <unistd.h>

int main (int argc, char* argv[])
{
    printf ("Bye Bye Cruel World :-( \n");
    return (unlink (argv[0]));
}
[/tmp]$ gcc -o try try.cpp -lstdc++
[/tmp]$ ls -l try
-rwxr-xr-x  1 xxxxxx g900 5202 May  9 02:51 try
[/tmp]$ ./try
Bye Bye Cruel World :-( 
[/tmp]$ ls -l try
ls: try: No such file or directory
[/tmp]$ 

Astounding that that works, vino! Looking at the Linux man page for unlink(2), I see that it does not detect "text busy". Compare that to the HP-UX man page for unlink(2). The Linux unlink(2) is rather permissive. If that man page is right, it even allows unlinking an active mount point! I'm not sure what effect that would have... Posix does not require that unlink(2) disallow unlinking mount points (but the text in the standard makes it clear that this is because Posix does not ever mention mount points). But unlinking an active program is another matter. On one hand, the Posix Standard says:

That sounds to me like an OS is required to disallow the operation. But toward the end of the page we have:

This leaves me confused as to whether or not the operation is allowed by Posix. But it will not work on most implementations of Unix. I would be interested if this program works with any kernel other than Linux.

A more portable solution would be have the program to write a little shell script to unlink the file and then exec that shell script.

I just tested on Solaris 10, t1000 server, seems to be working :

Does not work on HPUX 11i, as expected.

it works on RHEL3 !

It will work on any OS using a Linux kernel. This includes Red Hat. It is interesting that it works on Solaris though. I have always thought that ETXTBSY was silly. Of course you can't a program while it is running. But there is no reason to prohibit the unlink. The file will have zero links but will not be deleted until the reference count is also zero. This is the way data files have always been handled in Unix.

    return (unlink (argv[0]));

Would turning off permissions instead of unlinking work on HP-UX?

   return (chmod (argv[0],0));

That operation would be legal. It's not clear that this would be enough to satify the condition of the challenge. A chmod command would reverse that. The challenge seems to imply a more complete form of self-destruction such that a second compile of the source code would be in order.

OK, here is my version. It assumes that rm is /usr/bin/rm. If your rm is elsewhere you will need a little change.

#include <stdio.h>
#include <unistd.h>
main(int argc, char *argv[])
{
        printf("Hello, world... for now!\n");
        execl("/usr/bin/rm", "rm", argv[0], 0);
}

Well, this would work. Your binary file becomes non-ETXTBSY the moment the execl is run. So no problem removing it.

How about this approach,

After printing the statement,
copying /dev/null to the binary
or
making the binary 0 bytes.

Binary would be still available but unable to use for the second time

I believe this would work for the challenge posted!

This is another way of going about it:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
main(int argc, char *argv[]) {
        int fd;
        char buf[]="0000000000";
        fprintf(stdout,"Hello, world... for now!\n");
        fd=open(argv[0],O_WRONLY);
        if(fd==-1) {
                perror("Could not open file");
        }
        if((write(fd,buf,strlen(buf)))==-1) {
                perror("Could not write to file");
        }
        close(fd);
}

You can run this only once. Tested on Solaris 10. Dunno about HP-UX. Am trying to get a system so that I can test this.

-Edit:
You won't know what has happened either. The file is still there, still the same size too, but it won't run anymore.

This is something interesting.

I wonder how it worked on solaris.

It didnt when I tried on FC3 ( as expected )

It is not possible to write an exe file when it is in use or busy. Same thing happens when a program which is running for hours together when updated with a new copy of the binary; the operation will is not permitted. Would say the text file is currently busy.

This is the error when ran in FC3

Could not open file: Text file busy
Could not write to file: Bad file descriptor

I could find that after perror the operation still continues to write to the file without a valid file descriptor which is not the proper way.

This doesn't work on HP-UX either - as expected, to be honest. Throws the same error.

Does that mean tested and worked on Solaris 10 ?
Or I mistook it has worked for tested instead ?

It worked.

# cc test.c
# ls -l a.out
-rwxr-xr-x   1 root     other       6988 May 11 13:18 a.out
# ./a.out
Hello, world... for now!
# ls -l a.out
-rwxr-xr-x   1 root     other       6988 May 11 13:18 a.out
# ./a.out
bash: ./a.out: cannot execute binary file

OK, If Solaris is allowing that, Solaris is broken. Unlinking an executable is one thing. But opening it for writing?? You've got to be kidding me. The Linux man page for open clearly states that this won't fly in even in Linux. When I saw that Solaris allowed unlinking a busy executable, I thought that the Solaris folks had made a reasoned change in the way things work. Now I see that they just fumbled. Don't write into running executables. A program like that is going to Solaris specific at best. And once Sun notices the problem they will probably fix it.

Do you mean its a possible bug in Solaris allowing to write in a executable that is currently being executed ?

If thats a bug, how would they be notified about the bug ?

Perhaps http://bugs.opensolaris.org/ ?