Mercurial > notdcc
diff dcclib/mkstemp.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dcclib/mkstemp.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,618 @@ +/* Distributed Checksum Clearinghouse + * + * Copyright (c) 2008 by Rhyolite Software, LLC + * + * This agreement is not applicable to any entity which sells anti-spam + * solutions to others or provides an anti-spam solution as part of a + * security solution sold to other entities, or to a private network + * which employs the DCC or uses data provided by operation of the DCC + * but does not provide corresponding data to other users. + * + * Permission to use, copy, modify, and distribute this software without + * changes for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear in all + * copies and any distributed versions or copies are either unchanged + * or not called anything similar to "DCC" or "Distributed Checksum + * Clearinghouse". + * + * Parties not eligible to receive a license under this agreement can + * obtain a commercial license to use DCC by contacting Rhyolite Software + * at sales@rhyolite.com. + * + * A commercial license would be for Distributed Checksum and Reputation + * Clearinghouse software. That software includes additional features. This + * free license for Distributed ChecksumClearinghouse Software does not in any + * way grant permision to use Distributed Checksum and Reputation Clearinghouse + * software + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC + * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Rhyolite Software DCC 1.3.103-1.54 $Revision$ + */ + +#include "dcc_defs.h" +#include "dcc_paths.h" +#ifdef DCC_WIN32 +#include <direct.h> +#else +#include <dirent.h> +#endif + +static struct { + DCC_PATH path; /* has trailing '/' */ +} tmp[3]; +static int num_tmp_paths; + +static u_int32_t gen; + + +static const char * +mkstr(char str[DCC_MKSTEMP_LEN+1]) +{ + static const char digits[] = "0123456789" +#ifndef DCC_WIN32 + "abcdefghijklmnopqrstuvwxyz" +#endif + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + u_int32_t n; + int i; + + /* (re)start the generator and take a number + * There is a race among threads here to compute ++gen. + * However, it is rare and the worst that happens is that one thread + * will lose when it tries to create the file, just as it might lose + * to another process, and so need to come here again. */ + if (gen == 0) + gen = (getpid() << 16) + time(0); + n = ++gen; + + i = DCC_MKSTEMP_LEN; + str[i] = '\0'; + do { + str[--i] = digits[n % (LITZ(digits))]; + n /= (LITZ(digits)); + } while (i > 0); + return str; +} + + + +/* Some platforms have broken implementations of mkstemp() that generate + * only 32 different names. + * Given the main uses for mkstemp() in the DCC for log files in directories + * used only by the DCC, it is nice to try to make the names sort of + * sequential for a given dccm process. That means nothing more than putting + * the random bits in the most significant bits of the seed and using + * a small constant for the addition in the random number generator that is + * commonly used, and remembering the generator. */ +int /* -1 or FD of temporary file */ +dcc_mkstemp(DCC_EMSG emsg, + char *nm, int nm_len, /* put the generated name here */ + char *id, int id_len, /* put unique name string here */ + const char *old, /* try this name first */ + const char *tgtdir, /* directory or 0 for tmp_path */ + const char *p2, /* rest of the name */ + u_char close_del, /* 1=delete on close */ + int mode) /* add these mode bits to 0600 */ +{ + char str[DCC_MKSTEMP_LEN+1]; + int fd, limit, serrno; + const char *p1; + int i; +#ifdef DCC_WIN32 + HANDLE h; + DWORD flags; +#else + mode_t old_mask; +#endif + + if (!p2) + p2 = ""; + p1 = tgtdir; + if (!p1) { + if (!num_tmp_paths) + tmp_path_init(0, 0); + p1 = tmp[0].path; + } + + /* this loop should almost always need only a single pass */ + limit = 0; + for (;;) { + if (*p2 == '/') { /* avoid "//" */ + i = strlen(p1); + if (i > 0 && p1[i-1] == '/') + ++p2; + } + + if (old) { + i = snprintf(nm, nm_len, + "%s%s%."DCC_MKSTEMP_LEN_STR"s", + p1, p2, old); + } else { + i = snprintf(nm, nm_len, + "%s%s%s", + p1, p2, mkstr(str)); + } + if (i >= nm_len) { + dcc_pemsg(EX_SOFTWARE, emsg, + "temporary file name \"%s\" too big", nm); + *nm = '\0'; + return -1; + } + +#ifdef DCC_WIN32 + /* Given the uses of this function, if the temporary + * file is open, then it has been abandoned. All valid + * uses have the file renamed. Windows does not allow + * open files to be unlinked. FILE_FLAGS_DELETE_ON_CLOSE + * does not seem to be supported everywhere or it is + * unreliable. So open existing files without sharing + * but truncated. + * Use CreateFile() because the Borland and Microsoft + * open(), _open(), and _sopen() functions differ */ + flags = (close_del + ? FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE + : FILE_ATTRIBUTE_NORMAL); + h = CreateFile(nm, GENERIC_READ | GENERIC_WRITE, 0, + 0, CREATE_NEW, flags, 0); + if (h == INVALID_HANDLE_VALUE) + h = CreateFile(nm, GENERIC_READ | GENERIC_WRITE, 0, + 0, TRUNCATE_EXISTING, flags, 0); + if (h != INVALID_HANDLE_VALUE) { + fd = _open_osfhandle((long)h, 0); + if (fd >= 0) { + if (id && *id == '\0') + STRLCPY(id, str, id_len); + return fd; + } + dcc_pemsg(EX_IOERR, emsg, "_open_osfhandle(%s): %s", + nm, ERROR_STR()); + CloseHandle(h); + *nm = '\0'; + return -1; + } + serrno = errno; +#else + old_mask = umask(02); + fd = open(nm, O_RDWR | O_CREAT | O_EXCL, (mode & 0777) | 0600); + serrno = errno; + umask(old_mask); + if (fd >= 0) { + if (close_del + && 0 > unlink(nm)) { + dcc_pemsg(EX_IOERR, emsg, "unlink(%s): %s", + nm, ERROR_STR1(serrno)); + close(fd); + *nm = '\0'; + return -1; + } + if (id && *id == '\0') + STRLCPY(id, str, id_len); + return fd; + } +#endif + + if (errno != EEXIST) { + if (tgtdir != 0 || p1 == tmp[num_tmp_paths-1].path) { + dcc_pemsg(EX_IOERR, emsg, "open(%s): %s", + nm, ERROR_STR1(serrno)); + *nm = '\0'; + return -1; + } + p1 += sizeof(tmp[num_tmp_paths].path); + continue; + } + if (++limit > 100) { + dcc_pemsg(EX_IOERR, emsg, "open(%s) %d times: %s", + nm, limit, ERROR_STR1(EEXIST)); + *nm = '\0'; + return -1; + } + + if (old) { + old = 0; + } else { + /* There is already a file of the generated name. + * That implies that the current sequence of names + * has collided with an older sequence. + * So start another sequence. */ + gen = 0; + } + } +} + + + + +DCC_PATH dcc_main_logdir; +static LOG_MODE dcc_main_log_mode; + +/* see if a directory is a suitable for DCC client log files */ +static int /* 1=ok, -2=non-existent, -1=bad */ +stat_logdir(DCC_EMSG emsg, + const char *logdir, + struct stat *sb) +{ + int result; + + if (0 > stat(logdir, sb)) { + if (errno == ENOTDIR || errno == ENOENT) { + result = -2; + } else { + result = -1; + } + dcc_pemsg(EX_IOERR, emsg, "stat(log directory \"%s\"): %s", + logdir, ERROR_STR()); + return result; + } + + if (!S_ISDIR(sb->st_mode)) { + dcc_pemsg(EX_IOERR, emsg, "\"%s\" is not a log directory", + logdir); + return -1; + } + return 1; +} + + + +static void +tmpdir_ck(const char *dir, u_char complain) +{ +#ifndef DCC_WIN32 + struct stat sb; +#endif + char *path; + + if (!dir || *dir == '\0') { + if (complain) + dcc_error_msg("null -T temporary directory"); + return; + } + + path = tmp[num_tmp_paths].path; + if (!fnm2abs(path, dir, "") + || strlen(path) > ISZ(DCC_PATH)-DCC_MKSTEMP_LEN-2) { + if (complain) + dcc_error_msg("-T \"%s\" too long", dir); + path[0] = '\0'; + return; + } + ++num_tmp_paths; + +#ifndef DCC_WIN32 /* stat(directory) fails on Windows XP */ + if (complain) { + if (0 > stat(path, &sb)) { + dcc_error_msg("stat(temporary directory \"%s\"): %s", + path, ERROR_STR()); + return; + } + if (!S_ISDIR(sb.st_mode)) { + dcc_error_msg("\"%s\" is not a temporary directory", + path); + return; + } +#ifdef HAVE_EACCESS + if (0 > eaccess(path, W_OK)) { + dcc_error_msg("temporary directory \"%s\": %s", + path, ERROR_STR()); + return; + } +#endif + } +#endif /* !DCC_WIN32 */ + + strcat(path, "/"); +} + + + +/* get the temporary directory ready */ +void +tmp_path_init(const char *dir1, /* prefer this & complain if bad */ + const char *dir2) /* then this but no complaint */ +{ + if (dir1) + tmpdir_ck(dir1, 1); + + if (dir2) + tmpdir_ck(dir2, 0); + + tmpdir_ck(_PATH_TMP, 1); +} + + +/* Initialize the main DCC client log directory including parsing the + * prefix of subdirectory type. In case the path is relative, + * this should be called after the change to the DCC home directory */ +u_char /* 0=failed 1=ok */ +dcc_main_logdir_init(DCC_EMSG emsg, const char *arg) +{ + struct stat sb; + + if (!arg) + return 1; + + /* if the directory name starts with "D?", "H?", or "M?", + * automatically add subdirectories */ + if (!CLITCMP(arg, "D?")) { + dcc_main_log_mode = LOG_MODE_DAY; + arg += LITZ("D?"); + + } else if (!CLITCMP(arg, "H?")) { + dcc_main_log_mode = LOG_MODE_HOUR; + arg += LITZ("H?"); + + } else if (!CLITCMP(arg, "M?")) { + dcc_main_log_mode = LOG_MODE_MINUTE; + arg += LITZ("M?"); + + } else { + dcc_main_log_mode = LOG_MODE_FLAT; + } + + if (!fnm2rel(dcc_main_logdir, arg, 0)) { + dcc_pemsg(EX_DATAERR, emsg, "bad log directry \"%s\"", arg); + return 0; + } + + return stat_logdir(emsg, dcc_main_logdir, &sb) > 0; +} + + + +static u_char +mklogsubdir(DCC_EMSG emsg, + DCC_PATH dir, /* put directory name here */ + struct stat *dir_sb, + const char *grandparent, + const char *parent, + const struct stat *parent_sb, + const char *pat, u_int dirnum) +{ + int stat_result; + int i; + + i = snprintf(dir, sizeof(DCC_PATH), pat, parent, dirnum); + if (i >= ISZ(DCC_PATH)) { + dcc_pemsg(EX_DATAERR, emsg, "long log directry name \"%s\"", + grandparent); + return 0; + } + + /* see if it already exists */ + stat_result = stat_logdir(emsg, dir, dir_sb); + if (stat_result == -1) + return 0; + +#ifdef DCC_WIN32 + if (stat_result == -2 && 0 > mkdir(dir)) { + dcc_pemsg(EX_IOERR, emsg, "mkdir(%s): %s", + dir, ERROR_STR()); + return 0; + } + return 1; +#else + if (stat_result == -2) { + mode_t old_mask; + + old_mask = umask(02); + if (0 > mkdir(dir, (parent_sb->st_mode & 0775) | 0700)) { + dcc_pemsg(EX_IOERR, emsg, "mkdir(%s): %s", + dir, ERROR_STR()); + umask(old_mask); + return 0; + } + umask(old_mask); + stat_result = stat_logdir(emsg, dir, dir_sb); + if (stat_result < 0) + return 0; + } + /* set GID if necessary */ + if (dir_sb->st_gid != parent_sb->st_gid) { + if (0 > chown(dir, dir_sb->st_uid, parent_sb->st_gid)) { + /* this is expected to be needed and to work + * only for root */ + if (errno == EPERM) + return 1; + dcc_pemsg(EX_IOERR, emsg, "chown(%s,%d,%d): %s", + dir, (int)dir_sb->st_uid, + (int)parent_sb->st_gid, + ERROR_STR()); + return 0; + } + dir_sb->st_gid = parent_sb->st_gid; + } + return 1; +#endif /* DCC_WIN32 */ +} + + + +/* create and open a DCC client log file */ +int /* fd -1=no directory -2=serious */ +dcc_log_open(DCC_EMSG emsg, + DCC_PATH log_path, /* put the log file name here */ + char *id, int id_len, /* put log ID string here */ + const char *old, /* try this name first */ + const char *logdir, + const char *prefix, /* DCC_*_LOG_PREFIX */ + LOG_MODE log_mode) +{ + time_t now; + struct tm tm; + DCC_PATH dir_a, dir_b; + struct stat sb_a, sb_b, sb_f; + const struct stat *sb; + int stat_result, fd; + + log_path[0] = '\0'; + + stat_result = stat_logdir(emsg, logdir, &sb_b); + if (stat_result < 0) + return stat_result; + + if (log_mode == LOG_MODE_FLAT) { + sb = &sb_b; + + } else { + now = time(0); + dcc_localtime(now, &tm); + + if (!mklogsubdir(emsg, dir_a, &sb_a, + logdir, logdir, &sb_b, + "%s/%03d", tm.tm_yday+1)) + return -2; + + if (log_mode == LOG_MODE_DAY) { + logdir = dir_a; + sb = &sb_a; + } else { + if (!mklogsubdir(emsg, dir_b, &sb_b, + logdir, dir_a, &sb_a, + "%s/%02d", tm.tm_hour)) + return -2; + + if (log_mode == LOG_MODE_HOUR) { + logdir = dir_b; + sb = &sb_b; + } else { + if (!mklogsubdir(emsg, dir_a, &sb_a, + logdir, dir_b, &sb_b, + "%s/%02d", tm.tm_min)) + return -2; + logdir = dir_a; + sb = &sb_a; + } + } + } + + fd = dcc_mkstemp(emsg, log_path, sizeof(DCC_PATH), id, id_len, old, + logdir, prefix, 0, sb->st_mode & 0640); + if (fd < 0) + return -2; + + /* give it the right GID */ + if (0 > fstat(fd, &sb_f)) { + dcc_pemsg(EX_IOERR, emsg, "fstat(%s): %s", + log_path, ERROR_STR()); + close(fd); + return -2; + } +#ifndef DCC_WIN32 + if (sb->st_gid != sb_f.st_gid + && 0 > fchown(fd, sb_f.st_uid, sb->st_gid) + && errno != EPERM) { + /* this is expected to be needed and to work only for root */ + dcc_pemsg(EX_IOERR, emsg, "chown(%s,%d,%d): %s", + log_path, (int)sb_f.st_uid, (int)sb->st_gid, + ERROR_STR()); + close(fd); + return -2; + } +#endif + return fd; +} + + + +/* create and open a main DCC client log file */ +int /* fd -1=no directory -2=serious */ +dcc_main_log_open(DCC_EMSG emsg, + DCC_PATH log_path, /* put the log file name here */ + char *id, int id_len) /* put log ID string here */ +{ + return dcc_log_open(emsg, log_path, id, id_len, 0, + dcc_main_logdir, DCC_TMP_LOG_PREFIX, + dcc_main_log_mode); +} + + +/* rename a DCC client log file to a unique name */ +u_char +dcc_log_keep(DCC_EMSG emsg, DCC_PATH cur_path) +{ + DCC_PATH new_path; + char str[DCC_MKSTEMP_LEN+1]; + int path_len; + int limit; + + path_len = strlen(cur_path); + if (path_len < LITZ(DCC_TMP_LOG_PREFIX)+DCC_MKSTEMP_LEN) + dcc_logbad(EX_SOFTWARE, + "dcc_log_keep(%s): path too short", cur_path); + if (path_len >= ISZ(new_path)) + dcc_logbad(EX_SOFTWARE, + "dcc_log_keep(%s): path too long", cur_path); + + /* start by trying the current name with "tmp" replaced by "msg" */ + memcpy(new_path, cur_path, path_len+1); + memcpy(new_path+(path_len - (LITZ(DCC_TMP_LOG_PREFIX)+DCC_MKSTEMP_LEN)), + DCC_FIN_LOG_PREFIX, LITZ(DCC_FIN_LOG_PREFIX)); + limit = 0; + for (;;) { +#ifdef DCC_WIN32 + /* Windows does not have hard links */ + if (!rename(cur_path, new_path)) { + memcpy(cur_path, new_path, path_len); + return 1; + } + if (limit > 100 || errno != EACCES) { + dcc_pemsg(EX_IOERR, emsg, "rename(%s,%s) #%d: %s", + cur_path, new_path, limit, ERROR_STR()); + return 0; + } +#else + /* rename() on some UNIX systems deletes a pre-existing + * target, so we must use link() and unlink() */ + if (link(cur_path, new_path) >= 0) { + /* we made the link, so unlink the old name */ + if (unlink(cur_path) < 0) { + dcc_pemsg(EX_IOERR, emsg, "unlink(%s): %s", + cur_path, ERROR_STR()); + return 0; + } + memcpy(cur_path, new_path, path_len); + return 1; + } + if (limit > 100 || errno != EEXIST) { + dcc_pemsg(EX_IOERR, emsg, "link(%s,%s): %s", + cur_path, new_path, ERROR_STR()); + return 0; + } +#endif + + /* look for another sequence of names + * if our new choice for a name was taken */ + if (++limit > 1) + gen = 0; + memcpy(new_path+path_len-DCC_MKSTEMP_LEN, + mkstr(str), DCC_MKSTEMP_LEN); + } +} + + + +u_char +dcc_log_close(DCC_EMSG emsg, const char *nm, int fd, + const struct timeval *ldate) +{ + DCC_PATH path; + u_char result; + + result = dcc_set_mtime(emsg, nm, fd, ldate); + if (0 > close(fd) + && result) { + dcc_pemsg(EX_IOERR, emsg,"close(%s): %s", + fnm2abs_err(path, nm), ERROR_STR()); + result = 0; + } + return result; +}