diff options
author | Peter Ammon <corydoras@ridiculousfish.com> | 2012-03-02 00:27:40 -0800 |
---|---|---|
committer | Peter Ammon <corydoras@ridiculousfish.com> | 2012-03-02 00:27:40 -0800 |
commit | 8b26d0104c1d85c271e5ce6e08bfe64a779beba7 (patch) | |
tree | 995568526f190084fd12d74bc6dcd0e828b1018a /fishd.cpp | |
parent | 36622c35781c3212c2102c45781a496f3e1b3659 (diff) |
Some initial changes to use CLO_EXEC, with an eye towards some day using it correctly.
Diffstat (limited to 'fishd.cpp')
-rw-r--r-- | fishd.cpp | 256 |
1 files changed, 256 insertions, 0 deletions
@@ -73,6 +73,15 @@ time the original barrier request was sent have been received. #include "path.h" #include "print_help.h" +#ifndef HOST_NAME_MAX +/** + Maximum length of hostname return. It is ok if this is too short, + getting the actual hostname is not critical, so long as the string + is unique in the filesystem namespace. + */ +#define HOST_NAME_MAX 255 +#endif + /** Maximum length of socket filename */ @@ -189,6 +198,252 @@ static void handle_term( int signal ) /** + Writes a pid_t in decimal representation to str. + str must contain sufficient space. + The conservatively approximate maximum number of characters a pid_t will + represent is given by: (int)(0.31 * sizeof(pid_t) + CHAR_BIT + 1) + Returns the length of the string + */ +static int sprint_pid_t( pid_t pid, char *str ) +{ + int len, i = 0; + int dig; + + /* Store digits in reverse order into string */ + while( pid != 0 ) + { + dig = pid % 10; + str[i] = '0' + dig; + pid = ( pid - dig ) / 10; + i++; + } + len = i; + /* Reverse digits */ + i /= 2; + while( i ) + { + i--; + dig = str[i]; + str[i] = str[len - 1 - i]; + str[len - 1 - i] = dig; + } + return len; +} + + + +/** + Writes a pseudo-random number (between one and maxlen) of pseudo-random + digits into str. + str must point to an allocated buffer of size of at least maxlen chars. + Returns the number of digits written. + Since the randomness in part depends on machine time it has _some_ extra + strength but still not enough for use in concurrent locking schemes on a + single machine because gettimeofday may not return a different value on + consecutive calls when: + a) the OS does not support fine enough resolution + b) the OS is running on an SMP machine. + Additionally, gettimeofday errors are ignored. + Excludes chars other than digits since ANSI C only guarantees that digits + are consecutive. + */ +static int sprint_rand_digits( char *str, int maxlen ) +{ + int i, max; + struct timeval tv; + + /* + Seed the pseudo-random generator based on time - this assumes + that consecutive calls to gettimeofday will return different values + and ignores errors returned by gettimeofday. + Cast to unsigned so that wrapping occurs on overflow as per ANSI C. + */ + (void)gettimeofday( &tv, NULL ); + srand( (unsigned int)tv.tv_sec + (unsigned int)tv.tv_usec * 1000000UL ); + max = 1 + (maxlen - 1) * (rand() / (RAND_MAX + 1.0)); + for( i = 0; i < max; i++ ) + { + str[i] = '0' + 10 * (rand() / (RAND_MAX + 1.0)); + } + return i; +} + + +/** + Generate a filename unique in an NFS namespace by creating a copy of str and + appending .{hostname}.{pid} to it. If gethostname() fails then a pseudo- + random string is substituted for {hostname} - the randomness of the string + should be strong enough across different machines. The main assumption + though is that gethostname will not fail and this is just a "safe enough" + fallback. + The memory returned should be freed using free(). + */ +static char *gen_unique_nfs_filename( const char *filename ) +{ + int pidlen, hnlen, orglen = strlen( filename ); + char hostname[HOST_NAME_MAX + 1]; + char *newname; + + if ( gethostname( hostname, HOST_NAME_MAX + 1 ) == 0 ) + { + hnlen = strlen( hostname ); + } + else + { + hnlen = sprint_rand_digits( hostname, HOST_NAME_MAX ); + hostname[hnlen] = '\0'; + } + newname = (char *)malloc( orglen + 1 /* period */ + hnlen + 1 /* period */ + + /* max possible pid size: 0.31 ~= log(10)2 */ + (int)(0.31 * sizeof(pid_t) * CHAR_BIT + 1) + + 1 /* '\0' */ ); + + if ( newname == NULL ) + { + debug( 1, L"gen_unique_nfs_filename: %s", strerror( errno ) ); + return newname; + } + memcpy( newname, filename, orglen ); + newname[orglen] = '.'; + memcpy( newname + orglen + 1, hostname, hnlen ); + newname[orglen + 1 + hnlen] = '.'; + pidlen = sprint_pid_t( getpid(), newname + orglen + 1 + hnlen + 1 ); + newname[orglen + 1 + hnlen + 1 + pidlen] = '\0'; + /* debug( 1, L"gen_unique_nfs_filename returning with: newname = \"%s\"; " + L"HOST_NAME_MAX = %d; hnlen = %d; orglen = %d; " + L"sizeof(pid_t) = %d; maxpiddigits = %d; malloc'd size: %d", + newname, (int)HOST_NAME_MAX, hnlen, orglen, + (int)sizeof(pid_t), + (int)(0.31 * sizeof(pid_t) * CHAR_BIT + 1), + (int)(orglen + 1 + hnlen + 1 + + (int)(0.31 * sizeof(pid_t) * CHAR_BIT + 1) + 1) ); */ + return newname; +} + + +/** + The number of milliseconds to wait between polls when attempting to acquire + a lockfile + */ +#define LOCKPOLLINTERVAL 10 + +/** + Attempt to acquire a lock based on a lockfile, waiting LOCKPOLLINTERVAL + milliseconds between polls and timing out after timeout seconds, + thereafter forcibly attempting to obtain the lock if force is non-zero. + Returns 1 on success, 0 on failure. + To release the lock the lockfile must be unlinked. + A unique temporary file named by appending characters to the lockfile name + is used; any pre-existing file of the same name is subject to deletion. + */ +static int acquire_lock_file( const char *lockfile, const int timeout, int force ) +{ + int fd, timed_out = 0; + int ret = 0; /* early exit returns failure */ + struct timespec pollint; + struct timeval start, end; + double elapsed; + struct stat statbuf; + + /* + (Re)create a unique file and check that it has one only link. + */ + char *linkfile = gen_unique_nfs_filename( lockfile ); + if( linkfile == NULL ) + { + goto done; + } + (void)unlink( linkfile ); + /* OK to not use CLO_EXEC here because fishd is single threaded */ + if( ( fd = open( linkfile, O_CREAT|O_RDONLY, 0600 ) ) == -1 ) + { + debug( 1, L"acquire_lock_file: open: %s", strerror( errno ) ); + goto done; + } + /* + Don't need to check exit status of close on read-only file descriptors + */ + close( fd ); + if( stat( linkfile, &statbuf ) != 0 ) + { + debug( 1, L"acquire_lock_file: stat: %s", strerror( errno ) ); + goto done; + } + if ( statbuf.st_nlink != 1 ) + { + debug( 1, L"acquire_lock_file: number of hardlinks on unique " + L"tmpfile is %d instead of 1.", (int)statbuf.st_nlink ); + goto done; + } + if( gettimeofday( &start, NULL ) != 0 ) + { + debug( 1, L"acquire_lock_file: gettimeofday: %s", strerror( errno ) ); + goto done; + } + end = start; + pollint.tv_sec = 0; + pollint.tv_nsec = LOCKPOLLINTERVAL * 1000000; + do + { + /* + Try to create a hard link to the unique file from the + lockfile. This will only succeed if the lockfile does not + already exist. It is guaranteed to provide race-free + semantics over NFS which the alternative of calling + open(O_EXCL|O_CREAT) on the lockfile is not. The lock + succeeds if the call to link returns 0 or the link count on + the unique file increases to 2. + */ + if( link( linkfile, lockfile ) == 0 || + ( stat( linkfile, &statbuf ) == 0 && + statbuf.st_nlink == 2 ) ) + { + /* Successful lock */ + ret = 1; + break; + } + elapsed = end.tv_sec + end.tv_usec/1000000.0 - + ( start.tv_sec + start.tv_usec/1000000.0 ); + /* + The check for elapsed < 0 is to deal with the unlikely event + that after the loop is entered the system time is set forward + past the loop's end time. This would otherwise result in a + (practically) infinite loop. + */ + if( timed_out || elapsed >= timeout || elapsed < 0 ) + { + if ( timed_out == 0 && force ) + { + /* + Timed out and force was specified - attempt to + remove stale lock and try a final time + */ + (void)unlink( lockfile ); + timed_out = 1; + continue; + } + else + { + /* + Timed out and final try was unsuccessful or + force was not specified + */ + debug( 1, L"acquire_lock_file: timed out " + L"trying to obtain lockfile %s using " + L"linkfile %s", lockfile, linkfile ); + break; + } + } + nanosleep( &pollint, NULL ); + } while( gettimeofday( &end, NULL ) == 0 ); +done: + /* The linkfile is not needed once the lockfile has been created */ + (void)unlink( linkfile ); + free( linkfile ); + return ret; +} + +/** Acquire the lock for the socket Returns the name of the lock file if successful or NULL if unable to obtain lock. @@ -502,6 +757,7 @@ static void load_or_save( int save) save?"saving":"loading", name ); + /* OK to not use CLO_EXEC here because fishd is single threaded */ fd = open( name, save?(O_CREAT | O_TRUNC | O_WRONLY):O_RDONLY, 0600); free( name ); |