/* Set operations for device-inode pairs stored in a space-efficient manner.
Copyright 2009-2010 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
/* written by Jim Meyering */
#include
#include "di-set.h"
#include
#include
#include
#include
#include
#include
#include "verify.h"
/* Set operations for device-inode pairs stored in a space-efficient manner.
A naive mapping uses 16 bytes to save a single st_dev, st_ino pair.
However, in many applications, the vast majority of actual device,inode
number pairs can be efficiently compressed to fit in 8 or even 4 bytes,
by using a separate table to map a relatively small number of devices
to small integers. */
#define N_DEV_BITS_4 5
#define N_INO_BITS_4 (32 - N_DEV_BITS_4 - 2 - 1)
#define N_DEV_BITS_8 8
#define N_INO_BITS_8 (64 - N_DEV_BITS_8 - 2 - 1)
/* Note how the last bit is always set.
This is required, in order to be able to distinguish
an encoded di_ent value from a malloc-returned pointer,
which must be 4-byte-aligned or better. */
struct dev_ino_4
{
uint32_t mode:2; /* must be first */
uint32_t short_ino:N_INO_BITS_4;
uint32_t mapped_dev:N_DEV_BITS_4;
uint32_t always_set:1;
};
verify (N_DEV_BITS_4 <= 8 * sizeof (int));
verify (sizeof (struct dev_ino_4) == 4);
struct dev_ino_8
{
uint32_t mode:2; /* must be first */
uint64_t short_ino:N_INO_BITS_8;
uint32_t mapped_dev:N_DEV_BITS_8;
uint32_t always_set:1;
};
verify (sizeof (struct dev_ino_8) == 8);
struct dev_ino_full
{
uint32_t mode:2; /* must be first */
dev_t dev;
ino_t ino;
};
enum di_mode
{
DI_MODE_4 = 1,
DI_MODE_8 = 2,
DI_MODE_FULL = 3
};
/*
di_mode raw_inode mapped dev always_set
\____________|_______________\_____/
4-byte | 2| 25 | 5 |1| mapped_dev
`----------------------------------------------------|-----.
8-byte | 2| 53 | 8 |1|
`----------------------------------------------------------'
*/
struct di_ent
{
union
{
struct dev_ino_4 di4;
struct dev_ino_8 di8;
struct dev_ino_full full;
uint32_t u32;
uint64_t u64;
void *ptr;
} u;
};
struct dev_map_ent
{
dev_t dev;
uint32_t mapped_dev;
};
static inline bool
is_encoded_ptr (struct di_ent const *v)
{
return (size_t) v % 4;
}
static struct di_ent
decode_ptr (struct di_ent const *v)
{
if (!is_encoded_ptr (v))
return *v;
struct di_ent di;
di.u.ptr = (void *) v;
return di;
}
static size_t
di_ent_hash (void const *x, size_t table_size)
{
struct di_ent e = decode_ptr (x);
return (e.u.di4.mode == DI_MODE_4
? e.u.u32
: (e.u.di4.mode == DI_MODE_8
? e.u.u64
: e.u.full.ino)) % table_size;
}
/* Compare two di_ent structs.
Return true if they are the same. */
static bool
di_ent_compare (void const *x, void const *y)
{
struct di_ent a = decode_ptr (x);
struct di_ent b = decode_ptr (y);
if (a.u.di4.mode != b.u.di4.mode)
return false;
if (a.u.di4.mode == DI_MODE_4)
return (a.u.di4.short_ino == b.u.di4.short_ino
&& a.u.di4.mapped_dev == b.u.di4.mapped_dev);
if (a.u.di8.mode == DI_MODE_8)
return (a.u.di8.short_ino == b.u.di8.short_ino
&& a.u.di8.mapped_dev == b.u.di8.mapped_dev);
return (a.u.full.ino == b.u.full.ino
&& a.u.full.dev == b.u.full.dev);
}
static void
di_ent_free (void *v)
{
if ( ! is_encoded_ptr (v))
free (v);
}
int
di_set_init (struct di_set_state *dis, size_t initial_size)
{
if (dev_map_init (&dis->dev_map) < 0)
return -1;
dis->di_set = hash_initialize (initial_size, NULL,
di_ent_hash, di_ent_compare, di_ent_free);
return dis->di_set ? 0 : -1;
}
void
di_set_free (struct di_set_state *dis)
{
dev_map_free (&dis->dev_map);
hash_free (dis->di_set);
}
/* Given a device-inode set, DIS, create an entry for the DEV,INO
pair, and store it in *V. If possible, encode DEV,INO into the pointer
itself, but if not, allocate space for a full "struct di_ent" and set *V
to that pointer. Upon memory allocation failure, return -1.
Otherwise return 0. */
int
di_ent_create (struct di_set_state *dis,
dev_t dev, ino_t ino,
struct di_ent **v)
{
static int prev_m = -1;
static dev_t prev_dev = -1;
struct di_ent di_ent;
int mapped_dev;
if (dev == prev_dev)
mapped_dev = prev_m;
else
{
mapped_dev = dev_map_insert (&dis->dev_map, dev);
if (mapped_dev < 0)
return -1;
prev_dev = dev;
prev_m = mapped_dev;
}
if (mapped_dev < (1 << N_DEV_BITS_4)
&& ino < (1 << N_INO_BITS_4))
{
#if lint
/* When this struct is smaller than a pointer, initialize
the pointer so tools like valgrind don't complain about
the uninitialized bytes. */
if (sizeof di_ent.u.di4 < sizeof di_ent.u.ptr)
di_ent.u.ptr = NULL;
#endif
di_ent.u.di4.mode = DI_MODE_4;
di_ent.u.di4.short_ino = ino;
di_ent.u.di4.mapped_dev = mapped_dev;
di_ent.u.di4.always_set = 1;
*v = di_ent.u.ptr;
}
else if (mapped_dev < (1 << N_DEV_BITS_8)
&& ino < ((uint64_t) 1 << N_INO_BITS_8))
{
di_ent.u.di8.mode = DI_MODE_8;
di_ent.u.di8.short_ino = ino;
di_ent.u.di8.mapped_dev = mapped_dev;
di_ent.u.di8.always_set = 1;
*v = di_ent.u.ptr;
}
else
{
/* Handle the case in which INO is too large or in which (far less
likely) we encounter hard-linked files on 2^N_DEV_BITS_8
different devices. */
struct di_ent *p = malloc (sizeof *p);
if (!p)
return -1;
assert ((size_t) p % 4 == 0);
p->u.full.mode = DI_MODE_FULL;
p->u.full.ino = ino;
p->u.full.dev = dev;
*v = p;
}
return 0;
}
/* Attempt to insert the DEV,INO pair into the set, DIS.
If it matches a pair already in DIS, don't modify DIS and return 0.
Otherwise, if insertion is successful, return 1.
Upon any failure return -1. */
int
di_set_insert (struct di_set_state *dis, dev_t dev, ino_t ino)
{
struct di_ent *v;
if (di_ent_create (dis, dev, ino, &v) < 0)
return -1;
int err = hash_insert0 (dis->di_set, v, NULL);
if (err == -1) /* Insertion failed due to lack of memory. */
return -1;
if (err == 1) /* Insertion succeeded. */
return 1;
/* That pair is already in the table, so ENT was not inserted. Free it. */
if (! is_encoded_ptr (v))
free (v);
return 0;
}