f((poll_list[1].revents&POLLIN) == POLLIN) handle(poll_list[1].fd,NORMAL_DATA); if((poll_list[1].revents&POLLPRI) == POLLPRI) handle(poll_list[1].fd,HIPRI_DATA); } } 2.1.3 Can I use SysV IPC at the same time as select or poll? ------------------------------------------------------------ *No.* (Except on AIX, which has an incredibly ugly kluge to allow this.) In general, trying to combine the use of `select()' or `poll()' with using SysV message queues is troublesome. SysV IPC objects are not handled by file descriptors, so they can't be passed to `select()' or `poll()'. There are a number of workarounds, of varying degrees of ugliness: - Abandon SysV IPC completely :-) - `fork()', and have the child process handle the SysV IPC, communicating with the parent process by a pipe or socket, which the parent process can `select()' on. - As above, but have the child process do the `select()', and communicate with the parent by message queue. - Arrange for the process that sends messages to you to send a signal after each message. *Warning:* handling this right is non-trivial; it's very easy to write code that can potentially lose messages or deadlock using this method. (Other methods exist.) 2.2 How can I tell when the other end of a connection shuts down? ================================================================= If you try to read from a pipe, socket, FIFO etc. when the writing end of the connection has been closed, you get an end-of-file indication (`read()' returns 0 bytes read). If you try and write to a pipe, socket etc. when the reading end has closed, then a `SIGPIPE' signal will be delivered to the process, killing it unless the signal is caught. (If you ignore or block the signal, the `write()' call fails with `EPIPE'.) 2.3 Best way to read directories? ================================= While historically there have been several different interfaces for this, the only one that really matters these days the the Posix.1 standard `' functions. The function `opendir()' opens a specified directory; `readdir()' reads directory entries from it in a standardised format; `closedir()' does the obvious. Also provided are `rewinddir()', `telldir()' and `seekdir()' which should also be obvious. If you are looking to expand a wildcard filename, then most systems have the `glob()' function; also check out `fnmatch()' to match filenames against a wildcard, or `ftw()' to traverse entire directory trees. 2.4 How can I find out if someone else has a file open? ======================================================= This is another candidate for "Frequently Unanswered Questions" because, in general, your program should never be interested in whether someone else has the file open. If you need to deal with concurrent access to the file, then you should be looking at advisory locking. This is, in general, too hard to do anyway. Tools like `fuser' and `lsof' that find out about open files do so by grovelling through kernel data structures in a most unhealthy fashion. You can't usefully invoke them from a program, either, because by the time you've found out that the file is/isn't open, the information may already be out of date. 2.5 How do I 'lock' a file? =========================== There are three main file locking mechanisms available. All of them are "advisory"[*], which means that they rely on programs co-operating in order to work. It is therefore vital that all programs in an application should be consistent in their locking regime, and great care is required when your programs may be sharing files with third-party software. [*] Well, actually some Unices permit mandatory locking via the sgid bit - RTFM for this hack. Some applications use lock files - something like "filename.lock". Simply testing for the existence of such files is inadequate though, since a process may have been killed while holding the lock. The method used by UUCP (probably the most notable example: it uses lock files for controlling access to modems, remote systems etc.) is to store the PID in the lockfile, and test if that pid is still running. Even this isn't enough to be sure (since PIDs are recycled); it has to have a backstop check to see if the lockfile is old, which means that the process holding the lock must update the file regularly. Messy. The locking functions are: flock(); lockf(); fcntl(); `flock()' originates with BSD, and is now available in most (but not all) Unices. It is simple and effective on a single host, but doesn't work at all with NFS. It locks an entire file. Perhaps rather deceptively, the popular Perl programming language implements its own `flock()' where necessary, conveying the illusion of true portability. `fcntl()' is the only POSIX-compliant locking mechanism, and is therefore the only truly portable lock. It is also the most powerful, and the hardest to use. For NFS-mounted file systems, `fcntl()' requests are passed to a daemon (`rpc.lockd'), which communicates with the lockd on the server host. Unlike `flock()' it is capable of record-level locking. `lockf()' is merely a simplified programming interface to the locking functions of `fcntl()'. Whatever locking mechanism you use, it is important to sync all your file IO while the lock is active: lock(fd); write_to(some_function_of(fd)); flush_output_to(fd); /* NEVER unlock while output may be buffered */ unlock(fd); do_something_else; /* another process might update it */ lock(fd); seek(fd, somewhere); /* because our old file pointer is not safe */ do_something_with(fd); ... A few useful `fcntl()' locking recipes (error handling omitted for simplicity) are: #include #include read_lock(int fd) /* a shared lock on an entire file */ { fcntl(fd, F_SETLKW, file_lock(F_RDLCK, SEEK_SET)); } write_lock(int fd) /* an exclusive lock on an entire file */ { fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_SET)); } append_lock(int fd) /* a lock on the _end_ of a file - other processes may access existing records */ { fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_END)); } The function file_lock used by the above is struct flock* file_lock(short type, short whence) { static struct flock ret ; ret.l_type = type ; ret.l_start = 0 ; ret.l_whence = whence ; ret.l_len = 0 ; ret.l_pid = getpid() ; return &ret ; } 2.6 How do I find out if a file has been updated by another process? ==================================================================== This is close to being a Frequently Unanswered Question, because people asking it are often looking for some notification from the system when a file or directory is changed, and there is no portable way of getting this. (IRIX has a non-standard facility for monitoring file accesses, but I've never heard of it being available in any other flavour.) In general, the best you can do is to use `fstat()' on the file. (Note: the overhead on `fstat()' is quite low, usually much lower than the overhead of `stat()'.) By watching the mtime and ctime of the file, you can detect when it is modified, or deleted/linked/renamed. This is a bit kludgy, so you might want to rethink *why* you want to do it. 2.7 How does the 'du' utility work? =================================== `du' simply traverses the directory structure calling `stat()' (or more accurately, `lstat()') on every file and directory it encounters, adding up the number of blocks consumed by each. If you want more detail about how it works, then the simple answer is: Use the source, Luke! Source for BSD systems (FreeBSD, NetBSD and OpenBSD) is available as unpacked source trees on their FTP distribution sites; source for GNU versions of utilities is available from any of the GNU mirrors, but you have to unpack the archives yourself. 2.8 How do I find the size of a file? ===================================== Use `stat()', or `fstat()' if you have the file open. These calls fill in a data structure containing all the information about the file that the system keeps track of; that includes the owner, group, permissions, size, last access time, last modification time, etc. The following routine illustrates how to use `stat()' to get the file size. #include #include #include #include int get_file_size(char *path,off_t *size) { struct stat file_stats; if(stat(path,&file_stats)) return -1; *size = file_stats.st_size; return 0; } 2.9 How do I expand '~' in a filename like the shell does? ========================================================== The standard interpretation for `~' at the start of a filename is: if alone or followed by a `/', then substitute the current user's `HOME' directory; if followed by the name of a user, then substitute that user's `HOME' directory. If no valid expansion can be found, then shells will leave the filename unchanged. Be wary, however, of filenames that actually start with the `~' character. Indiscriminate tilde-expansion can make it very difficult to specify such filenames to a program; while quoting will prevent the shell from doing the expansion, the quotes will have been removed by the time the program sees the filename. As a general rule, do not try and perform tilde-expansion on filenames that have been passed to the program on the command line or in environment variables. (Filenames generated within the program, obtained by prompting the user, or obtained from a configuration file, are good candidates for tilde-expansion.) Here's a piece of C++ code (using the standard string class) to do the job: string expand_path(const string& path) { if (path.length() == 0 || path[0] != '~') return path; const char *pfx = NULL; string::size_type pos = path.find_first_of('/'); if (path.length() == 1 || pos == 1) { pfx = getenv("HOME"); if (!pfx) { // Punt. We're trying to expand ~/, but HOME isn't set struct passwd *pw = getpwuid(getuid()); if (pw) pfx = pw->pw_dir; } } else { string user(path,1,(pos==string::npos) ? string::npos : pos-1); struct passwd *pw = getpwnam(user.c_str()); if (pw) pfx = pw->pw_dir; } // if we failed to find an expansion, return the path unchanged. if (!pfx) return path; string result(pfx); if (pos == string::npos) return result; if (result.length() == 0 || result[result.length()-1] != '/') result += '/'; result += path.substr(pos+1); return result; } 2.10 What can I do with named pipes (FIFOs)? ============================================ 2.10.1 What is a named pipe? ---------------------------- A "named pipe" is a special file that is used to transfer data between unrelated processes. One (or more) process(es) writes to it, while another process reads from it. Named pipes are visible in the file system and may be viewed with ls like any other file. (Named pipes are also called fifo's, which stands for "First In, First Out".) Named pipes may be used to pass data between unrelated processes, while normal (unnamed) pipes can only connect parent/child processes (unless you try *very* hard). Named pipes are strictly unidirectional, even on systems where anonymous pipes are bidirectional (full-duplex). 2.10.2 How do I create a named pipe? ------------------------------------ To create a named pipe interactively, you'll use either `mknod' or `mkfifo'. On some systems, mknod will be found in /etc. In other words, it might not be on your path. See your man pages for details. To make a named pipe within a C program use `mkfifo()': /* set the umask explicitly, you don't know where it's been */ umask(0); if (mkfifo("test_fifo", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) { perror("mkfifo"); exit(1); } If you don't have `mkfifo()', you'll have to use `mknod()': /* set the umask explicitly, you don't know where it's been */ umask(0); if (mknod("test_fifo", S_IFIFO | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, 0)) { perror("mknod"); exit(1); } 2.10.3 How do I use a named pipe? --------------------------------- To use the pipe, you open it like a normal file, and use `read()' and `write()' just as though it was a plain pipe. However, the `open()' of the pipe may block. The following rules apply: * If you open for both reading and writing (O_RDWR), then the open will not block. * If you open for reading (O_RDONLY), the open will block until another process opens the FIFO for writing, unless O_NONBLOCK is specified, in which case the open succeeds. * If you open for writing (O_WRONLY), the open will block until another process opens the FIFO for reading, unless O_NONBLOCK is specified, in which case the open fails. When reading and writing the FIFO, the same considerations apply as for regular pipes and sockets, i.e. `read()' will return EOF when all writers have closed, and `write()' will raise SIGPIPE when there are no readers (if SIGPIPE is blocked or ignored, the call fails with EPIPE). 2.10.4 Can I use a named pipe across NFS? ----------------------------------------- No, you can't. There is no facility in the NFS protocol to do this. (You may be able to use a named pipe on an NFS-mounted filesystem to communicate between processes on the same client, though.) 2.10.5 Can multiple processes write to the pipe simultaneously? --------------------------------------------------------------- If each piece of data written to the pipe is less than PIPE_BUF in size, then they will not be interleaved. However, the boundaries of writes are not preserved; when you read from the pipe, the read call will return as much data as possible, even if it originated from multiple writes. The value of PIPE_BUF is guaranteed (by Posix) to be at least 512. It may or may not be defined in `', but it can be queried for individual pipes using `pathconf()' or `fpathconf()'. 2.10.6 Using named pipes in applications ---------------------------------------- How can I implement two way communication between one server and several clients? It is possible that more than one client is communicating with your server at once. As long as each command they send to the server is smaller than PIPE_BUF (see above), they can all use the same named pipe to send data to the server. All clients can easily know the name of the server's incoming fifo. However, the server can not use a single pipe to communicate with the clients. If more than one client is reading the same pipe, there is no way to ensure that the appropriate client receives a given response. A solution is to have the client create its own incoming pipe before sending data to the server, or to have the server create its outgoing pipes after receiving data from the client. Using the client's process ID in the pipe's name is a common way to identify them. Using fifo's named in this manner, each time the client sends a command to the server, it can include its PID as part of the command. Any returned data can be sent through the appropriately named pipe.  * 3. Terminal I/O *  *************** 3.1 How can I make my program not echo input? ============================================= How can I make my program not echo input, like login does when asking for your password? There is an easy way, and a slightly harder way: The easy way, is to use `getpass()', which is probably found on almost all Unices. It takes a string to use as a prompt. It will read up to an EOF or newline and returns a pointer to a static area of memory holding the string typed in. The harder way is to use `tcgetattr()' and `tcsetattr()', both use a `struct termios' to manipulate the terminal. The following two routines should allow echoing, and non-echoing mode. #include #include #include #include static struct termios stored; void echo_off(void) { struct termios new; tcgetattr(0,&stored); memcpy(&new, &stored, sizeof(struct termios)); new.c_lflag &= (~ECHO); tcsetattr(0,TCSANOW,&new); return; } void echo_on(void) { tcsetattr(0,TCSANOW,&stored); return; } Both routines used, are defined by the POSIX standard. 3.2 How can I read single characters from the terminal? ======================================================= How can I read single characters from the terminal? My program is always waiting for the user to press `'. Terminals are usually in canonical mode, where input is read in lines after it is edited. You may set this into non-canonical mode, where you set how many characters should be read before input is given to your program. You also may set the timer in non-canonical mode terminals to 0, this timer flushs your buffer at set intervals. By doing this, you can use `getc()' to grab the key pressed immediately by the user. We use `tcgetattr()' and `tcsetattr()' both of which are defined by POSIX to manipulate the `termios' structure. #include #include #include #include static struct termios stored; void set_keypress(void) { struct termios new; tcgetattr(0,&stored); memcpy(&new,&stored,sizeof(struct termios)); /* Disable canonical mode, and set buffer size to 1 byte */ new.c_lflag &= (~ICANON); new.c_cc[VTIME] = 0; new.c_cc[VMIN] = 1; tcsetattr(0,TCSANOW,&new); return; } void reset_keypress(void) { tcsetattr(0,TCSANOW,&stored); return; } 3.3 How can I check and see if a key was pressed? ================================================= How can I check and see if a key was pressed? On DOS I use the `kbhit()' function, but there doesn't seem to be an equivalent? If you set the terminal to single-character mode (see previous answer), then (on most systems) you can use `select()' or `poll()' to test for readability. 3.4 How can I move the cursor around the screen? ================================================ How can I move the cursor around the screen? I want to do full screen editing without using curses. Seriously, you probably *don't* want to do this. Curses knows about how to handle all sorts of oddities that different terminal types exhibit; while the termcap/terminfo data will tell you whether any given terminal type possesses any of these oddities, you will probably find that correctly handling all the combinations is a *huge* job. However, if you insist on getting your hands dirty (so to speak), look into the `termcap' functions, particularly `tputs()', `tparm()' and `tgoto()'. 3.5 What are pttys? =================== Pseudo-teletypes (pttys, ptys, other variant abbreviations) are pseudo-devices that have two parts: the "master" side, which can be thought of as the 'user', and the "slave" side, which behaves like a standard tty device. They exist in order to provide a means to emulate the behaviour of a serial terminal under the control of a program. For example, `telnet' uses a pseudo-terminal on the remote system; the remote login shell sees the behaviour it expects from a tty device, but the master side of the pseudo-terminal is being controlled by a daemon that forwards all data over the network. They are also used by programs such as `xterm', `expect', `script', `screen', `emacs', and many others. 3.6 How to handle a serial port or modem? ========================================= todo  * 4. System Information *  ********************* 4.1 How can I tell how much memory my system has? ================================================= This is another "Frequently Unanswered Question". In most cases, you should not even *attempt* to find out. If you really must, then it can usually be done, but in a highly system-dependent fashion. For example, on Solaris, you can use `sysconf(_SC_PHYS_PAGES)'; on FreeBSD, you can use `sysctl()', on Linux there is probably something in `/proc', etc. I'm not aware of any more portable methods. For HP-UX (9 and 10), the following code has been contributed: struct pst_static pst; if (pstat_getstatic(&pst, sizeof(pst), (size_t) 1, 0) != -1) { printf(" Page Size: %lu\n", pst.page_size); printf("Phys Pages: %lu\n", pst.physical_memory); } 4.2 How do I check a user's password? ===================================== 4.2.1 How do I get a user's password? ------------------------------------- Traditionally user passwords were kept in the `/etc/passwd' file, on most UNIX flavours. Which is usually of this format: username:password:uid:gid:gecos field:home directory:login shell Though this has changed with time, now user information may be kept on other hosts, or not necessarily in the `/etc/passwd' file. Modern implementations also made use of "shadow" password files which hold the password, along with sensitive information. This file would be readable only by privileged users. The password is usually not in clear text, but encrypted due to security concerns. POSIX defines a suite of routines which can be used to access this database for queries. The quickest way to get an individual record for a user is with the `getpwnam()' and `getpwuid()' routines. Both return a pointer to a struct passwd, which holds the users information in various members. `getpwnam()' accepts a string holding the user's name, `getpwuid()' accepts a uid (type `uid_t' as defined by POSIX). Both return NULL if they fail. However, as explained earlier, a shadow database exists on most modern systems to hold sensitive information, namely the password. Some systems only return the password if the calling uid is of the superuser, others require you to use another suite of functions for the shadow password database. If this is the case you need to make use of `getspnam()', which accepts a username and returns a struct spwd. Again, in order to successfully do this, you will need to have privileges. (On some systems, notably HP-UX and SCO, you may need to use `getprpwnam()' instead.) 4.2.2 How do I get shadow passwords by uid? ------------------------------------------- My system uses the getsp* suite of routines to get the sensitive user information. However I do not have `getspuid()', only `getspnam()'. How do I work around this, and get by uid? The work around is relatively painless. The following routine should go straight into your personal utility library: #include #include #include #include struct spwd *getspuid(uid_t pw_uid) { struct spwd *shadow; struct passwd *ppasswd; if( ((ppasswd = getpwuid(pw_uid)) == NULL) || ((shadow = getspnam(ppasswd->pw_name)) == NULL)) return NULL; return shadow; } The problem is, that some systems do not keep the uid, or other information in the shadow database. 4.2.3 How do I verify a user's password? ---------------------------------------- The fundamental problem here is, that various authentication systems exist, and passwords aren't always what they seem. Also with the traditional one way encryption method used by most UNIX flavours (out of the box), the encryption algorithm may differ, some systems use a one way DES encryption, others like the international release of FreeBSD use MD5. The most popular way is to have a one way encryption algorithm, where the password cannot be decrypted. Instead the password is taken in clear text from input, and encrypted and checked against the encrypted password in the database. The details of how to encrypt should really come from your man page for `crypt()', but here's a usual version: /* given a plaintext password and an encrypted password, check if * they match; returns 1 if they match, 0 otherwise. */ int check_pass(const char *plainpw, const char *cryptpw) { return strcmp(crypt(plainpw,cryptpw), cryptpw) == 0; } This works because the salt used in encrypting the password is stored as an initial substring of the encrypted value. *WARNING:* on some systems, password encryption is actually done with a variant of crypt called `bigcrypt()'.  * 5. Miscellaneous programming *  **************************** 5.1 How do I compare strings using wildcards? ============================================= The answer to *that* depends on what exactly you mean by "wildcards". There are two quite different concepts that qualify as "wildcards". They are: *Filename patterns* These are what the shell uses for filename expansion ("globbing") *Regular Expressions* These are used by editors, `grep', etc. for matching text, but they normally *aren't* applied to filenames 5.1.1 How do I compare strings using filename patterns? ------------------------------------------------------- Unless you are unlucky, your system should have a function `fnmatch()' to do filename matching. This generally allows only the Bourne shell style of pattern; i.e. it recognises `*', `[...]' and `?', but probably won't support the more arcane patterns available in the Korn and Bourne-Again shells. If you don't have this function, then rather than reinvent the wheel, you are probably better off snarfing a copy from the BSD or GNU sources. Also, for the common cases of matching actual filenames, look for `glob()', which will find all existing files matching a pattern. 5.1.2 How do I compare strings using regular expressions? --------------------------------------------------------- There are a number of slightly different syntaxes for regular expressions; most systems use at least two: the one recognised by `ed', sometimes known as "Basic Regular Expressions", and the one recognised by `egrep', "Extended Regular Expressions". Perl has it's own slightly different flavour, as does Emacs. To support this multitude of formats, there is a corresponding multitude of implementations. Systems will generally have regexp-matching functions (usually `regcomp()' and `regexec()') supplied, but be wary; some systems have more than one implementation of these functions available, with different interfaces. In addition, there are many library implementations available. (It's common, BTW, for regexps to be compiled to an internal form before use, on the assumption that you may compare several separate strings against the same regexp.) One library available for this is the `rx' library, available from the GNU mirrors. This seems to be under active development, which may be a good or a bad thing depending on your point of view :-) 5.2 What's the best way to send mail from a program? ==================================================== There are several ways to send email from a Unix program. Which is the best method to use in a given situation varies, so I'll present two of them. A third possibility, not covered here, is to connect to a local SMTP port (or a smarthost) and use SMTP directly; see RFC 821. 5.2.1 The simple method: /bin/mail ---------------------------------- For simple applications, it may be sufficient to invoke `mail' (usually `/bin/mail', but could be `/usr/bin/mail' on some systems). *WARNING:* Some versions of UCB Mail may execute commands prefixed by `~!' or `~|' given in the message body even in non-interactive mode. This can be a security risk. Invoked as `mail -s 'subject' recipients...' it will take a message body on standard input, and supply a default header (including the specified subject), and pass the message to `sendmail' for delivery. This example mails a test message to `root' on the local system: #include #define MAILPROG "/bin/mail" int main() { FILE *mail = popen(MAILPROG " -s 'Test Message' root", "w"); if (!mail) { perror("popen"); exit(1); } fprintf(mail, "This is a test.\n"); if (pclose(mail)) { fprintf(stderr, "mail failed!\n"); exit(1); } } If the text to be sent is already in a file, then one can do: system(MAILPROG " -s 'file contents' root #include #include #include #include #include /* #include if you have it */ #ifndef _PATH_SENDMAIL #define _PATH_SENDMAIL "/usr/lib/sendmail" #endif /* -oi means "dont treat . as a message terminator" * remove ,"--" if using a pre-V8 sendmail (and hope that no-one * ever uses a recipient address starting with a hyphen) * you might wish to add -oem (report errors by mail) */ #define SENDMAIL_OPTS "-oi","--" /* this is a macro for returning the number of elements in array */ #define countof(a) ((sizeof(a))/sizeof((a)[0])) /* send the contents of the file open for reading on FD to the * specified recipients; the file is assumed to contain RFC822 headers * & body, the recipient list is terminated by a NULL pointer; returns * -1 if error detected, otherwise the return value from sendmail * (which uses to provide meaningful exit codes) */ int send_message(int fd, const char **recipients) { static const char *argv_init[] = { _PATH_SENDMAIL, SENDMAIL_OPTS }; const char **argvec = NULL; int num_recip = 0; pid_t pid; int rc; int status; /* count number of recipients */ while (recipients[num_recip]) ++num_recip; if (!num_recip) return 0; /* sending to no recipients is successful */ /* alloc space for argument vector */ argvec = malloc((sizeof char*) * (num_recip+countof(argv_init)+1)); if (!argvec) return -1; /* initialise argument vector */ memcpy(argvec, argv_init, sizeof(argv_init)); memcpy(argvec+countof(argv_init), recipients, num_recip*sizeof(char*)); argvec[num_recip + countof(argv_init)] = NULL; /* may need to add some signal blocking here. */ /* fork */ switch (pid = fork()) { case 0: /* child */ /* Plumbing */ if (fd != STDIN_FILENO) dup2(fd, STDIN_FILENO); /* defined elsewhere - closes all FDs >= argument */ closeall(3); /* go for it: */ execv(_PATH_SENDMAIL, argvec); _exit(EX_OSFILE); default: /* parent */ free(argvec); rc = waitpid(pid, &status, 0); if (rc < 0) return -1; if (WIFEXITED(status)) return WEXITSTATUS(status); return -1; case -1: /* error */ free(argvec); return -1; } } 5.2.2.2 Allowing sendmail to deduce the recipients .................................................. The `-t' option to `sendmail' instructs `sendmail' to parse the headers of the message, and use all the recipient-type headers (i.e. `To:', `Cc:' and `Bcc:') to construct the list of envelope recipients. This has the advantage of simplifying the `sendmail' command line, but makes it impossible to specify recipients other than those listed in the headers. (This is not usually a problem.) As an example, here's a program to mail a file on standard input to specified recipients as a MIME attachment. Some error checks have been omitted for brevity. This requires the `mimencode' program from the `metamail' distribution. #include #include #include /* #include if you have it */ #ifndef _PATH_SENDMAIL #define _PATH_SENDMAIL "/usr/lib/sendmail" #endif #define SENDMAIL_OPTS "-oi" #define countof(a) ((sizeof(a))/sizeof((a)[0])) char tfilename[L_tmpnam]; char command[128+L_tmpnam]; void cleanup(void) { unlink(tfilename); } int main(int argc, char **argv) { FILE *msg; int i; if (argc < 2) { fprintf(stderr, "usage: %s recipients...\n", argv[0]); exit(2); } if (tmpnam(tfilename) == NULL || (msg = fopen(tfilename,"w")) == NULL) exit(2); atexit(cleanup); fclose(msg); msg = fopen(tfilename,"a"); if (!msg) exit(2); /* construct recipient list */ fprintf(msg, "To: %s", argv[1]); for (i = 2; i < argc; i++) fprintf(msg, ",\n\t%s", argv[i]); fputc('\n',msg); /* Subject */ fprintf(msg, "Subject: file sent by mail\n"); /* sendmail can add it's own From:, Date:, Message-ID: etc. */ /* MIME stuff */ fprintf(msg, "MIME-Version: 1.0\n"); fprintf(msg, "Content-Type: application/octet-stream\n"); fprintf(msg, "Content-Transfer-Encoding: base64\n"); /* end of headers - insert a blank line */ fputc('\n',msg); fclose(msg); /* invoke encoding program */ sprintf(command, "mimencode -b >>%s", tfilename); if (system(command)) exit(1); /* invoke mailer */ sprintf(command, "%s %s -t <%s", _PATH_SENDMAIL, SENDMAIL_OPTS, tfilename); if (system(command)) exit(1); return 0; }  * 6. Use of tools *  *************** 6.1 How can I debug the children after a fork? ============================================== Depending on the tools available there are various ways: Your debugger may have options to select whether to follow the parent or the child process (or both) after a `fork()', which may be sufficient for some purposes. Alternatively, your debugger may have an option which allows you to attach to a running process. This can be used to attach to the child process after it has been started. If you don't need to examine the very start of the child process, this is usually sufficient. Otherwise, you may wish to insert a `sleep()' call after the `fork()' in the child process, or a loop such as the following: { volatile int f = 1; while(f); } which will hang the child process until you explicitly set `f' to 0 using the debugger. Remember, too, that actively using a debugger isn't the only way to find errors in your program; utilities are available to trace system calls and signals on many unix flavours, and verbose logging is also often useful. 6.2 How to build library from other libraries? ============================================== Assuming we're talking about an archive (static) library, the easiest way is to explode all the constituent libraries into their original objects using `ar x' in an empty directory, and combine them all back together. Of course, there is the potential for collision of filenames, but if the libraries are large, you probably don't want to be combining them in the first place.... 6.3 How to create shared libraries / dlls? ========================================== The precise method for creating shared libraries varies between different systems. There are two main parts to the process; firstly the objects to be included in the shared library must be compiled, usually with options to indicate that the code is to be position-independent; secondly, these objects are linked together to form the library. Here's a trivial example that should illustrate the idea: /* file shrobj.c */ const char *myfunc() { return "Hello World"; } /* end shrobj.c */ /* file hello.c */ #include extern const char *myfunc(); main() { printf("%s\n", myfunc()); return 0; } /* end hello.c */ $ gcc -fpic -c shrobj.c $ gcc -shared -o libshared.so shrobj.o $ gcc hello.c libshared.so $ ./a.out Hello World For compilers other than gcc, change the compiler options as follows: AIX 3.2 using xlc (unverified) Drop the `-fpic', and use `-bM:SRE -bE:libshared.exp' instead of `-shared'. You also need to create a file `libshared.exp' containing the list of symbols to export, in this case `myfunc'. In addition, use `-e _nostart' when linking the library (on newer versions of AIX, I believe this changes to `-bnoentry'). SCO OpenServer 5 using the SCO Develop