xref: /minix/minix/commands/cleantmp/cleantmp.c (revision 83133719)
1 /*	cleantmp 1.6 - clean out a tmp dir.		Author: Kees J. Bot
2  *								11 Apr 1991
3  */
4 #define nil 0
5 #include <sys/types.h>
6 #include <stdio.h>
7 #include <stddef.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <sys/stat.h>
11 #include <string.h>
12 #include <time.h>
13 #include <dirent.h>
14 #include <errno.h>
15 
16 #define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
17 #define arraylimit(a)	((a) + arraysize(a))
18 
19 #ifndef S_ISLNK
20 /* There were no symlinks in medieval times. */
21 #define lstat stat
22 #endif
23 
24 #ifndef DEBUG
25 #ifndef NDEBUG
26 #define NDEBUG
27 #endif
28 #endif
29 #include <assert.h>
30 
31 #define SEC_DAY	(24 * 3600L)	/* A full day in seconds */
32 #define DOTDAYS	14		/* Don't remove tmp/.* in at least 14 days. */
33 
34 void report(const char *label)
35 {
36 	fprintf(stderr, "cleantmp: %s: %s\n", label, strerror(errno));
37 }
38 
39 void fatal(const char *label)
40 {
41 	report(label);
42 	exit(1);
43 }
44 
45 void *alloc(size_t s)
46 {
47 	void *mem;
48 
49 	if ((mem= (void *) malloc(s)) == nil) fatal("");
50 	return mem;
51 }
52 
53 int force= 0;			/* Force remove all. */
54 int debug= 0;			/* Debug level. */
55 
56 void days2time(unsigned long days, time_t *retired, time_t *dotretired)
57 {
58 	struct tm *tm;
59 	time_t t;
60 
61 	time(&t);
62 
63 	tm= localtime(&t);
64 	tm->tm_hour= 0;
65 	tm->tm_min= 0;
66 	tm->tm_sec= 0;	/* Step back to midnight of this day. */
67 	t= mktime(tm);
68 
69 	if (t < (days - 1) * SEC_DAY) {
70 		*retired= *dotretired= 0;
71 	} else {
72 		*retired= t - (days - 1) * SEC_DAY;
73 		*dotretired= t - (DOTDAYS - 1) * SEC_DAY;
74 		if (*dotretired > *retired) *dotretired= *retired;
75 	}
76 	if (debug >= 2) fprintf(stderr, "Retired:    %s", ctime(retired));
77 	if (debug >= 2) fprintf(stderr, "Dotretired: %s", ctime(dotretired));
78 }
79 
80 /* Path name construction, addpath adds a component, delpath removes it.
81  * The string 'path' is used throughout the program as the file under
82  * examination.
83  */
84 
85 char *path;	/* Path name constructed in path[]. */
86 int plen= 0, pidx= 0;	/* Lenght/index for path[]. */
87 
88 void addpath(int *didx, const char *name)
89 /* Add a component to path. (name may also be a full path at the first call)
90  * The index where the current path ends is stored in *pdi.
91  */
92 {
93 	if (plen == 0) path= (char *) alloc((plen= 32) * sizeof(path[0]));
94 
95 	*didx= pidx;	/* Record point to go back to for delpath. */
96 
97 	if (pidx > 0 && path[pidx-1] != '/') path[pidx++]= '/';
98 
99 	do {
100 		if (*name != '/' || pidx == 0 || path[pidx-1] != '/') {
101 			if (pidx == plen &&
102 				(path= (char *) realloc((void *) path,
103 					(plen*= 2) * sizeof(path[0]))) == nil
104 			) fatal("");
105 			path[pidx++]= *name;
106 		}
107 	} while (*name++ != 0);
108 
109 	--pidx;		/* Put pidx back at the null.  The path[pidx++]= '/'
110 			 * statement will overwrite it at the next call.
111 			 */
112 	assert(pidx < plen);
113 }
114 
115 void delpath(int didx)
116 {
117 	assert(0 <= didx);
118 	assert(didx <= pidx);
119 	path[pidx= didx]= 0;
120 }
121 
122 struct file {
123 	struct file	*next;
124 	char		*name;
125 };
126 
127 struct file *listdir(void)
128 {
129 	DIR *dp;
130 	struct dirent *entry;
131 	struct file *first, **last= &first;
132 
133 	if ((dp= opendir(path)) == nil) {
134 		report(path);
135 		return nil;
136 	}
137 
138 	while ((entry= readdir(dp)) != nil) {
139 		struct file *new;
140 
141 		if (strcmp(entry->d_name, ".") == 0
142 			|| strcmp(entry->d_name, "..") == 0) continue;
143 
144 		new= (struct file *) alloc(sizeof(*new));
145 		new->name= (char *) alloc((size_t) strlen(entry->d_name) + 1);
146 		strcpy(new->name, entry->d_name);
147 
148 		*last= new;
149 		last= &new->next;
150 	}
151 	closedir(dp);
152 	*last= nil;
153 
154 	return first;
155 }
156 
157 struct file *shorten(struct file *list)
158 {
159 	struct file *junk;
160 
161 	assert(list != nil);
162 
163 	junk= list;
164 	list= list->next;
165 
166 	free((void *) junk->name);
167 	free((void *) junk);
168 
169 	return list;
170 }
171 
172 /* Hash list of files to ignore. */
173 struct file *ignore_list[1024];
174 size_t n_ignored= 0;
175 
176 unsigned ihash(const char *name)
177 /* A simple hashing function on a file name. */
178 {
179 	unsigned h= 0;
180 
181 	while (*name != 0) h= (h * 0x1111) + *name++;
182 
183 	return h & (arraysize(ignore_list) - 1);
184 }
185 
186 void do_ignore(int add, const char *name)
187 /* Add or remove a file to/from the list of files to ignore. */
188 {
189 	struct file **ipp, *ip;
190 
191 	ipp= &ignore_list[ihash(name)];
192 	while ((ip= *ipp) != nil) {
193 		if (strcmp(name, ip->name) <= 0) break;
194 		ipp= &ip->next;
195 	}
196 
197 	if (add) {
198 		ip= alloc(sizeof(*ip));
199 		ip->name= alloc((strlen(name) + 1) * sizeof(ip->name[0]));
200 		strcpy(ip->name, name);
201 		ip->next= *ipp;
202 		*ipp= ip;
203 		n_ignored++;
204 	} else {
205 		assert(ip != nil);
206 		*ipp= ip->next;
207 		free(ip->name);
208 		free(ip);
209 		n_ignored--;
210 	}
211 }
212 
213 int is_ignored(const char *name)
214 /* Is a file in the list of ignored files? */
215 {
216 	struct file *ip;
217 	int r;
218 
219 	ip= ignore_list[ihash(name)];
220 	while (ip != nil) {
221 		if ((r = strcmp(name, ip->name)) <= 0) return (r == 0);
222 		ip= ip->next;
223 	}
224 	return 0;
225 }
226 
227 #define is_ignored(name) (n_ignored > 0 && (is_ignored)(name))
228 
229 time_t retired, dotretired;
230 
231 enum level { TOP, DOWN };
232 
233 void cleandir(enum level level, time_t retired)
234 {
235 	struct file *list;
236 	struct stat st;
237 	time_t ret;
238 
239 	if (debug >= 2) fprintf(stderr, "Cleaning %s\n", path);
240 
241 	list= listdir();
242 
243 	while (list != nil) {
244 		int didx;
245 
246 		ret= (level == TOP && list->name[0] == '.') ?
247 			dotretired : retired;
248 			/* don't rm tmp/.* too soon. */
249 
250 		addpath(&didx, list->name);
251 
252 		if (is_ignored(path)) {
253 			if (debug >= 1) fprintf(stderr, "ignoring %s\n", path);
254 			do_ignore(0, path);
255 		} else
256 		if (is_ignored(list->name)) {
257 			if (debug >= 1) fprintf(stderr, "ignoring %s\n", path);
258 		} else
259 		if (lstat(path, &st) < 0) {
260 			report(path);
261 		} else
262 		if (S_ISDIR(st.st_mode)) {
263 			cleandir(DOWN, ret);
264 			if (force || st.st_mtime < ret) {
265 				if (debug < 3 && rmdir(path) < 0) {
266 					if (errno != ENOTEMPTY
267 							&& errno != EEXIST) {
268 						report(path);
269 					}
270 				} else {
271 					if (debug >= 1) {
272 						fprintf(stderr,
273 							"rmdir %s\n", path);
274 					}
275 				}
276 			}
277 		} else {
278 			if (force || (st.st_atime < ret
279 					&& st.st_mtime < ret
280 					&& st.st_ctime < ret)
281 			) {
282 				if (debug < 3 && unlink(path) < 0) {
283 					if (errno != ENOENT) {
284 						report(path);
285 					}
286 				} else {
287 					if (debug >= 1) {
288 						fprintf(stderr,
289 							"rm %s\n", path);
290 					}
291 				}
292 			}
293 		}
294 		delpath(didx);
295 		list= shorten(list);
296 	}
297 }
298 
299 void usage(void)
300 {
301 	fprintf(stderr,
302 	"Usage: cleantmp [-d[level]] [-i file ] ... -days|-f directory ...\n");
303 	exit(1);
304 }
305 
306 int main(int argc, char **argv)
307 {
308 	int i;
309 	unsigned long days;
310 
311 	i= 1;
312 	while (i < argc && argv[i][0] == '-') {
313 		char *opt= argv[i++] + 1;
314 
315 		if (opt[0] == '-' && opt[1] == 0) break;
316 
317 		if (opt[0] == 'd') {
318 			debug= 1;
319 			if (opt[1] != 0) debug= atoi(opt + 1);
320 		} else
321 		if (opt[0] == 'i') {
322 			if (*++opt == 0) {
323 				if (i == argc) usage();
324 				opt= argv[i++];
325 			}
326 			do_ignore(1, opt);
327 		} else
328 		if (opt[0] == 'f' && opt[1] == 0) {
329 			force= 1;
330 			days= 1;
331 		} else {
332 			char *end;
333 			days= strtoul(opt, &end, 10);
334 			if (*opt == 0 || *end != 0
335 				|| days == 0
336 				|| ((time_t) (days * SEC_DAY)) / SEC_DAY != days
337 			) {
338 				fprintf(stderr,
339 				"cleantmp: %s is not a valid number of days\n",
340 					opt);
341 				exit(1);
342 			}
343 		}
344 	}
345 	if (days == 0) usage();
346 
347 	days2time(days, &retired, &dotretired);
348 
349 	while (i < argc) {
350 		int didx;
351 
352 		if (argv[i][0] == 0) {
353 			fprintf(stderr, "cleantmp: empty pathname!\n");
354 			exit(1);
355 		}
356 		addpath(&didx, argv[i]);
357 		cleandir(TOP, retired);
358 		delpath(didx);
359 		assert(path[0] == 0);
360 		i++;
361 	}
362 	exit(0);
363 }
364