home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
misc
/
volume39
/
enh-du2
/
part01
/
fsinfo.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-08-21
|
17KB
|
689 lines
#ifndef lint
static char SCCSID[] = "@(#) fsinfo.c 1.25 93/08/18 00:09:06";
#endif
/*
* "du" enhanced disk usage summary - version 2.
*
* Copyright 1990-1993, Unicom Systems Development. All rights reserved.
* See accompanying README file for terms of distribution and use.
*
* Edit at tabstops=4.
*/
/*
* All of the machine-specific filesystem support should be encapsulated
* within this one file. We maintain the necessary filesystem information
* as a dynamically allocated list of (struct fsinfo) records, one record
* per mounted filesystem.
*
* There are four routines for working with filesystem information.
*
* fs_initinfo() - Initialize the "Fsinfo_list" filesystem information.
* fs_getinfo() - Gets filesystem information on a entry.
* fs_linkdone() - Determines whether a file has been visited already.
* fs_numblocks() - Calculates disk usage of an entry.
*
* One of the trickiest tasks is discovering what filesystems are mounted.
* This is *highly* system specific. We also provide a set of three
* routines -- open_mnttab(), read_mnttab(), and close_mnttab() -- for
* fs_initinfo() to use to get this information.
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#ifdef USE_UNISTD
# include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include "du.h"
/*
* Within each filesystem information record, we maintain a dynamically
* allocated bit vector that indicates which inodes have been already
* visited on the filesystem. If the system has a working statfs() call
* then we can preallocate the bit vector to precisely the right size.
* If statfs() is missing (e.g. old SysV) or broken (e.g. the Neurotic
* File System) we will instead make a guess and grow the bit vector as
* needed. In this case, the allocation increment will be in chunks of
* ITABINCR/8 bytes (since one byte will hold the status of 8 inodes).
* Selection of this value is a simple speed vs memory tradeoff.
*/
#define ITABINCR 8192
/*
* Length of bit vector required for a particular number of inodes.
*/
#define NUMINO_TO_VECLEN(NUMINO) ((NUMINO)/8 + 1)
/*
* Can you believe this shit?
*/
#ifdef USE_MOUNT_MNTTAB
# include <mnttab.h>
# define mount_struct mnttab
# define mount_device mt_dev
# define mount_point mt_filsys
#endif
#ifdef USE_MOUNT_R4MNTTAB
# include <sys/mnttab.h>
# define mount_struct mnttab
# define mount_device mnt_special
# define mount_point mnt_mountp
#endif
#ifdef USE_MOUNT_MNTENT
# include <mntent.h>
# define mount_struct mntent
# define mount_device mnt_fsname
# define mount_point mnt_dir
#endif
#ifdef USE_MOUNT_FSTAB
# include <fstab.h>
# define mount_struct fstab
# define mount_device fs_spec
# define mount_point fs_file
#endif
#ifdef USE_MOUNT_MNTCTL
# include <sys/mntctl.h>
# include <sys/vmount.h>
struct mount_struct {
char *mount_device;
char *mount_point;
};
#endif
/*
* sigh...
*/
#ifdef USE_STATFS_SYSV
# include <sys/statfs.h>
# define STATFS(MNT, PTR, PSIZE, FSTYPE) \
statfs(MNT, PTR, PSIZE, FSTYPE)
#endif
#ifdef USE_STATFS_BSD
# include <sys/mount.h>
# define STATFS(MNT, PTR, PSIZE, FSTYPE) \
statfs(MNT, PTR)
#endif
#ifdef USE_STATFS_SUN
# include <sys/vfs.h>
# define STATFS(MNT, PTR, PSIZE, FSTYPE) \
statfs(MNT, PTR, PSIZE, FSTYPE)
#endif
#ifdef USE_STATFS_HPUX
# include <sys/vfs.h>
# define STATFS(MNT, PTR, PSIZE, FSTYPE) \
statfs(MNT, PTR)
#endif
#ifdef USE_STATFS_NONE
# include <sys/param.h>
struct statfs {
long f_bsize, f_files;
};
# define STATFS(MNT, PTR, PSIZE, FSTYPE) \
((PTR)->f_bsize = BSIZE, (PTR)->f_files = 0, 0)
#endif
/*
* Dynamically allocated list of filesystem information records.
*/
struct fsinfo **Fsinfo_list;
int Fsinfo_size;
/*
* Mount table handling routines.
*/
int open_mnttab __ARGS((void));
struct mount_struct *read_mnttab __ARGS((void));
int close_mnttab __ARGS((void));
/*
* fs_initinfo() - Initialize the "Fsinfo_list" filesystem information.
*/
void fs_initinfo()
{
struct fsinfo *fsp;
struct mount_struct *mnt;
struct stat sbuf;
struct statfs fsbuf;
int n;
Fsinfo_list = (struct fsinfo **) xmalloc(sizeof(struct fsinfo *));
Fsinfo_list[0] = NULL;
Fsinfo_size = 0;
/*
* Open up the mount table.
*/
if (open_mnttab() != 0)
errmssg(ERR_ABORT, errno, "could not open mount table");
/*
* Initialize a filesystem information record for each mounted filesystem.
*/
while ((mnt = read_mnttab()) != NULL) {
/*
* Get the information on this filesystem.
*/
if (stat(mnt->mount_point, &sbuf) != 0) {
errmssg(ERR_WARN, errno, "could not stat \"%s\"", mnt->mount_point);
continue;
}
if (STATFS(mnt->mount_point, &fsbuf, sizeof(struct statfs), 0) != 0) {
errmssg(ERR_WARN, errno,
"could not statfs \"%s\"", mnt->mount_point);
continue;
}
/*
* Allocate and initialize the filesystem information structure.
* If "f_files" is zero then we take an initial guess at the
* number of inodes. This will happen if this system does not
* have a statfs() call or this is an NFS filesystem.
*/
fsp = (struct fsinfo *) xmalloc(sizeof(struct fsinfo));
fsp->dev = sbuf.st_dev;
fsp->nino = (fsbuf.f_files > 0 ? fsbuf.f_files : ITABINCR);
fsp->bsize = fsbuf.f_bsize;
fsp->nindir = fsp->bsize / sizeof(daddr_t);
fsp->remote = (sbuf.st_dev < 0) ||
(strchr(mnt->mount_device, ':') != NULL);
#ifdef BROKE_STBLOCKS
/* very cool algorithm by Lars Henrik Mathiesen <thorinn@diku.dk> */
if (sbuf.st_blocks == 0) {
errmssg(ERR_WARN, 0,
"cannot determine \"%s\" stat blksize - assuming 512",
mnt->mount_device);
fsp->stbsize = 512;
} else {
int round, ratio;
round = (sbuf.st_size + fsbuf.f_bsize - 1) & ~(fsbuf.f_bsize - 1);
ratio = round / sbuf.st_blocks;
for (fsp->stbsize = 512 ; fsp->stbsize < ratio ; fsp->stbsize <<= 1)
;
}
#endif
/*
* Determine how long pathname buffers should be for
* entries on this filesystem.
*/
fsp->path_max = max_path_len(mnt->mount_point);
/*
* Create the bit vector that is used by fs_linkdone() to track
* which inodes have already been encountered.
*/
n = NUMINO_TO_VECLEN(fsp->nino);
fsp->idone = (unsigned char *) xmalloc((unsigned)n);
(void) memset((PTRTYPE *)fsp->idone, 0, n);
/*
* Attach the filesystem information to the end of the list.
*/
Fsinfo_list = (struct fsinfo **) xrealloc((PTRTYPE *)Fsinfo_list,
(Fsinfo_size+2)*sizeof(struct fsinfo *));
Fsinfo_list[Fsinfo_size++] = fsp;
Fsinfo_list[Fsinfo_size] = NULL;
#ifdef DEBUG
Dprintf(stderr, "*** mount_device=\"%s\" mount_point=\"%s\"\n",
mnt->mount_device, mnt->mount_point);
Dprintf(stderr, " remote=%s dev=%d nino=%d bsize=%d nindir=%d\n",
(fsp->remote ? "TRUE" : "FALSE"),
fsp->dev, fsp->nino, fsp->bsize, fsp->nindir);
#ifdef BROKE_STBLOCKS
Dprintf(stderr, " stbsize=%d", fsp->stbsize);
#endif
Dprintf(stderr, " path_max=%d\n", fsp->path_max);
#endif
}
(void) close_mnttab();
}
/*
* fs_getinfo() - Gets filesystem information on a entry.
*
* Given the inode info for an entry, locate the corresponding filesystem
* info record and return a pointer to it. The most recent result is cached
* to reduce the number of list searches.
*/
struct fsinfo *fs_getinfo(sbufp)
register struct stat *sbufp;
{
static struct fsinfo *fsp_save = NULL;
register struct fsinfo **fsp;
if (fsp_save != NULL && fsp_save->dev == sbufp->st_dev)
return fsp_save;
for (fsp = Fsinfo_list ; *fsp != NULL ; ++fsp) {
if ((*fsp)->dev == sbufp->st_dev) {
fsp_save = *fsp;
return fsp_save;
}
}
return (struct fsinfo *) NULL;
}
/*
* fs_linkdone() - Determines whether a file has been visited already.
*
* This procedure implements the logic to avoid recounting of multiply
* linked files. Each (struct fsinfo) contains a bit vector to track
* which inodes have already been visited. The first time this procedure
* is called for a particular inode, we return FALSE and mark it in the bit
* vector. TRUE is returned subsequent times this procedure is called for
* the same inode.
*/
int fs_linkdone(fsp, sbufp)
register struct fsinfo *fsp;
register struct stat *sbufp;
{
register unsigned char *rowp;
int mask;
unsigned old_bytes, new_bytes;
/*
* Verify we haven't gone off the edge of the bit vector. This
* could happen if we had to take an initial guess at the number
* of inodes.
*/
if (sbufp->st_ino > fsp->nino) {
old_bytes = NUMINO_TO_VECLEN(fsp->nino);
#ifdef DEBUG
Dprintf(stderr, "*** growing bit vector for device %d at inode %d\n",
fsp->dev, sbufp->st_ino);
Dprintf(stderr, " old vector size %d bytes (%d inodes)\n",
old_bytes, fsp->nino);
#endif
while (sbufp->st_ino > fsp->nino)
fsp->nino += ITABINCR;
new_bytes = NUMINO_TO_VECLEN(fsp->nino);
#ifdef DEBUG
Dprintf(stderr, " new vector size %d bytes (%d inodes)\n",
new_bytes, fsp->nino);
#endif
fsp->idone =
(unsigned char *)xrealloc((PTRTYPE *)fsp->idone, new_bytes);
(void) memset((PTRTYPE *)(fsp->idone+old_bytes),
0, new_bytes-old_bytes);
}
/*
* Locate the bit within the vector for this inode.
*/
rowp = fsp->idone + (sbufp->st_ino >> 3);
mask = 1 << (sbufp->st_ino & 07);
/*
* If the bit is set then this link was already done.
*/
if (*rowp & mask)
return TRUE;
/*
* Set the bit and indicate the link hasn't been done yet.
*/
*rowp |= mask;
return FALSE;
}
/*
* A classic UNIX filesys contains ten data block addresses in the inode.
*/
#define DIRBLKS 10
/*
* Macro to calculate ceiling(A/B) pretty efficiently.
*/
#define CEIL_DIV(A, B) (((A)+(B)-1) / (B))
/*
* fs_numblocks() - Calculates disk usage of an entry.
*
* This routine is a profiling "hot spot". It is called for every entry,
* and about 15% of the time appears to be spent here. Unfortunately, the
* calculation this routine has to implement is apparently easy to botch given
* the number of vendor's du's that get it wrong. Therefore, I traded a
* bit of speed for readability and clarity. Even still, my benchmarking
* tests show that it stands up well to other implementations.
*/
long fs_numblocks(fsp, sbufp)
register struct fsinfo *fsp;
struct stat *sbufp;
{
register long n_used; /* num blocks used, incl overhead */
#ifdef USE_STBLOCKS /*{*/
/*
* Ignore this entry if we are counting usage for a specific
* user and that user doesn't own this entry.
*/
if (Selected_user >= 0 && sbufp->st_uid != Selected_user)
return 0L;
#ifdef BROKE_STBLOCKS
# define STBSIZE(fsp) ((fsp)->stbsize)
#else
# define STBSIZE(fsp) (512)
#endif
/*
* This is a piece of cake on systems with "st_blocks".
*/
if (Report_blksize == STBSIZE(fsp))
n_used = sbufp->st_blocks;
else if (Report_blksize == 0)
n_used = CEIL_DIV(sbufp->st_blocks*STBSIZE(fsp), fsp->bsize);
else
n_used = CEIL_DIV(sbufp->st_blocks*STBSIZE(fsp), Report_blksize);
#else /*}!USE_STBLOCKS{*/
register long n_to_place; /* num data blocks to be placed */
long n_single_ind; /* scratch single indirect block cntr */
long n_double_ind; /* scratch double indirect block cntr */
/*
* Ignore this entry if we are counting usage for a specific
* user and that user doesn't own this entry.
*/
if (Selected_user >= 0 && sbufp->st_uid != Selected_user)
return 0L;
/*
* Determine the number of data blocks required to store this file.
*/
n_used = CEIL_DIV(sbufp->st_size, fsp->bsize);
/*
* The first DIRBLKS addresses are stored directly in the inode
* and thus require no additional overhead. Figure out how many
* data blocks remain to be placed through indirect addresses.
*/
n_to_place = n_used - DIRBLKS;
/*
* If the file has DIRBLKS or less data blocks then the entire
* file can be stored in direct data blocks, there is no indirect
* block overhead, and thus we are done.
*/
if (n_to_place <= 0)
goto done;
/*
* With the single indirect block, we can get another "nindir" blocks.
*/
++n_used;
n_to_place -= fsp->nindir;
if (n_to_place <= 0)
goto done;
/*
* With the double indirect block, we can get another "nindir" single
* indirect blocks, for a total of another "nindir**2" data blocks.
*/
n_single_ind = CEIL_DIV(n_to_place, fsp->nindir);
if (n_single_ind > fsp->nindir)
n_single_ind = fsp->nindir;
n_used += 1 + n_single_ind;
n_to_place -= n_single_ind * fsp->nindir ;
if (n_to_place <= 0)
goto done;
/*
* With the triple indirect block, we can get another "nindir" double
* indirect blocks, for another "nindir**2" single indirect blocks, for
* a total of another "nindir**3" data blocks.
*/
n_single_ind = CEIL_DIV(n_to_place, fsp->nindir);
n_double_ind = CEIL_DIV(n_single_ind, fsp->nindir);
n_used += 1 + n_double_ind + n_single_ind;
done:
/*
* If required, convert from native blocksize to reporting blocksize.
*/
if (Report_blksize != 0 && Report_blksize != fsp->bsize)
n_used = CEIL_DIV(n_used*fsp->bsize, Report_blksize);
#endif /*}!USE_STBLOCKS*/
return n_used;
}
/*****************************************************************************
*
* Mount Table Access Routines
*
* int open_mnttab();
* Initialize for mount table scan. Return zero on success.
*
* struct mount_struct *read_mnttab();
* Return information on next mount entry; NULL at end of table.
*
* int close_mnttab();
* Close the mount table. Return zero on success, nonzero on error.
*
****************************************************************************/
#ifdef USE_MOUNT_MNTTAB /*{*/
#ifndef MNTTAB
# ifdef PNMNTTAB
# define MNTTAB PNMNTTAB
# else
# define MNTTAB "/etc/mnttab"
# endif
#endif
#ifndef ISMNTFREE
# define ISMNTFREE(mp) ((mp)->mt_dev[0] == '\0')
#endif
static FILE *fpmnt;
int open_mnttab()
{
return ((fpmnt = fopen(MNTTAB, "r")) != NULL ? 0 : -1);
}
struct mnttab *read_mnttab()
{
static struct mnttab mbuf;
while (fread((PTRTYPE *)&mbuf, sizeof(mbuf), 1, fpmnt) == 1) {
if (!ISMNTFREE(&mbuf))
return &mbuf;
}
return (struct mnttab *)NULL;
}
int close_mnttab()
{
return fclose(fpmnt);
}
#endif /*} USE_MOUNT_MNTTAB*/
/****************************************************************************/
#ifdef USE_MOUNT_R4MNTTAB /*{*/
#ifndef MNTTAB
# define MNTTAB "/etc/mnttab"
#endif
static FILE *fpmnt;
int open_mnttab()
{
return ((fpmnt = fopen(MNTTAB, "r")) != NULL ? 0 : -1);
}
struct mnttab *read_mnttab()
{
static struct mnttab mbuf;
mntnull(&mbuf);
return (getmntent(fpmnt, &mbuf) == 0 ? &mbuf : (struct mnttab *)NULL);
}
int close_mnttab()
{
return fclose(fpmnt);
}
#endif /*} USE_MOUNT_R4MNTTAB*/
/****************************************************************************/
#ifdef USE_MOUNT_MNTENT /*{*/
#ifndef MNT_MNTTAB
# define MNT_MNTTAB "/etc/mtab"
#endif
static FILE *fpmnt;
int open_mnttab()
{
return ((fpmnt = setmntent(MNT_MNTTAB, "r")) != NULL ? 0 : -1);
}
struct mntent *read_mnttab()
{
return getmntent(fpmnt);
}
int close_mnttab()
{
return endmntent(fpmnt);
}
#endif /*}USE_MOUNT_MNTENT*/
/****************************************************************************/
#ifdef USE_MOUNT_FSTAB /*{*/
int open_mnttab()
{
return (setfsent() ? 0 : -1);
}
struct fstab *read_mnttab()
{
return getfsent();
}
int close_mnttab()
{
endfsent();
return 0;
}
#endif /*}USE_MOUNT_FSTAB*/
/****************************************************************************/
#ifdef USE_MOUNT_MNTCTL /*{*/
/*
* Here is a fine example of what happens when you have ivory tower
* doctorates instead of experienced system programmers designing stuff.
*/
static char *mntctl_buf;
static char *mntctl_entry;
static int mntctl_fscount;
int open_mnttab()
{
unsigned n;
if (mntctl(MCTL_QUERY, sizeof(n), (char *)&n) != 0)
return -1;
n += 1024;
mntctl_buf = xmalloc(n);
if ((mntctl_fscount = mntctl(MCTL_QUERY, n, mntctl_buf)) == -1)
return -1;
mntctl_entry = mntctl_buf;
return 0;
}
struct mount_struct *read_mnttab()
{
struct vmount *v;
unsigned n, m;
static struct mount_struct mbuf = { NULL, NULL };
if (mntctl_fscount <= 0)
return (struct mount_struct *) NULL;
v = (struct vmount *)mntctl_entry;
mntctl_entry += v->vmt_length;
--mntctl_fscount;
if (mbuf.mount_device != NULL)
free(mbuf.mount_device);
if (mbuf.mount_point != NULL)
free(mbuf.mount_point);
n = vmt2datasize(v, VMT_STUB);
mbuf.mount_point = xmalloc(n+1);
(void) strncpy(mbuf.mount_point, vmt2dataptr(v, VMT_STUB), n);
mbuf.mount_point[n] = '\0';
n = vmt2datasize(v, VMT_HOST);
m = vmt2datasize(v, VMT_OBJECT);
mbuf.mount_device = xmalloc(n+sizeof((char)':')+m+1);
if (v->vmt_flags & MNT_REMOTE) {
(void) strncpy(mbuf.mount_device, vmt2dataptr(v, VMT_HOST), n);
mbuf.mount_device[n] = '\0';
n = strlen(mbuf.mount_device);
mbuf.mount_device[n++] = ':';
} else {
n = 0;
}
(void) strncpy(mbuf.mount_device+n, vmt2dataptr(v, VMT_OBJECT), m);
mbuf.mount_device[n+m] = '\0';
return &mbuf;
}
int close_mnttab()
{
free((PTRTYPE *)mntctl_buf);
return 0;
}
#endif /*} USE_MOUNT_MNTCTL*/