xref: /onnv-gate/usr/src/cmd/filebench/common/fileset.c (revision 5184:da60d2b4a9e2)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 
29 #include <fcntl.h>
30 #include <pthread.h>
31 #include <errno.h>
32 #include <math.h>
33 #include <libgen.h>
34 #include <sys/mman.h>
35 #include "fileset.h"
36 #include "filebench.h"
37 #include "gamma_dist.h"
38 
39 /*
40  * File sets, of type fileset_t, are entities which contain
41  * information about collections of files and subdirectories in Filebench.
42  * The fileset, once populated, consists of a tree of fileset entries of
43  * type filesetentry_t which specify files and directories.  The fileset
44  * is rooted in a directory specified by fs_path, and once the populated
45  * fileset has been created, has a tree of directories and files
46  * corresponding to the fileset's filesetentry tree.
47  */
48 
49 /*
50  * Removes the last file or directory name from a pathname.
51  * Basically removes characters from the end of the path by
52  * setting them to \0 until a forward slash '/' is
53  * encountered. It also removes the forward slash.
54  */
55 static char *
56 trunc_dirname(char *dir)
57 {
58 	char *s = dir + strlen(dir);
59 
60 	while (s != dir) {
61 		int c = *s;
62 
63 		*s = 0;
64 		if (c == '/')
65 			break;
66 		s--;
67 	}
68 	return (dir);
69 }
70 
71 /*
72  * Prints a list of allowed options and how to specify them.
73  */
74 void
75 fileset_usage(void)
76 {
77 	(void) fprintf(stderr, "define fileset name=<name>,path=<pathname>,"
78 	    "entries=<number>\n");
79 	(void) fprintf(stderr, "		        [,dirwidth=[width]\n");
80 	(void) fprintf(stderr, "		        [,dirgamma=[100-10000] "
81 	    "(Gamma * 1000)\n");
82 	(void) fprintf(stderr,
83 	    "		        [,sizegamma=[100-10000] (Gamma * 1000)\n");
84 	(void) fprintf(stderr,
85 	    "		        [,prealloc=[percent]]\n");
86 	(void) fprintf(stderr, "		        [,reuse]\n");
87 	(void) fprintf(stderr, "\n");
88 }
89 
90 /*
91  * Frees up memory mapped file region of supplied size. The
92  * file descriptor "fd" indicates which memory mapped file.
93  * If successful, returns 0. Otherwise returns -1 if "size"
94  * is zero, or -1 times the number of times msync() failed.
95  */
96 static int
97 fileset_freemem(int fd, off64_t size)
98 {
99 	off64_t left;
100 	int ret = 0;
101 
102 	for (left = size; left > 0; left -= MMAP_SIZE) {
103 		off64_t thismapsize;
104 		caddr_t addr;
105 
106 		thismapsize = MIN(MMAP_SIZE, left);
107 		addr = mmap64(0, thismapsize, PROT_READ|PROT_WRITE,
108 		    MAP_SHARED, fd, size - left);
109 		ret += msync(addr, thismapsize, MS_INVALIDATE);
110 		(void) munmap(addr, thismapsize);
111 	}
112 	return (ret);
113 }
114 
115 /*
116  * Creates a path string from the filesetentry_t "*entry"
117  * and all of its parent's path names. The resulting path
118  * is a concatination of all the individual parent paths.
119  * Allocates memory for the path string and returns a
120  * pointer to it.
121  */
122 char *
123 fileset_resolvepath(filesetentry_t *entry)
124 {
125 	filesetentry_t *fsep = entry;
126 	char path[MAXPATHLEN];
127 	char pathtmp[MAXPATHLEN];
128 	char *s;
129 
130 	*path = 0;
131 	while (fsep->fse_parent) {
132 		(void) strcpy(pathtmp, "/");
133 		(void) strcat(pathtmp, fsep->fse_path);
134 		(void) strcat(pathtmp, path);
135 		(void) strcpy(path, pathtmp);
136 		fsep = fsep->fse_parent;
137 	}
138 
139 	s = malloc(strlen(path) + 1);
140 	(void) strcpy(s, path);
141 	return (s);
142 }
143 
144 /*
145  * Creates multiple nested directories as required by the
146  * supplied path. Starts at the end of the path, creating
147  * a list of directories to mkdir, up to the root of the
148  * path, then mkdirs them one at a time from the root on down.
149  */
150 static int
151 fileset_mkdir(char *path, int mode)
152 {
153 	char *p;
154 	char *dirs[65536];
155 	int i = 0;
156 
157 	if ((p = strdup(path)) == NULL)
158 		goto null_str;
159 
160 	/*
161 	 * Fill an array of subdirectory path names until either we
162 	 * reach the root or encounter an already existing subdirectory
163 	 */
164 	/* CONSTCOND */
165 	while (1) {
166 		struct stat64 sb;
167 
168 		if (stat64(p, &sb) == 0)
169 			break;
170 		if (strlen(p) < 3)
171 			break;
172 		if ((dirs[i] = strdup(p)) == NULL) {
173 			free(p);
174 			goto null_str;
175 		}
176 
177 		(void) trunc_dirname(p);
178 		i++;
179 	}
180 
181 	/* Make the directories, from closest to root downwards. */
182 	for (--i; i >= 0; i--) {
183 		(void) mkdir(dirs[i], mode);
184 		free(dirs[i]);
185 	}
186 
187 	free(p);
188 	return (0);
189 
190 null_str:
191 	/* clean up */
192 	for (--i; i >= 0; i--)
193 		free(dirs[i]);
194 
195 	filebench_log(LOG_ERROR,
196 	    "Failed to create directory path %s: Out of memory", path);
197 
198 	return (-1);
199 }
200 
201 
202 /*
203  * First creates the parent directories of the file using
204  * fileset_mkdir(). Then Optionally sets the O_DSYNC flag
205  * and opens the file with open64(). It unlocks the fileset
206  * entry lock, sets the DIRECTIO_ON or DIRECTIO_OFF flags
207  * as requested, and returns the file descriptor integer
208  * for the opened file.
209  */
210 int
211 fileset_openfile(fileset_t *fileset,
212     filesetentry_t *entry, int flag, int mode, int attrs)
213 {
214 	char path[MAXPATHLEN];
215 	char dir[MAXPATHLEN];
216 	char *pathtmp;
217 	struct stat64 sb;
218 	int fd;
219 	int open_attrs = 0;
220 
221 	*path = 0;
222 	(void) strcpy(path, *fileset->fs_path);
223 	(void) strcat(path, "/");
224 	(void) strcat(path, fileset->fs_name);
225 	pathtmp = fileset_resolvepath(entry);
226 	(void) strcat(path, pathtmp);
227 	(void) strcpy(dir, path);
228 	free(pathtmp);
229 	(void) trunc_dirname(dir);
230 
231 	/* If we are going to create a file, create the parent dirs */
232 	if ((flag & O_CREAT) && (stat64(dir, &sb) != 0)) {
233 		if (fileset_mkdir(dir, 0755) == -1)
234 			return (-1);
235 	}
236 
237 	if (flag & O_CREAT)
238 		entry->fse_flags |= FSE_EXISTS;
239 
240 	if (attrs & FLOW_ATTR_DSYNC) {
241 #ifdef sun
242 		open_attrs |= O_DSYNC;
243 #else
244 		open_attrs |= O_FSYNC;
245 #endif
246 	}
247 
248 	if ((fd = open64(path, flag | open_attrs, mode)) < 0) {
249 		filebench_log(LOG_ERROR,
250 		    "Failed to open file %s: %s",
251 		    path, strerror(errno));
252 		(void) ipc_mutex_unlock(&entry->fse_lock);
253 		return (-1);
254 	}
255 	(void) ipc_mutex_unlock(&entry->fse_lock);
256 
257 #ifdef sun
258 	if (attrs & FLOW_ATTR_DIRECTIO)
259 		(void) directio(fd, DIRECTIO_ON);
260 	else
261 		(void) directio(fd, DIRECTIO_OFF);
262 #endif
263 
264 	return (fd);
265 }
266 
267 
268 /*
269  * Selects a fileset entry from a fileset. If the
270  * FILESET_PICKDIR flag is set it will pick a directory
271  * entry, otherwise a file entry. The FILESET_PICKRESET
272  * flag will cause it to reset the free list to the
273  * overall list (file or directory). The FILESET_PICKUNIQUE
274  * flag will take an entry off of one of the free (unused)
275  * lists (file or directory), otherwise the entry will be
276  * picked off of one of the rotor lists (file or directory).
277  * The FILESET_PICKEXISTS will insure that only extant
278  * (FSE_EXISTS) state files are selected, while
279  * FILESET_PICKNOEXIST insures that only non extant
280  * (not FSE_EXISTS) state files are selected.
281  */
282 filesetentry_t *
283 fileset_pick(fileset_t *fileset, int flags, int tid)
284 {
285 	filesetentry_t *entry = NULL;
286 	filesetentry_t *first = NULL;
287 
288 	(void) ipc_mutex_lock(&filebench_shm->fileset_lock);
289 
290 	while (entry == NULL) {
291 
292 		if ((flags & FILESET_PICKDIR) && (flags & FILESET_PICKRESET)) {
293 			entry = fileset->fs_dirlist;
294 			while (entry) {
295 				entry->fse_flags |= FSE_FREE;
296 				entry = entry->fse_dirnext;
297 			}
298 			fileset->fs_dirfree = fileset->fs_dirlist;
299 		}
300 
301 		if (!(flags & FILESET_PICKDIR) && (flags & FILESET_PICKRESET)) {
302 			entry = fileset->fs_filelist;
303 			while (entry) {
304 				entry->fse_flags |= FSE_FREE;
305 				entry = entry->fse_filenext;
306 			}
307 			fileset->fs_filefree = fileset->fs_filelist;
308 		}
309 
310 		if (flags & FILESET_PICKUNIQUE) {
311 			if (flags & FILESET_PICKDIR) {
312 				entry = fileset->fs_dirfree;
313 				if (entry == NULL)
314 					goto empty;
315 				fileset->fs_dirfree = entry->fse_dirnext;
316 			} else {
317 				entry = fileset->fs_filefree;
318 				if (entry == NULL)
319 					goto empty;
320 				fileset->fs_filefree = entry->fse_filenext;
321 			}
322 			entry->fse_flags &= ~FSE_FREE;
323 		} else {
324 			if (flags & FILESET_PICKDIR) {
325 				entry = fileset->fs_dirrotor;
326 				if (entry == NULL)
327 				fileset->fs_dirrotor =
328 				    entry = fileset->fs_dirlist;
329 				fileset->fs_dirrotor = entry->fse_dirnext;
330 			} else {
331 				entry = fileset->fs_filerotor[tid];
332 				if (entry == NULL)
333 					fileset->fs_filerotor[tid] =
334 					    entry = fileset->fs_filelist;
335 				fileset->fs_filerotor[tid] =
336 				    entry->fse_filenext;
337 			}
338 		}
339 
340 		if (first == entry)
341 			goto empty;
342 
343 		if (first == NULL)
344 			first = entry;
345 
346 		/* Return locked entry */
347 		(void) ipc_mutex_lock(&entry->fse_lock);
348 
349 		/* If we ask for an existing file, go round again */
350 		if ((flags & FILESET_PICKEXISTS) &&
351 		    !(entry->fse_flags & FSE_EXISTS)) {
352 			(void) ipc_mutex_unlock(&entry->fse_lock);
353 			entry = NULL;
354 		}
355 
356 		/* If we ask for not an existing file, go round again */
357 		if ((flags & FILESET_PICKNOEXIST) &&
358 		    (entry->fse_flags & FSE_EXISTS)) {
359 			(void) ipc_mutex_unlock(&entry->fse_lock);
360 			entry = NULL;
361 		}
362 	}
363 
364 	(void) ipc_mutex_unlock(&filebench_shm->fileset_lock);
365 	filebench_log(LOG_DEBUG_SCRIPT, "Picked file %s", entry->fse_path);
366 	return (entry);
367 
368 empty:
369 	(void) ipc_mutex_unlock(&filebench_shm->fileset_lock);
370 	return (NULL);
371 }
372 
373 /*
374  * Given a fileset "fileset", create the associated files as
375  * specified in the attributes of the fileset. The fileset is
376  * rooted in a directory whose pathname is in fs_path. If the
377  * directory exists, meaning that there is already a fileset,
378  * and the fs_reuse attribute is false, then remove it and all
379  * its contained files and subdirectories. Next, the routine
380  * creates a root directory for the fileset. All the file type
381  * filesetentries are cycled through creating as needed
382  * their containing subdirectory trees in the filesystem and
383  * creating actual files for fs_preallocpercent of them. The
384  * created files are filled with fse_size bytes of unitialized
385  * data. The routine returns -1 on errors, 0 on success.
386  */
387 static int
388 fileset_create(fileset_t *fileset)
389 {
390 	filesetentry_t *entry;
391 	char path[MAXPATHLEN];
392 	char *buf;
393 	struct stat64 sb;
394 	int pickflags = FILESET_PICKUNIQUE | FILESET_PICKRESET;
395 	hrtime_t start = gethrtime();
396 	int preallocated = 0;
397 	int reusing = 0;
398 
399 	if ((buf = (char *)malloc(FILE_ALLOC_BLOCK)) == NULL)
400 		return (-1);
401 
402 	if (*fileset->fs_path == NULL) {
403 		filebench_log(LOG_ERROR, "Fileset path not set");
404 		return (-1);
405 	}
406 
407 	/* XXX Add check to see if there is enough space */
408 
409 	/* Remove existing */
410 	(void) strcpy(path, *fileset->fs_path);
411 	(void) strcat(path, "/");
412 	(void) strcat(path, fileset->fs_name);
413 	if ((stat64(path, &sb) == 0) && (strlen(path) > 3) &&
414 	    (strlen(*fileset->fs_path) > 2)) {
415 		if (!integer_isset(fileset->fs_reuse)) {
416 			char cmd[MAXPATHLEN];
417 
418 			(void) snprintf(cmd, sizeof (cmd), "rm -rf %s", path);
419 			(void) system(cmd);
420 			filebench_log(LOG_VERBOSE,
421 			    "Removed any existing fileset %s in %d seconds",
422 			    fileset->fs_name,
423 			    ((gethrtime() - start) / 1000000000) + 1);
424 		} else {
425 			/* we are re-using */
426 			reusing = 1;
427 			filebench_log(LOG_VERBOSE,
428 			    "Re-using fileset %s on %s file system.",
429 			    fileset->fs_name, sb.st_fstype);
430 		}
431 	}
432 	(void) mkdir(path, 0755);
433 
434 	start = gethrtime();
435 
436 	filebench_log(LOG_VERBOSE, "Creating fileset %s...",
437 	    fileset->fs_name);
438 
439 	while (entry = fileset_pick(fileset, pickflags, 0)) {
440 		char dir[MAXPATHLEN];
441 		char *pathtmp;
442 		off64_t seek;
443 		int fd;
444 		int randno;
445 
446 		pickflags = FILESET_PICKUNIQUE;
447 
448 		entry->fse_flags &= ~FSE_EXISTS;
449 
450 		if (!integer_isset(fileset->fs_prealloc)) {
451 			(void) ipc_mutex_unlock(&entry->fse_lock);
452 			continue;
453 		}
454 
455 		*path = 0;
456 		(void) strcpy(path, *fileset->fs_path);
457 		(void) strcat(path, "/");
458 		(void) strcat(path, fileset->fs_name);
459 		pathtmp = fileset_resolvepath(entry);
460 		(void) strcat(path, pathtmp);
461 		(void) strcpy(dir, path);
462 		free(pathtmp);
463 
464 		(void) trunc_dirname(dir);
465 
466 		if (stat64(dir, &sb) != 0) {
467 			if (fileset_mkdir(dir, 0775) == -1) {
468 				(void) ipc_mutex_unlock(&entry->fse_lock);
469 				return (-1);
470 			}
471 		}
472 
473 		randno = ((RAND_MAX * (100 - *(fileset->fs_preallocpercent)))
474 		    / 100);
475 
476 		if (rand() < randno) {
477 			(void) ipc_mutex_unlock(&entry->fse_lock);
478 			continue;
479 		}
480 
481 		preallocated++;
482 
483 		filebench_log(LOG_DEBUG_IMPL, "Populated %s", entry->fse_path);
484 
485 		/* see if reusing and this file exists */
486 		if (reusing && (stat64(path, &sb) == 0)) {
487 			if ((fd = open64(path, O_RDWR)) < 0) {
488 				filebench_log(LOG_INFO,
489 				    "Attempted but failed to Re-use file %s",
490 				    path);
491 				(void) ipc_mutex_unlock(&entry->fse_lock);
492 				return (-1);
493 			}
494 
495 			if (sb.st_size == (off64_t)entry->fse_size) {
496 				filebench_log(LOG_INFO,
497 				    "Re-using file %s", path);
498 
499 				if (!integer_isset(fileset->fs_cached))
500 					(void) fileset_freemem(fd,
501 					    entry->fse_size);
502 
503 				entry->fse_flags |= FSE_EXISTS;
504 				(void) close(fd);
505 				(void) ipc_mutex_unlock(&entry->fse_lock);
506 				continue;
507 
508 			} else if (sb.st_size > (off64_t)entry->fse_size) {
509 				/* reuse, but too large */
510 				filebench_log(LOG_INFO,
511 				    "Truncating & re-using file %s", path);
512 
513 				(void) ftruncate64(fd,
514 				    (off64_t)entry->fse_size);
515 
516 				if (!integer_isset(fileset->fs_cached))
517 					(void) fileset_freemem(fd,
518 					    entry->fse_size);
519 
520 				entry->fse_flags |= FSE_EXISTS;
521 				(void) close(fd);
522 				(void) ipc_mutex_unlock(&entry->fse_lock);
523 				continue;
524 			}
525 		} else {
526 
527 			/* No file or not reusing, so create */
528 			if ((fd = open64(path, O_RDWR | O_CREAT, 0644)) < 0) {
529 				filebench_log(LOG_ERROR,
530 				    "Failed to pre-allocate file %s: %s",
531 				    path, strerror(errno));
532 
533 				return (-1);
534 			}
535 		}
536 
537 		entry->fse_flags |= FSE_EXISTS;
538 
539 		for (seek = 0; seek < entry->fse_size; ) {
540 			off64_t wsize;
541 			int ret = 0;
542 
543 			/*
544 			 * Write FILE_ALLOC_BLOCK's worth,
545 			 * except on last write
546 			 */
547 			wsize = MIN(entry->fse_size - seek, FILE_ALLOC_BLOCK);
548 
549 			ret = write(fd, buf, wsize);
550 			if (ret != wsize) {
551 				filebench_log(LOG_ERROR,
552 				    "Failed to pre-allocate file %s: %s",
553 				    path, strerror(errno));
554 				(void) close(fd);
555 				return (-1);
556 			}
557 			seek += wsize;
558 		}
559 
560 		if (!integer_isset(fileset->fs_cached))
561 			(void) fileset_freemem(fd, entry->fse_size);
562 
563 		(void) close(fd);
564 		(void) ipc_mutex_unlock(&entry->fse_lock);
565 
566 		filebench_log(LOG_DEBUG_IMPL,
567 		    "Pre-allocated file %s size %lld", path, entry->fse_size);
568 	}
569 	filebench_log(LOG_VERBOSE,
570 	    "Preallocated %d of %lld of fileset %s in %d seconds",
571 	    preallocated,
572 	    *(fileset->fs_entries),
573 	    fileset->fs_name,
574 	    ((gethrtime() - start) / 1000000000) + 1);
575 
576 	free(buf);
577 	return (0);
578 }
579 
580 /*
581  * Adds an entry to the fileset's file list. Single threaded so
582  * no locking needed.
583  */
584 static void
585 fileset_insfilelist(fileset_t *fileset, filesetentry_t *entry)
586 {
587 	if (fileset->fs_filelist == NULL) {
588 		fileset->fs_filelist = entry;
589 		entry->fse_filenext = NULL;
590 	} else {
591 		entry->fse_filenext = fileset->fs_filelist;
592 		fileset->fs_filelist = entry;
593 	}
594 }
595 
596 /*
597  * Adds an entry to the fileset's directory list. Single
598  * threaded so no locking needed.
599  */
600 static void
601 fileset_insdirlist(fileset_t *fileset, filesetentry_t *entry)
602 {
603 	if (fileset->fs_dirlist == NULL) {
604 		fileset->fs_dirlist = entry;
605 		entry->fse_dirnext = NULL;
606 	} else {
607 		entry->fse_dirnext = fileset->fs_dirlist;
608 		fileset->fs_dirlist = entry;
609 	}
610 }
611 
612 /*
613  * Obtaines a filesetentry entity for a file to be placed in a
614  * (sub)directory of a fileset. The size of the file may be
615  * specified by fs_meansize, or calculated from a gamma
616  * distribution of parameter fs_sizegamma and of mean size
617  * fs_meansize. The filesetentry entity is placed on the file
618  * list in the specified parent filesetentry entity, which may
619  * be a directory filesetentry, or the root filesetentry in the
620  * fileset. It is also placed on the fileset's list of all
621  * contained files. Returns 0 if successful or -1 if ipc memory
622  * for the path string cannot be allocated.
623  */
624 static int
625 fileset_populate_file(fileset_t *fileset, filesetentry_t *parent, int serial)
626 {
627 	char tmpname[16];
628 	filesetentry_t *entry;
629 	double drand;
630 	double gamma;
631 
632 	if ((entry = (filesetentry_t *)ipc_malloc(FILEBENCH_FILESETENTRY))
633 	    == NULL) {
634 		filebench_log(LOG_ERROR,
635 		    "fileset_populate_file: Can't malloc filesetentry");
636 		return (-1);
637 	}
638 
639 	(void) pthread_mutex_init(&entry->fse_lock, ipc_mutexattr());
640 	entry->fse_parent = parent;
641 	entry->fse_fileset = fileset;
642 	entry->fse_flags |= FSE_FREE;
643 	fileset_insfilelist(fileset, entry);
644 
645 	(void) snprintf(tmpname, sizeof (tmpname), "%08d", serial);
646 	if ((entry->fse_path = (char *)ipc_pathalloc(tmpname)) == NULL) {
647 		filebench_log(LOG_ERROR,
648 		    "fileset_populate_file: Can't alloc path string");
649 		return (-1);
650 	}
651 
652 	gamma = *(fileset->fs_sizegamma) / 1000.0;
653 
654 	if (gamma > 0) {
655 		drand = gamma_dist_knuth(gamma, fileset->fs_meansize / gamma);
656 		entry->fse_size = (off64_t)drand;
657 	} else {
658 		entry->fse_size = (off64_t)fileset->fs_meansize;
659 	}
660 
661 	fileset->fs_bytes += entry->fse_size;
662 
663 	fileset->fs_realfiles++;
664 	return (0);
665 }
666 
667 /*
668  * Creates a directory node in a fileset, by obtaining a
669  * filesetentry entity for the node and initializing it
670  * according to parameters of the fileset. It determines a
671  * directory tree depth and directory width, optionally using
672  * a gamma distribution. If its calculated depth is less then
673  * its actual depth in the directory tree, it becomes a leaf
674  * node and files itself with "width" number of file type
675  * filesetentries, otherwise it files itself with "width"
676  * number of directory type filesetentries, using recursive
677  * calls to fileset_populate_subdir. The end result of the
678  * initial call to this routine is a tree of directories of
679  * random width and varying depth with sufficient leaf
680  * directories to contain all required files.
681  * Returns 0 on success. Returns -1 if ipc path string memory
682  * cannot be allocated and returns an error code (currently
683  * also -1) from calls to fileset_populate_file or recursive
684  * calls to fileset_populate_subdir.
685  */
686 static int
687 fileset_populate_subdir(fileset_t *fileset, filesetentry_t *parent,
688     int serial, double depth)
689 {
690 	double randepth, drand, ranwidth, gamma;
691 	int isleaf = 0;
692 	char tmpname[16];
693 	filesetentry_t *entry;
694 	int i;
695 
696 	depth += 1;
697 
698 	/* Create dir node */
699 	if ((entry = (filesetentry_t *)ipc_malloc(FILEBENCH_FILESETENTRY))
700 	    == NULL) {
701 		filebench_log(LOG_ERROR,
702 		    "fileset_populate_subdir: Can't malloc filesetentry");
703 		return (-1);
704 	}
705 
706 	(void) pthread_mutex_init(&entry->fse_lock, ipc_mutexattr());
707 
708 	(void) snprintf(tmpname, sizeof (tmpname), "%08d", serial);
709 	if ((entry->fse_path = (char *)ipc_pathalloc(tmpname)) == NULL) {
710 		filebench_log(LOG_ERROR,
711 		    "fileset_populate_subdir: Can't alloc path string");
712 		return (-1);
713 	}
714 
715 	entry->fse_parent = parent;
716 	entry->fse_flags |= FSE_DIR | FSE_FREE;
717 	fileset_insdirlist(fileset, entry);
718 
719 	gamma = *(fileset->fs_dirgamma) / 1000.0;
720 	if (gamma > 0) {
721 		drand = gamma_dist_knuth(gamma, fileset->fs_meandepth / gamma);
722 		randepth = (int)drand;
723 	} else {
724 		randepth = (int)fileset->fs_meandepth;
725 	}
726 
727 	gamma = *(fileset->fs_sizegamma) / 1000.0;
728 
729 	if (gamma > 0) {
730 		drand = gamma_dist_knuth(gamma, fileset->fs_meanwidth / gamma);
731 		ranwidth = drand;
732 	} else {
733 		ranwidth = fileset->fs_meanwidth;
734 	}
735 
736 	if (randepth == 0)
737 		randepth = 1;
738 	if (ranwidth == 0)
739 		ranwidth = 1;
740 	if (depth >= randepth)
741 		isleaf = 1;
742 
743 	/*
744 	 * Create directory of random width according to distribution, or
745 	 * if root directory, continue until #files required
746 	 */
747 	for (i = 1;
748 	    ((parent == NULL) || (i < ranwidth + 1)) &&
749 	    (fileset->fs_realfiles < *(fileset->fs_entries)); i++) {
750 		int ret = 0;
751 
752 		if (parent && isleaf)
753 			ret = fileset_populate_file(fileset, entry, i);
754 		else
755 			ret = fileset_populate_subdir(fileset, entry, i, depth);
756 
757 		if (ret != 0)
758 			return (ret);
759 	}
760 	return (0);
761 }
762 
763 /*
764  * Populates a fileset with files and subdirectory entries. Uses
765  * the supplied fs_dirwidth and fs_entries (number of files) to
766  * calculate the required fs_meandepth (of subdirectories) and
767  * initialize the fs_meanwidth and fs_meansize variables. Then
768  * calls fileset_populate_subdir() to do the recursive
769  * subdirectory entry creation and leaf file entry creation. All
770  * of the above is skipped if the fileset has already been
771  * populated. Returns 0 on success, or an error code from the
772  * call to fileset_populate_subdir if that call fails.
773  */
774 static int
775 fileset_populate(fileset_t *fileset)
776 {
777 	int nfiles;
778 	int meandirwidth = *(fileset->fs_dirwidth);
779 	int ret;
780 
781 	/* Skip if already populated */
782 	if (fileset->fs_bytes > 0)
783 		goto exists;
784 
785 	/*
786 	 * Input params are:
787 	 *	# of files
788 	 *	ave # of files per dir
789 	 *	max size of dir
790 	 *	# ave size of file
791 	 *	max size of file
792 	 */
793 	nfiles = *(fileset->fs_entries);
794 	fileset->fs_meandepth = log(nfiles) / log(meandirwidth);
795 	fileset->fs_meanwidth = meandirwidth;
796 	fileset->fs_meansize = *(fileset->fs_size);
797 
798 	if ((ret = fileset_populate_subdir(fileset, NULL, 1, 0)) != 0)
799 		return (ret);
800 
801 
802 exists:
803 	filebench_log(LOG_VERBOSE, "Fileset %s: %lld files, "
804 	    "avg dir = %.1lf, avg depth = %.1lf, mbytes=%lld",
805 	    fileset->fs_name,
806 	    *(fileset->fs_entries),
807 	    fileset->fs_meanwidth,
808 	    fileset->fs_meandepth,
809 	    fileset->fs_bytes / 1024UL / 1024UL);
810 
811 	return (0);
812 }
813 
814 /*
815  * Allocates a fileset instance, initializes fs_dirgamma and
816  * fs_sizegamma default values, and sets the fileset name to the
817  * supplied name string. Puts the allocated fileset on the
818  * master fileset list and returns a pointer to it.
819  */
820 fileset_t *
821 fileset_define(char *name)
822 {
823 	fileset_t *fileset;
824 
825 	if (name == NULL)
826 		return (NULL);
827 
828 	if ((fileset = (fileset_t *)ipc_malloc(FILEBENCH_FILESET)) == NULL) {
829 		filebench_log(LOG_ERROR,
830 		    "fileset_define: Can't malloc fileset");
831 		return (NULL);
832 	}
833 
834 	filebench_log(LOG_DEBUG_IMPL, "Defining file %s", name);
835 
836 	(void) ipc_mutex_lock(&filebench_shm->fileset_lock);
837 
838 	fileset->fs_dirgamma = integer_alloc(1500);
839 	fileset->fs_sizegamma = integer_alloc(1500);
840 
841 	/* Add fileset to global list */
842 	if (filebench_shm->filesetlist == NULL) {
843 		filebench_shm->filesetlist = fileset;
844 		fileset->fs_next = NULL;
845 	} else {
846 		fileset->fs_next = filebench_shm->filesetlist;
847 		filebench_shm->filesetlist = fileset;
848 	}
849 
850 	(void) ipc_mutex_unlock(&filebench_shm->fileset_lock);
851 
852 	(void) strcpy(fileset->fs_name, name);
853 
854 	return (fileset);
855 }
856 
857 /*
858  * If supplied with a pointer to a fileset and the fileset's
859  * fs_prealloc flag is set, calls fileset_populate() to populate
860  * the fileset with filesetentries, then calls fileset_create()
861  * to make actual directories and files for the filesetentries.
862  * Otherwise, it applies fileset_populate() and fileset_create()
863  * to all the filesets on the master fileset list. It always
864  * returns zero (0) if one fileset is populated / created,
865  * otherwise it returns the sum of returned values from
866  * fileset_create() and fileset_populate(), which
867  * will be a negative one (-1) times the number of
868  * fileset_create() calls which failed.
869  */
870 int
871 fileset_createset(fileset_t *fileset)
872 {
873 	fileset_t *list;
874 	int ret = 0;
875 
876 	if (fileset && integer_isset(fileset->fs_prealloc)) {
877 		if ((ret = fileset_populate(fileset)) != 0)
878 			return (ret);
879 		return (fileset_create(fileset));
880 	}
881 
882 	list = filebench_shm->filesetlist;
883 	while (list) {
884 		ret += fileset_populate(list);
885 		ret += fileset_create(list);
886 		list = list->fs_next;
887 	}
888 
889 	return (ret);
890 }
891 
892 /*
893  * Searches through the master fileset list for the named fileset.
894  * If found, returns pointer to same, otherwise returns NULL.
895  */
896 fileset_t *
897 fileset_find(char *name)
898 {
899 	fileset_t *fileset = filebench_shm->filesetlist;
900 
901 	(void) ipc_mutex_lock(&filebench_shm->fileset_lock);
902 
903 	while (fileset) {
904 		if (strcmp(name, fileset->fs_name) == 0) {
905 			(void) ipc_mutex_unlock(&filebench_shm->fileset_lock);
906 			return (fileset);
907 		}
908 		fileset = fileset->fs_next;
909 	}
910 	(void) ipc_mutex_unlock(&filebench_shm->fileset_lock);
911 
912 	return (NULL);
913 }
914