1 /*-
2  * Copyright (c) 1992, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1992, 1993\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)cleanerd.c	8.1 (Berkeley) 06/04/93";
16 #endif /* not lint */
17 
18 #include <sys/param.h>
19 #include <sys/mount.h>
20 #include <sys/time.h>
21 
22 #include <ufs/ufs/dinode.h>
23 #include <ufs/lfs/lfs.h>
24 
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 
30 #include "clean.h"
31 char *special = "cleanerd";
32 int do_small = 0;
33 int do_mmap = 0;
34 struct cleaner_stats {
35 	int	blocks_read;
36 	int	blocks_written;
37 	int	segs_cleaned;
38 	int	segs_empty;
39 	int	segs_error;
40 } cleaner_stats;
41 
42 struct seglist {
43 	int sl_id;	/* segment number */
44 	int sl_cost; 	/* cleaning cost */
45 	char sl_empty;	/* is segment empty */
46 };
47 
48 struct tossstruct {
49 	struct lfs *lfs;
50 	int seg;
51 };
52 
53 /* function prototypes for system calls; not sure where they should go */
54 int	 lfs_segwait __P((fsid_t, struct timeval *));
55 int	 lfs_segclean __P((fsid_t, u_long));
56 int	 lfs_bmapv __P((fsid_t, BLOCK_INFO *, int));
57 int	 lfs_markv __P((fsid_t, BLOCK_INFO *, int));
58 
59 /* function prototypes */
60 int	 bi_tossold __P((const void *, const void *, const void *));
61 int	 choose_segments __P((FS_INFO *, struct seglist *,
62 	     int (*)(FS_INFO *, SEGUSE *)));
63 void	 clean_fs __P((FS_INFO	*, int (*)(FS_INFO *, SEGUSE *)));
64 int	 clean_loop __P((FS_INFO *));
65 int	 clean_segment __P((FS_INFO *, int));
66 int	 cost_benefit __P((FS_INFO *, SEGUSE *));
67 int	 cost_compare __P((const void *, const void *));
68 void	 sig_report __P((int));
69 
70 /*
71  * Cleaning Cost Functions:
72  *
73  * These return the cost of cleaning a segment.  The higher the cost value
74  * the better it is to clean the segment, so empty segments have the highest
75  * cost.  (It is probably better to think of this as a priority value
76  * instead).
77  *
78  * This is the cost-benefit policy simulated and described in Rosenblum's
79  * 1991 SOSP paper.
80  */
81 
82 int
83 cost_benefit(fsp, su)
84 	FS_INFO *fsp;		/* file system information */
85 	SEGUSE *su;
86 {
87 	struct lfs *lfsp;
88 	struct timeval t;
89 	int age;
90 	int live;
91 
92 	gettimeofday(&t, NULL);
93 
94 	live = su->su_nbytes;
95 	age = t.tv_sec < su->su_lastmod ? 0 : t.tv_sec - su->su_lastmod;
96 
97 	lfsp = &fsp->fi_lfs;
98 	if (live == 0)
99 		return (t.tv_sec * lblkno(lfsp, seg_size(lfsp)));
100 	else {
101 		/*
102 		 * from lfsSegUsage.c (Mendel's code).
103 		 * priority calculation is done using INTEGER arithmetic.
104 		 * sizes are in BLOCKS (that is why we use lblkno below).
105 		 * age is in seconds.
106 		 *
107 		 * priority = ((seg_size - live) * age) / (seg_size + live)
108 		 */
109 #ifdef VERBOSE
110 		if (live < 0 || live > seg_size(lfsp)) {
111 			err(0, "Bad segusage count: %d", live);
112 			live = 0;
113 		}
114 #endif
115 		return (lblkno(lfsp, seg_size(lfsp) - live) * age)
116 			/ lblkno(lfsp, seg_size(lfsp) + live);
117 	}
118 }
119 
120 int
121 main(argc, argv)
122 	int argc;
123 	char *argv[];
124 {
125 	FS_INFO	*fsp;
126 	struct statfs *lstatfsp;	/* file system stats */
127 	struct timeval timeout;		/* sleep timeout */
128 	fsid_t fsid;
129 	int i, nodaemon;
130 	int opt, cmd_err;
131 	char *fs_name;			/* name of filesystem to clean */
132 	extern int optind;
133 
134 	cmd_err = nodaemon = 0;
135 	while ((opt = getopt(argc, argv, "smd")) != EOF) {
136 		switch (opt) {
137 			case 's':	/* small writes */
138 				do_small = 1;
139 				break;
140 			case 'm':
141 				do_mmap = 1;
142 				break;
143 			case 'd':
144 				nodaemon = 1;
145 				break;
146 			default:
147 				++cmd_err;
148 		}
149 	}
150 	argc -= optind;
151 	argv += optind;
152 	if (cmd_err || (argc != 1))
153 		err(1, "usage: lfs_cleanerd [-smd] fs_name");
154 
155 	fs_name = argv[0];
156 
157 	signal (SIGINT, sig_report);
158 	signal (SIGUSR1, sig_report);
159 	signal (SIGUSR2, sig_report);
160 	if (fs_getmntinfo(&lstatfsp, fs_name, MOUNT_LFS) == 0) {
161 		/* didn't find the filesystem */
162 		err(1, "lfs_cleanerd: filesystem %s isn't an LFS!", fs_name);
163 	}
164 
165 	if (!nodaemon)	/* should we become a daemon, chdir to / & close fd's */
166 		if (daemon(0, 0) == -1)
167 			err(1, "lfs_cleanerd: couldn't become a daemon!");
168 
169 	timeout.tv_sec = 5*60; /* five minutes */
170 	timeout.tv_usec = 0;
171 	fsid.val[0] = 0;
172 	fsid.val[1] = 0;
173 
174 	for (fsp = get_fs_info(lstatfsp, do_mmap); ;
175 	    reread_fs_info(fsp, do_mmap)) {
176 		/*
177 		 * clean the filesystem, and, if it needed cleaning
178 		 * (i.e. it returned nonzero) try it again
179 		 * to make sure that some nasty process hasn't just
180 		 * filled the disk system up.
181 		 */
182 		if (clean_loop(fsp))
183 			continue;
184 
185 #ifdef VERBOSE
186 		(void)printf("Cleaner going to sleep.\n");
187 #endif
188 		if (lfs_segwait(fsid, &timeout) < 0)
189 			err(0, "lfs_segwait: returned error\n");
190 #ifdef VERBOSE
191 		(void)printf("Cleaner waking up.\n");
192 #endif
193 	}
194 }
195 
196 /* return the number of segments cleaned */
197 int
198 clean_loop(fsp)
199 	FS_INFO	*fsp;	/* file system information */
200 {
201 	double loadavg[MAXLOADS];
202 	time_t	now;
203 	u_long max_free_segs;
204 
205         /*
206 	 * Compute the maximum possible number of free segments, given the
207 	 * number of free blocks.
208 	 */
209 	max_free_segs = fsp->fi_statfsp->f_bfree / fsp->fi_lfs.lfs_ssize;
210 
211 	/*
212 	 * We will clean if there are not enough free blocks or total clean
213 	 * space is less than BUSY_LIM % of possible clean space.
214 	 */
215 	now = time((time_t *)NULL);
216 	if (fsp->fi_cip->clean < max_free_segs &&
217 	    (fsp->fi_cip->clean <= MIN_SEGS(&fsp->fi_lfs) ||
218 	    fsp->fi_cip->clean < max_free_segs * BUSY_LIM)) {
219 		printf("Cleaner Running  at %s (%d of %d segments available)\n",
220 		    ctime(&now), fsp->fi_cip->clean, max_free_segs);
221 		clean_fs(fsp, cost_benefit);
222 		return (1);
223 	} else {
224 	        /*
225 		 * We will also clean if the system is reasonably idle and
226 		 * the total clean space is less then IDLE_LIM % of possible
227 		 * clean space.
228 		 */
229 		if (getloadavg(loadavg, MAXLOADS) == -1) {
230 			perror("getloadavg: failed\n");
231 			return (-1);
232 		}
233 		if (loadavg[ONE_MIN] == 0.0 && loadavg[FIVE_MIN] &&
234 		    fsp->fi_cip->clean < max_free_segs * IDLE_LIM) {
235 		        clean_fs(fsp, cost_benefit);
236 			printf("Cleaner Running  at %s (system idle)\n",
237 			    ctime(&now));
238 			return (1);
239 		}
240 	}
241 	printf("Cleaner Not Running at %s\n", ctime(&now));
242 	return (0);
243 }
244 
245 
246 void
247 clean_fs(fsp, cost_func)
248 	FS_INFO	*fsp;	/* file system information */
249 	int (*cost_func) __P((FS_INFO *, SEGUSE *));
250 {
251 	struct seglist *segs, *sp;
252 	int i;
253 
254 	if ((segs = malloc(fsp->fi_lfs.lfs_nseg * sizeof(struct seglist)))
255 	    == NULL) {
256 		err(0, "malloc failed");
257 		return;
258 	}
259 	i = choose_segments(fsp, segs, cost_func);
260 #ifdef VERBOSE
261 	printf("clean_fs: found %d segments to clean in file system %s\n",
262 		i, fsp->fi_statfsp->f_mntonname);
263 	fflush(stdout);
264 #endif
265 	if (i)
266 		for (i = MIN(i, NUM_TO_CLEAN(fsp)), sp = segs; i-- ; ++sp) {
267 			if (clean_segment(fsp, sp->sl_id) < 0)
268 				perror("clean_segment failed");
269 			else if (lfs_segclean (fsp->fi_statfsp->f_fsid,
270 			    sp->sl_id) < 0)
271 				perror("lfs_segclean failed");
272 			printf("Completed cleaning segment %d\n", sp->sl_id);
273 		}
274 	free(segs);
275 }
276 
277 /*
278  * Segment with the highest priority get sorted to the beginning of the
279  * list.  This sort assumes that empty segments always have a higher
280  * cost/benefit than any utilized segment.
281  */
282 int
283 cost_compare(a, b)
284 	const void *a;
285 	const void *b;
286 {
287 	return (((struct seglist *)b)->sl_cost -
288 	    ((struct seglist *)a)->sl_cost);
289 }
290 
291 
292 /*
293  * Returns the number of segments to be cleaned with the elements of seglist
294  * filled in.
295  */
296 int
297 choose_segments(fsp, seglist, cost_func)
298 	FS_INFO *fsp;
299 	struct seglist *seglist;
300 	int (*cost_func) __P((FS_INFO *, SEGUSE *));
301 {
302 	struct lfs *lfsp;
303 	struct seglist *sp;
304 	SEGUSE *sup;
305 	int i, nsegs;
306 
307 	lfsp = &fsp->fi_lfs;
308 
309 #ifdef VERBOSE
310 	(void) printf("Entering choose_segments\n");
311 #endif
312 	dump_super(lfsp);
313 	dump_cleaner_info(fsp->fi_cip);
314 
315 	for (sp = seglist, i = 0; i < lfsp->lfs_nseg; ++i) {
316 		sup = SEGUSE_ENTRY(lfsp, fsp->fi_segusep, i);
317 		 PRINT_SEGUSE(sup, i);
318 		if (!(sup->su_flags & SEGUSE_DIRTY) ||
319 		    sup->su_flags & SEGUSE_ACTIVE)
320 			continue;
321 #ifdef VERBOSE
322 		(void) printf("\tchoosing segment %d\n", i);
323 #endif
324 		sp->sl_cost = (*cost_func)(fsp, sup);
325 		sp->sl_id = i;
326 		sp->sl_empty = sup->su_nbytes ? 0 : 1;
327 		++sp;
328 	}
329 	nsegs = sp - seglist;
330 	qsort(seglist, nsegs, sizeof(struct seglist), cost_compare);
331 #ifdef VERBOSE
332 	(void)printf("Returning %d segments\n", nsegs);
333 #endif
334 	return (nsegs);
335 }
336 
337 
338 int
339 clean_segment(fsp, id)
340 	FS_INFO *fsp;	/* file system information */
341 	int id;		/* segment number */
342 {
343 	BLOCK_INFO *block_array, *bp;
344 	SEGUSE *sp;
345 	struct lfs *lfsp;
346 	struct tossstruct t;
347 	caddr_t seg_buf;
348 	int num_blocks, maxblocks, clean_blocks;
349 
350 	lfsp = &fsp->fi_lfs;
351 	sp = SEGUSE_ENTRY(lfsp, fsp->fi_segusep, id);
352 
353 #ifdef VERBOSE
354 	(void) printf("cleaning segment %d: contains %lu bytes\n", id,
355 	    sp->su_nbytes);
356 	fflush(stdout);
357 #endif
358 	/* XXX could add debugging to verify that segment is really empty */
359 	if (sp->su_nbytes == sp->su_nsums * LFS_SUMMARY_SIZE) {
360 		++cleaner_stats.segs_empty;
361 		return (0);
362 	}
363 
364 	/* map the segment into a buffer */
365 	if (mmap_segment(fsp, id, &seg_buf, do_mmap) < 0) {
366 		err(0, "mmap_segment failed");
367 		++cleaner_stats.segs_error;
368 		return (-1);
369 	}
370 	/* get a list of blocks that are contained by the segment */
371 	if (lfs_segmapv(fsp, id, seg_buf, &block_array, &num_blocks) < 0) {
372 		err(0, "clean_segment: lfs_segmapv failed");
373 		++cleaner_stats.segs_error;
374 		return (-1);
375 	}
376 	cleaner_stats.blocks_read += fsp->fi_lfs.lfs_ssize;
377 
378 #ifdef VERBOSE
379 	(void) printf("lfs_segmapv returned %d blocks\n", num_blocks);
380 	fflush (stdout);
381 #endif
382 
383 	/* get the current disk address of blocks contained by the segment */
384 	if (lfs_bmapv(fsp->fi_statfsp->f_fsid, block_array, num_blocks) < 0) {
385 		perror("clean_segment: lfs_bmapv failed\n");
386 		++cleaner_stats.segs_error;
387 		return -1;
388 	}
389 
390 	/* Now toss any blocks not in the current segment */
391 	t.lfs = lfsp;
392 	t.seg = id;
393 	toss(block_array, &num_blocks, sizeof(BLOCK_INFO), bi_tossold, &t);
394 
395 	/* Check if last element should be tossed */
396 	if (num_blocks && bi_tossold(&t, block_array + num_blocks - 1, NULL))
397 		--num_blocks;
398 
399 #ifdef VERBOSE
400 	{
401 		BLOCK_INFO *_bip;
402 		u_long *lp;
403 		int i;
404 
405 		(void) printf("after bmapv still have %d blocks\n", num_blocks);
406 		fflush (stdout);
407 		if (num_blocks)
408 			printf("BLOCK INFOS\n");
409 		for (_bip = block_array, i=0; i < num_blocks; ++_bip, ++i) {
410 			PRINT_BINFO(_bip);
411 			lp = (u_long *)_bip->bi_bp;
412 		}
413 	}
414 #endif
415 	cleaner_stats.blocks_written += num_blocks;
416 	if (do_small)
417 		maxblocks = MAXPHYS / fsp->fi_lfs.lfs_bsize - 1;
418 	else
419 		maxblocks = num_blocks;
420 
421 	for (bp = block_array; num_blocks > 0; bp += clean_blocks) {
422 		clean_blocks = maxblocks < num_blocks ? maxblocks : num_blocks;
423 		if (lfs_markv(fsp->fi_statfsp->f_fsid, bp, clean_blocks) < 0 ) {
424 			err(0, "clean_segment: lfs_markv failed");
425 			++cleaner_stats.segs_error;
426 			return (-1);
427 		}
428 		num_blocks -= clean_blocks;
429 	}
430 
431 	free(block_array);
432 	munmap_segment (fsp, seg_buf, do_mmap);
433 	++cleaner_stats.segs_cleaned;
434 	return (0);
435 }
436 
437 
438 int
439 bi_tossold(client, a, b)
440 	const void *client;
441 	const void *a;
442 	const void *b;
443 {
444 	const struct tossstruct *t;
445 
446 	t = (struct tossstruct *)client;
447 
448 	return (((BLOCK_INFO *)a)->bi_daddr == LFS_UNUSED_DADDR ||
449 	    datosn(t->lfs, ((BLOCK_INFO *)a)->bi_daddr) != t->seg);
450 }
451 
452 void
453 sig_report(sig)
454 	int sig;
455 {
456 	printf ("lfs_cleanerd:\t%s%d\n\t\t%s%d\n\t\t%s%d\n\t\t%s%d\n\t\t%s%d\n",
457 		"blocks_read    ", cleaner_stats.blocks_read,
458 		"blocks_written ", cleaner_stats.blocks_written,
459 		"segs_cleaned   ", cleaner_stats.segs_cleaned,
460 		"segs_empty     ", cleaner_stats.segs_empty,
461 		"seg_error      ", cleaner_stats.segs_error);
462 	if (sig == SIGUSR2) {
463 		cleaner_stats.blocks_read = 0;
464 		cleaner_stats.blocks_written = 0;
465 		cleaner_stats.segs_cleaned = 0;
466 		cleaner_stats.segs_empty = 0;
467 		cleaner_stats.segs_error = 0;
468 	}
469 	if (sig == SIGINT)
470 		exit(0);
471 }
472