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