Browse Source

Helper routines for saving errno in error paths.

Rusty Russell 18 years ago
parent
commit
36ef9b2c28
4 changed files with 153 additions and 0 deletions
  1. 54 0
      noerr/_info.c
  2. 29 0
      noerr/noerr.c
  3. 22 0
      noerr/noerr.h
  4. 48 0
      noerr/test/run.c

+ 54 - 0
noerr/_info.c

@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+/**
+ * noerr - routines for cleaning up without blatting errno
+ *
+ * It is a good idea to follow the standard C convention of setting errno in
+ * your own helper functions.  Unfortunately, care must be taken in the error
+ * paths as most standard functions can (and do) overwrite errno, even if they
+ * succeed.
+ *
+ * Example:
+ *	#include <sys/types.h>
+ *	#include <sys/stat.h>
+ *	#include <fcntl.h>
+ *
+ *	bool write_string_to_file(const char *file, const char *string)
+ *	{
+ *		int ret, fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0600);
+ *		if (fd < 0)
+ *			return false;
+ *		ret = write(fd, string, strlen(string));
+ *		if (ret < 0) {
+ *			// Preserve errno from write above.
+ *			close_noerr(fd);
+ *			unlink_noerr(file);
+ *			return false;
+ *		}
+ *		if (close(fd) != 0) {
+ *			// Again, preserve errno.
+ *			unlink_noerr(file);
+ *			return false;
+ *		}
+ *		// A short write means out of space.
+ *		if (ret < strlen(string)) {
+ *			unlink(file);
+ *			errno = ENOSPC;
+ *			return false;
+ *		}
+ *		return true;
+ *	}
+ */
+int main(int argc, char *argv[])
+{
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0)
+		/* Nothing. */
+		return 0;
+
+	return 1;
+}

+ 29 - 0
noerr/noerr.c

@@ -0,0 +1,29 @@
+#include "noerr.h"
+#include <unistd.h>
+#include <errno.h>
+
+int close_noerr(int fd)
+{
+	int saved_errno = errno, ret;
+
+	if (close(fd) != 0)
+		ret = errno;
+	else
+		ret = 0;
+
+	errno = saved_errno;
+	return ret;
+}
+
+int unlink_noerr(const char *pathname)
+{
+	int saved_errno = errno, ret;
+
+	if (unlink(pathname) != 0)
+		ret = errno;
+	else
+		ret = 0;
+
+	errno = saved_errno;
+	return ret;
+}

+ 22 - 0
noerr/noerr.h

@@ -0,0 +1,22 @@
+#ifndef NOERR_H
+#define NOERR_H
+
+/**
+ * close_noerr - close without stomping errno.
+ * @fd: the file descriptor to close.
+ *
+ * errno is saved and restored across the call to close: if an error occurs,
+ * the resulting (non-zero) errno is returned.
+ */
+int close_noerr(int fd);
+
+/**
+ * unlink_noerr - unlink a file without stomping errno.
+ * @pathname: the path to unlink.
+ *
+ * errno is saved and restored across the call to unlink: if an error occurs,
+ * the resulting (non-zero) errno is returned.
+ */
+int unlink_noerr(const char *pathname);
+
+#endif /* NOERR_H */

+ 48 - 0
noerr/test/run.c

@@ -0,0 +1,48 @@
+#include "noerr/noerr.h"
+#include "tap.h"
+#include "noerr/noerr.c"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <assert.h>
+
+int main(int argc, char *argv[])
+{
+	/* tempnam(3) is generally a bad idea, but OK here. */
+	char *name = tempnam(NULL, "noerr");
+	int fd;
+
+	plan_tests(12);
+	/* Should fail to unlink. */
+	ok1(unlink(name) != 0);
+	ok1(errno == ENOENT);
+
+	/* This one should not set errno. */
+	errno = 100;
+	ok1(unlink_noerr(name) == ENOENT);
+	ok1(errno == 100);
+
+	/* Should fail to close. */
+	ok1(close(-1) != 0);
+	ok1(errno == EBADF);
+
+	/* This one should not set errno. */
+	errno = 100;
+	ok1(close_noerr(-1) == EBADF);
+	ok1(errno == 100);
+
+	/* Test successful close/unlink doesn't hit errno either. */
+	fd = open(name, O_WRONLY|O_CREAT|O_EXCL, 0600);
+	assert(fd >= 0);
+
+	errno = 100;
+	ok1(close_noerr(fd) == 0);
+	ok1(errno == 100);
+
+	errno = 100;
+	ok1(unlink_noerr(name) == 0);
+	ok1(errno == 100);
+
+	return exit_status();
+}