Mercurial > notdcc
view 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 source
/* 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; }