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;
+}