summaryrefslogtreecommitdiffstats
path: root/src/common/rmtree.c
blob: cd99d3f4719b260ec367a7d4dd1e067319f11a35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*-------------------------------------------------------------------------
 *
 * rmtree.c
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/common/rmtree.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <unistd.h>
#include <sys/stat.h>

#include "common/file_utils.h"

#ifndef FRONTEND
#include "storage/fd.h"
#define pg_log_warning(...) elog(WARNING, __VA_ARGS__)
#define LOG_LEVEL WARNING
#define OPENDIR(x) AllocateDir(x)
#define CLOSEDIR(x) FreeDir(x)
#else
#include "common/logging.h"
#define LOG_LEVEL PG_LOG_WARNING
#define OPENDIR(x) opendir(x)
#define CLOSEDIR(x) closedir(x)
#endif

/*
 *	rmtree
 *
 *	Delete a directory tree recursively.
 *	Assumes path points to a valid directory.
 *	Deletes everything under path.
 *	If rmtopdir is true deletes the directory too.
 *	Returns true if successful, false if there was any problem.
 *	(The details of the problem are reported already, so caller
 *	doesn't really have to say anything more, but most do.)
 */
bool
rmtree(const char *path, bool rmtopdir)
{
	char		pathbuf[MAXPGPATH];
	DIR		   *dir;
	struct dirent *de;
	bool		result = true;
	size_t		dirnames_size = 0;
	size_t		dirnames_capacity = 8;
	char	  **dirnames = palloc(sizeof(char *) * dirnames_capacity);

	dir = OPENDIR(path);
	if (dir == NULL)
	{
		pg_log_warning("could not open directory \"%s\": %m", path);
		return false;
	}

	while (errno = 0, (de = readdir(dir)))
	{
		if (strcmp(de->d_name, ".") == 0 ||
			strcmp(de->d_name, "..") == 0)
			continue;
		snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, de->d_name);
		switch (get_dirent_type(pathbuf, de, false, LOG_LEVEL))
		{
			case PGFILETYPE_ERROR:
				/* already logged, press on */
				break;
			case PGFILETYPE_DIR:

				/*
				 * Defer recursion until after we've closed this directory, to
				 * avoid using more than one file descriptor at a time.
				 */
				if (dirnames_size == dirnames_capacity)
				{
					dirnames = repalloc(dirnames,
										sizeof(char *) * dirnames_capacity * 2);
					dirnames_capacity *= 2;
				}
				dirnames[dirnames_size++] = pstrdup(pathbuf);
				break;
			default:
				if (unlink(pathbuf) != 0 && errno != ENOENT)
				{
					pg_log_warning("could not remove file \"%s\": %m", pathbuf);
					result = false;
				}
				break;
		}
	}

	if (errno != 0)
	{
		pg_log_warning("could not read directory \"%s\": %m", path);
		result = false;
	}

	CLOSEDIR(dir);

	/* Now recurse into the subdirectories we found. */
	for (size_t i = 0; i < dirnames_size; ++i)
	{
		if (!rmtree(dirnames[i], true))
			result = false;
		pfree(dirnames[i]);
	}

	if (rmtopdir)
	{
		if (rmdir(path) != 0)
		{
			pg_log_warning("could not remove directory \"%s\": %m", path);
			result = false;
		}
	}

	pfree(dirnames);

	return result;
}