#include #include #include #include #include #include #include #include #include #include #include /* argv[0], used by memerr() and die() */ static char* cmdname; /* to make sure that nick names remain unique we create in the bidding directory an empty file named after our nick name; this is to be deleted when terminating */ stralloc my_file = {0}; /* print an out of memory message to standard error and exit */ void memerr() { static char memerrmsg[] = "out of memory error\n"; write(2, memerrmsg, sizeof(memerrmsg) - 1); if (my_file.len) unlink(my_file.s); exit(1); } /* print a error message to standard error and exit; include "message" in the output message, if not 0, otherwise strerror(errno) is being used */ void die(const char* filename, const char* message) { stralloc msg = {0}; if (stralloc_copys(&msg, cmdname) && stralloc_cats(&msg, ": ") && ( message? stralloc_cats(&msg, message) : stralloc_cats(&msg, strerror(errno)) ) && stralloc_cats(&msg, ": ") && stralloc_cats(&msg, filename) && stralloc_cats(&msg, "\n")) { write(2, msg.s, msg.len); } else { memerr(); } if (my_file.len) unlink(my_file.s); exit(1); } /* sleep for some pseudo-randomically determined time; successive attempts result in longer delays */ void randsleep() { static int invocations = 0; if (invocations == 0) { srand(getpid()); } ++invocations; /* determine timeout value (in milliseconds) */ int timeout = rand() % (10 * invocations + 100); if (poll(0, 0, timeout) < 0) die("poll", 0); } /* delete old contents of buf (if any) and fill buf with the actual contents of filename; false is returned if the given file does not exist */ bool read_contents(stralloc* filename, stralloc* buf) { int fd = open(filename->s, O_RDONLY); if (fd < 0) return false; const size_t buflen = 512; buf->len = 0; /* truncate old contents */ for(;;) { if (!stralloc_readyplus(buf, buflen)) memerr(); ssize_t nbytes = read(fd, buf->s + buf->len, buflen); if (nbytes < 0) die(filename->s, 0); if (nbytes == 0) break; buf->len += nbytes; } close(fd); return true; } /* attempt to update target with the help of the given tmpfile with the new contents provided that the given condition holds; note that we need to lock first (creating the tmpfile) before we can proceed to check the condition */ bool write_and_update_atomically_and_conditionally(stralloc* target, stralloc* tmpfile, stralloc* new_contents, bool (*condition)(stralloc* old_contents, stralloc* new_contents)) { /* get hold of temporary file and prepare it */ int fd; for(;;) { fd = open(tmpfile->s, O_WRONLY|O_CREAT|O_EXCL, 0666); if (fd >= 0) break; if (errno != EEXIST) die(tmpfile->s, 0); randsleep(); } ssize_t nbytes = write(fd, new_contents->s, new_contents->len); if (nbytes < new_contents->len || fsync(fd) < 0 || close(fd) < 0) { unlink(tmpfile->s); die(tmpfile->s, 0); } /* check condition */ stralloc old_contents = {0}; if (read_contents(target, &old_contents) && !condition(&old_contents, new_contents)) { /* we must not update the target file as the condition failed */ unlink(tmpfile->s); stralloc_free(&old_contents); return false; } /* target does not exist yet or condition is met, hence we proceed and update the target */ if (rename(tmpfile->s, target->s) < 0) { unlink(tmpfile->s); die(target->s, "rename failed"); } return true; } /* extract non-negative decimal integer value from the beginning of the stralloc object; return 0 if it is empty or if it starts with a non-digit */ unsigned int get_value(stralloc* sa) { unsigned int value = 0; for (char* cp = sa->s; cp < sa->s + sa->len && isdigit(*cp); ++cp) { value = 10 * value + *cp - '0'; } return value; } /* condition that can be passed to write_and_update_atomically_and_conditionally */ bool higher_bidding(stralloc* old_contents, stralloc* new_contents) { unsigned int old_bidding = get_value(old_contents); unsigned int new_bidding = get_value(new_contents); return new_bidding > old_bidding; } int main(int argc, char** argv) { /* process command line arguments */ cmdname = argv[0]; if (argc != 3) { stralloc usage = {0}; if (stralloc_copys(&usage, "Usage: ") && stralloc_cats(&usage, cmdname) && stralloc_cats(&usage, " bidding-directory name\n")) { write(2, usage.s, usage.len); } else { memerr(); } exit(1); } char* bidding_directory = argv[1]; char* nick_name = argv[2]; /* construct the pathnames for the files we work with */ stralloc bidding_file = {0}; stralloc_copys(&bidding_file, bidding_directory); stralloc_cats(&bidding_file, "/current_bid"); stralloc bidding_tmpfile = {0}; stralloc_copy(&bidding_tmpfile, &bidding_file); stralloc_cats(&bidding_tmpfile, ".tmp"); stralloc_0(&bidding_tmpfile); stralloc_0(&bidding_file); stralloc_copys(&my_file, bidding_directory); stralloc_cats(&my_file, "/"); stralloc_cats(&my_file, nick_name); stralloc_0(&my_file); /* make sure that nobody else has our nick name */ int fd = open(my_file.s, O_WRONLY|O_EXCL|O_CREAT, 0600); if (fd < 0) die(my_file.s, "Sorry, someone else uses your nick name."); stralloc response = {0}; /* input line read from fd 0 */ stralloc current_bid = {0}; /* currently active bid, if any */ stralloc next_bidding = {0}; /* used to prepare our bidding line */ for(;;) { /* display current state of the bidding */ unsigned int last_bidding_value; if (read_contents(&bidding_file, ¤t_bid)) { write(1, current_bid.s, current_bid.len); last_bidding_value = get_value(¤t_bid); } else { static const char msg[] = "No bid yet.\n"; write(1, msg, sizeof(msg) - 1); last_bidding_value = 0; } /* prompt for the next bid */ static const char prompt[] = "Your bid: "; write(1, prompt, sizeof(prompt) - 1); response.len = 0; stralloc_readyplus(&response, 64); ssize_t nbytes = read(0, response.s, 64); if (nbytes <= 0) break; if (!isdigit(response.s[0])) continue; response.len = nbytes; unsigned int value = get_value(&response); if (value <= last_bidding_value) { static const char too_low[] = "This bid is not greater than the highest bid.\n"; write(1, too_low, sizeof(too_low) - 1); continue; } /* try to bid */ next_bidding.len = 0; stralloc_catint(&next_bidding, value); stralloc_cats(&next_bidding, " by "); stralloc_cats(&next_bidding, nick_name); stralloc_cats(&next_bidding, "\n"); if (!write_and_update_atomically_and_conditionally(&bidding_file, &bidding_tmpfile, &next_bidding, higher_bidding)) { static const char too_bad[] = "Sorry, someone else was faster.\n"; write(1, too_bad, sizeof(too_bad) - 1); continue; } static const char ok[] = "Ok.\n"; write(1, ok, sizeof(ok) - 1); /* we were successful... */ } unlink(my_file.s); }