Class 13: Ending (or pausing) Project 1


Reading
Read Chapter 1 of The Art of Unix Programming.

Homework
Read Chapter 1 of The Art of Unix Programming. In Section 1.6 Raymond gives 16 rules behind the Unix design philosophy. Choose any 3 of these rules and describe some aspect of Project 1 that has spoken to that rule. This will mean reading Raymond's descriptions of what the rules mean, not just the list of rules. Note: Please ... please, please, please ... try to be a little bit introspective, a little bit thoughtful about what we've been doing.


The final piece of the library puzzle
The pp project, unlike the others, simply did not work well as a library. The interface could be clean, but at the cost of having the thread of control pass to the plotting function, never to return. We could throw out the library, and launch pp as a program using named pipes to send it data, which would allow the thread of control to continue in the original application, but at the cost of a fair bit of programming overhead ... which you saw in the solution I gave in the last class. So, what to do about it?

One way to deal with a situation like this is to make a library that has the same interface as the original, something like

wplot(vector<double> & X, vector<double> & Y, string type);
in which the implementation goes through all the trouble of spawning the new pp process, setting up the named pipe, sending the data down the named pipe, and removing the named pipe. This was the approach I told you take in class, and this is what you spent most of class working on.

What could go wrong?
What could go wrong? Plenty. For starters, the above approach in its most natural form has us hard-coding the path to pp in our new library. If pp moves, of course, our library breaks --- and it can't be fixed without recompiling the library and, if it's a static library, relinking all the applications that use it. Worse, different users on different systems will doubtless have pp located elsewhere, and for them our library is useless! The typical was to deal with this is to not hard-code pp's location in our library. Instead, we let the user provide pp's location via an environment variable. Every user has environment variables --- like the LD_LIBRARY_PATH variable we've already seen --- which control many aspects of their interaction with Unix. If you type
setenv
you'll get a listing of all your environment variables. They are often defined in your .cshrc or .login files. If we assume that there's an environment variable PP_HOME that gives us the location of pp (we could define such a beast with a line lik
setenv PP_HOME /home/m050000
which could be entered in the shell or stuck in a .cshrc file) then our program could grab the path with the getenv function. Check out genenv with man getenv. Here's a silly little program demoing it:
#include <cstdlib>
#include <iostream>
using namespace std;

int main()
{
  // Get variable name from user
  string s;
  cout << "enter variable name: ";
  cin >> s;

  // Print out var's value or error if var doesn't exist
  char *p = getenv(s.c_str());
  if (!p)
    cout << "Variable not found!" << endl;
  else
    cout << p << endl;

  return 0;
}

Now, in many cases we may not want the user to be burdened with adding the environment variable --- for instance we may expect the sysadmin to install the program we're writing. Assuming we're still talking about the cth program from the previous lecture, we might make "the program" a script that sets the environment variable and launches cth.

#!/bin/tcsh

setenv PP_HOME /home/m050000
cth
With this setup, the system admin could edit the path in PP_HOME, and the actual users would never have to worry about it. When you launch firefox, that's what happens. The executable firefox is just the script:
#!/bin/csh  -x
#
# FIREFOX 1.0 wrapper in /usr/local/bin

#Set home and paths
setenv FIREFOXHOME /usr/local/firefox

#execute firefox
$FIREFOXHOME/firefox-bin $*

What else could go wrong?
The other thing that could go wrong is kind of subtle: What if two people run the cth program at the same time on the same machine ... then one person's named pipe "/tmp/cthPipe" will fail to get created because that named pipe will already exist from the other person. The second user is out of luck!

The solution to this problem is to make sure that each pipe you generate gets its own unique name. There's a standard function mktemp that does this for you. Do a man for more info or ... look at my example:

#include <cstdlib>
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
  // Create string, since mktemp modifies its argument,
  // we can't use a string literal.
  char s[30];
  strcpy(s,"/tmp/dummyXXXXXX");

  // Call mktemp and convert the result to a C++ string
  string fn(mktemp(s));
  if (fn != "")
  {
    ofstream fout(fn.c_str());
    fout << "hello" << endl << "world" << endl;
  }

  return 0;
}


Christopher W Brown
Last modified: Tue Feb 8 16:25:49 EST 2005