/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Code to maintain the runtime and on-disk filehandle mapping table for
* nfslog.
*/
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <unistd.h>
#include <dirent.h>
#include <ndbm.h>
#include <time.h>
#include <libintl.h>
#include "fhtab.h"
#include "nfslogd.h"
/*
* It is important that this string not match the length of the
* file handle key length NFS_FHMAXDATA
*/
struct db_list {
};
struct link_keys {
int lnsize;
};
extern int debug;
extern time_t mapping_update_interval;
extern time_t prune_timeout;
int create_flag);
int ksize);
char *str);
int *errorp);
int *errorp);
static void free_link_cookies(void *cookie);
/*
* The following functions do the actual database I/O. Currently use DBM.
*/
/*
* The "db_*" functions are functions that access the database using
* database-specific calls. Currently the only database supported is
* dbm. Because of the limitations of this database, in particular when
* it comes to manipulating records with the same key, or using multiple keys,
* the following design decisions have been made:
*
* Each file system has a separate dbm file, which are kept open as
* accessed, listed in a linked list.
* Two possible access mode are available for each file - either by
* file handle, or by directory file handle and name. Since
* dbm does not allow multiple keys, we will have a primary
* The primary key is the pair (inode,gen) which can be obtained
* from the file handle. This points to a record with
* the full file handle and the secondary key (dfh-key,name)
* for one of the links.
* The secondary key is the pair (dfh-key,name) where dfh-key is
* the primary key for the directory and the name is the
* link name. It points to a record that contains the primary
* key for the file and to the previous and next hard link
* found for this file (if they exist).
*
* Summary of operations:
* Adding a new file: Create the primary record and secondary (link)
* record and add both to the database. The link record
* would have prev and next links set to NULL.
*
* Adding a link to a file in the database: Add the link record,
* to the head of the links list (i.e. prev = NULL, next =
* secondary key recorded in the primary record). Update
* the primary record to point to the new link, and the
* secondary record for the old head of list to point to new.
*
* Deleting a file: Delete the link record. If it is the last link
* then mark the primary record as deleted but don't delete
* that one from the database (in case some clients still
* hold the file handle). If there are other links, and the
* deleted link is the head of the list (in the primary
* record), update the primary record with the new head.
*
* Renaming a file: Add the new link and then delete the old one.
*
* Lookup by file handle (read, write, lookup, etc.) - fetch primary rec.
* Lookup by dir info (delete, link, rename) - fetch secondary rec.
*
* XXX NOTE: The code is written single-threaded. To make it multi-
* threaded, the following considerations must be made:
* (example: deleting a link may affect up to 4 separate database
* entries: the deleted link, the prev and next links if exist,
* and the filehandle entry, if it points to the deleted link -
* these changes must be atomic).
*/
/*
* Create a link key given directory fh and name
*/
static int
{
if (linksize32 > linksize)
return (linksize32);
}
/*
* db_get_db - gets the database for the filesystem, or creates one
* if none exists. Return the pointer for the database in *dbpp if success.
* Return 0 for success, error code otherwise.
*/
static struct db_list *
{
*errorp = 0;
for (p = db_fs_list;
p = p->next);
if (p != NULL) {
/* Found it */
return (p);
}
/* Create it */
"db_get_db: malloc db failed: Error %s"),
return (NULL);
}
== NULL) {
"db_get_db: malloc dbpath failed: Error %s"),
goto err_exit;
}
/*
* The open mode is masked by UMASK.
*/
== NULL) {
"db_get_db: dbm_open db '%s' failed: Error %s"),
if (*errorp == 0) /* should not happen but may */
*errorp = -1;
goto err_exit;
}
/*
* Add the version identifier (have to check first in the
* case the db exists)
*/
}
db_fs_list = newp;
if (debug > 1) {
}
return (newp);
}
}
}
return (NULL);
}
/*
* db_get_all_databases - gets the database for any filesystem. This is used
* when any database will do - typically to retrieve the path for the
* public filesystem. If any database is open - return the first one,
* otherwise, search for it using fhpath. If getall is TRUE, open all
* matching databases, and mark them (to indicate that all such were opened).
* Return the pointer for a matching database if success.
*/
static struct db_list *
{
break;
}
/*
* if one database for that prefix is open, and either only
* one is needed, or already opened all such databases,
* return here without exhaustive search
*/
return (dbp);
}
"db_get_all_databases: strdup '%s' Error '%s*'"),
return (NULL);
}
fhpathname = NULL;
/* no directory */
goto exit;
}
"db_get_all_databases: strdup '%s' Error '%s*'"),
goto exit;
}
/* Terminate fhdir string at last '/' */
/* Search the directory */
if (debug > 2) {
(void) printf("db_get_all_databases: search '%s' for '%s*'\n",
fhdir, fhpathname);
}
"db_get_all_databases: opendir '%s' Error '%s*'"),
goto exit;
}
continue;
}
if (!getall)
break;
}
}
}
exit:
if (fhpathname != NULL)
return (ret_dbp);
}
static void
{
/* may be inode,name - try to print the fields */
if (ksize >= NFS_FHMAXDATA) {
if (ksize > NFS_FHMAXDATA) {
}
}
}
static void
{
return;
linkp->prev_offset);
}
static void
{
return;
}
static void
{
if (ksize > NFS_FHMAXDATA) {
/* probably a link struct */
} else if (ksize == NFS_FHMAXDATA) {
/* probably an fhlist struct */
} else {
/* don't know... */
}
}
/*
* store_record - store the record in the database and return 0 for success
* or error code otherwise.
*/
static int
{
int error;
char *err;
errno = 0;
if (debug > 2) {
}
/* Could not store */
if (error) {
if (errno)
else {
}
} else { /* should not happen but sometimes does */
errno = -1;
}
if (debug) {
} else
return (errno);
}
return (0);
}
/*
* fetch_record - fetch the record from the database and return 0 for success
* and errno for failure.
* dataaddr is an optional valid address for the result. If dataaddr
* is non-null, then that memory is already alloc'd. Else, alloc it, and
* the caller must free the returned struct when done.
*/
static void *
{
char *err;
errno = 0;
*errorp = 0;
/* see if there is a database error */
/* clear and report the database error */
} else {
/* primary record not in database */
}
if (debug > 3) {
}
return (NULL);
}
/* copy to local struct because dbm may return non-aligned pointers */
"%s: dbm_fetch - malloc %ld: Error %s"),
return (NULL);
}
if (debug > 3) {
}
*errorp = 0;
return (dataaddr);
}
/*
* delete_record - delete the record from the database and return 0 for success
* or error code for failure.
*/
static int
{
int error = 0;
char *err;
errno = 0;
if (debug > 2) {
}
if (error) {
if (errno)
else {
}
} else { /* should not happen but sometimes does */
errno = -1;
}
if (debug) {
} else
}
return (errno);
}
/*
* db_update_fhrec - puts fhrec in db with updated atime if more than
* mapping_update_interval seconds passed. Return 0 if success, error otherwise.
*/
static int
{
}
return (0);
}
/*
* db_update_linkinfo - puts linkinfo in db with updated atime if more than
* mapping_update_interval seconds passed. Return 0 if success, error otherwise.
*/
static int
{
}
return (0);
}
/*
* create_primary_struct - add primary record to the database.
* Database must be open when this function is called.
* If success, return the added database entry. fhrecp may be used to
* provide an existing memory area, else malloc it. If failed, *errorp
* contains the error code and return NULL.
*/
static fhlist_ent *
{
"create_primary_struct: malloc %d Error %s"),
return (NULL);
}
}
/* Fill in the fields */
else
}
if (*errorp != 0) {
/* Could not store */
return (NULL);
}
return (new_fhrecp);
}
/*
* db_add_primary - add primary record to the database.
* If record already in and live, return it (even if for a different link).
* If in database but marked deleted, replace it. If not in database, add it.
* Database must be open when this function is called.
* If success, return the added database entry. fhrecp may be used to
* provide an existing memory area, else malloc it. If failed, *errorp
* contains the error code and return NULL.
*/
static fhlist_ent *
{
if (debug > 2)
errorp, "db_add_primary");
if (new_fhrecp != NULL) {
/* primary record is in the database */
/* Update atime if needed */
"db_add_primary put fhrec");
if (debug > 2)
(void) printf("db_add_primary exits(2): name '%s'\n",
name);
return (new_fhrecp);
}
/* primary record not in database - create it */
if (new_fhrecp == NULL) {
/* Could not store */
if (debug > 2)
(void) printf(
"db_add_primary exits(1): name '%s' Error %s\n",
"Unknown"));
return (NULL);
}
if (debug > 2)
return (new_fhrecp);
}
/*
* get_next_link - get and check the next link in the chain.
* Re-use space if linkp param non-null. Also set *linkkey and *linksizep
* to values for next link (*linksizep set to 0 if last link).
* cookie is used to detect corrupted link entries XXXXXXX
* Return the link pointer or NULL if none.
*/
static linkinfo_ent *
{
char *nextkey;
if (linksize == 0)
return (NULL);
*linksizep = 0;
return (NULL);
/* Set linkkey to point to next record */
if (nextsize == 0)
return (new_linkp);
/* Add this key to the cookie list */
return (NULL);
}
/* Make sure record does not point to itself or other internal loops */
/*
* XXX This entry's next pointer points to
* itself. This is only a work-around, remove
* this check once bug 4203186 is fixed.
*/
if (debug) {
"%s: get_next_link: last record invalid.\n",
msg);
}
/* Return as if this is the last link */
return (new_linkp);
}
}
return (new_linkp);
}
/*
* free_link_cookies - free the cookie list
*/
static void
{
}
}
/*
* add_mc_path - add a mc link to a file that has other links. Add it at end
* of linked list. Called when it's known there are other links.
*/
static void
{
void *cookie;
do {
} while (linksize > 0);
/* reached end of list */
/* nothing to do */
if (debug > 1) {
}
return;
}
/* Add new link after last link */
/*
* next - link key for the next in the list - add at end so null.
* prev - link key for the previous link in the list.
*/
/* Add the link information to the database */
if (*errorp != 0)
return;
/* Now update previous last link to point forward to new link */
/* Copy prev link out since it's going to be overwritten */
/* Update previous last link to point to new one */
/* Update the link information to the database */
}
/*
* create_link_struct - create the secondary struct.
* (dfh,name) is the secondary key, fhrec is the primary record for the file
* and linkpp is a place holder for the record (could be null).
* Insert the record to the database.
* Return 0 if success, error otherwise.
*/
static linkinfo_ent *
{
"create_link_struct: malloc failed: Error %s"),
return (NULL);
}
else
/* Calculate offsets of variable fields */
/* name - component name (in directory dfh) */
/*
* next - link key for the next link in the list - NULL if it's
* the first link. If this is the public fs, only one link allowed.
* Avoid setting a multi-component path as primary path,
* unless no choice.
*/
len = 0;
/* different link than the one that's in the record */
/* parent is public fh - either multi-comp or root */
sizeof (public_fh))) {
/* multi-comp path */
errorp);
if (*errorp != 0) {
return (NULL);
}
return (linkp);
}
} else {
/* new link to a file with a different one already */
}
}
/*
* prev - link key for the previous link in the list - since we
* always insert at the front of the list, it's always initially NULL.
*/
/* Add the link information to the database */
"create_link_struct");
if (*errorp != 0) {
return (NULL);
}
return (linkp);
}
/*
* db_add_secondary - add secondary record to the database (for the directory
* information).
* Assumes this is a new link, not yet in the database, and that the primary
* record is already in.
* If fhrecp is non-null, then fhrecp is the primary record.
* Database must be open when this function is called.
* Return 0 if success, error code otherwise.
*/
static int
{
char *nextaddr;
if (debug > 2)
/* Fetch the primary record */
&error, "db_add_secondary primary");
if (new_fhrecp == NULL) {
return (error);
}
}
/* Update fhrec atime if needed */
"db_add_secondary primary");
/* now create and insert the secondary record */
new_fhrecp = NULL;
}
if (debug > 2)
(void) printf("create_link_struct '%s' Error %s\n",
"Unknown"));
return (error);
}
if (nextsize == 0) {
/* No next - can exit now */
if (debug > 2)
(void) printf("db_add_secondary: no next link\n");
return (0);
}
/*
* Update the linked list to point to new head: replace head of
* list in the primary record, then update previous secondary record
* to point to new head
*/
new_fhrecp, &error);
if (new_fhrecp == NULL) {
if (debug > 2)
(void) printf(
"db_add_secondary: replace primary failed\n");
return (error);
}
/*
* newlink is the new head of the list, with its "next" pointing to
* the old head, and its "prev" pointing to NULL. We now need to
* modify the "next" entry to have its "prev" point to the new entry.
*/
if (debug > 2) {
}
/* Get the next link entry from the database */
&error, "db_add_secondary next link");
if (debug > 2)
(void) printf(
"db_add_secondary: fetch next link failed\n");
return (error);
}
/*
* since the "prev" field is the only field to be changed, and it's
* the last in the link record, we only need to modify it (and reclen).
* Re-use link to update the next record.
*/
if (debug > 2)
(void) printf(
return (error);
}
/*
* Update the next link to point to the new prev.
* Return 0 for success, error code otherwise.
* If successful, and nextlinkpp is non-null,
* *nextlinkpp contains the record for the next link, since
* we may will it if the primary record should be updated.
*/
static linkinfo_ent *
{
"update_next_link: malloc next Error %s"),
return (NULL);
}
errorp, "update next");
/* if there is no next record - ok */
/* Return no error */
*errorp = 0;
return (NULL);
}
/* Set its prev to the prev of the deleted record */
/* Change the len and set prev */
if (prevsize > 0) {
}
/* No other changes needed because prev is last field */
if (*errorp != 0) {
}
return (nextlinkp);
}
/*
* Update the prev link to point to the new next.
* Return 0 for success, error code otherwise.
*/
static int
{
/* Update its next to the given one */
"update prev");
/* if error there is no next record - ok */
return (0);
}
/* Change the len and set next - may push prev */
if (diff != 0) {
}
if (nextsize > 0) {
}
/* Store updated record */
return (error);
}
/*
* update_linked_list - update the next link to point back to prev, and vice
* versa. Normally called by delete_link to drop the deleted link from the
* linked list of hard links for the file. next and prev are the keys of next
* and previous links for the deleted link in the list (could be NULL).
* Return 0 for success, error code otherwise.
* If successful, and nextlinkpp is non-null,
* return the record for the next link, since
* if the primary record should be updated we'll need it. In this case,
* actually allocate the space for it because we can't tell otherwise.
*/
static linkinfo_ent *
{
*errorp = 0;
if (nextsize > 0) {
/* not an error if no next link */
if (*errorp != 0) {
if (debug > 1) {
"update_next_link Error %s\n",
"Unknown"));
}
return (NULL);
}
}
}
if (prevsize > 0) {
if (*errorp != 0) {
if (debug > 1) {
"update_prev_link Error %s\n",
"Unknown"));
}
}
}
return (nextlinkp);
}
/*
* db_update_primary_new_head - Update a primary record that the head of
* the list is deleted. Similar to db_add_primary, but the primary record
* must exist, and is always replaced with one pointing to the new link,
* unless it does not point to the deleted link. If the link we deleted
* was the last link, the delete the primary record as well.
* Return 0 for success, error code otherwise.
*/
static int
{
int error;
/* If the deleted link was not the head of the list, we are done */
/* should never be here... */
if (debug > 1) {
"db_update_primary_new_head: primary "
"is for [%s,", name);
}
return (0); /* not head of list so done */
}
/* Set the head to nextkey if exists. Otherwise, mark file as deleted */
/* last link */
/* remove primary record from database */
(void) delete_record(dbp,
"db_update_primary_new_head: fh delete");
return (0);
} else {
/*
* There are still "live" links, so update the primary record.
*/
/* Replace link data with the info for the next link */
}
/* not last link */
return (error);
}
/*
* Exported functions
*/
/*
* db_add - add record to the database. If dfh, fh and name are all here,
* add both primary and secondary records. If fh is not available, don't
* add anything...
* Assumes this is a new file, not yet in the database and that the record
* for fh is already in.
* Return 0 for success, error code otherwise.
*/
int
{
int error = 0;
/* nothing to add */
return (EINVAL);
}
} else {
}
if (debug > 3) {
(void) printf("db_add: name '%s', db '%s'\n",
}
continue;
}
/* Can't add link information */
"db_add: dfh %p, name %p - invalid"),
continue;
}
/* Replace the public fh rather than add link */
}
continue;
}
}
}
}
return (error);
}
/*
* db_lookup - search the database for the file identified by fh.
* Return the entry in *fhrecpp if found, or NULL with error set otherwise.
*/
{
return (NULL);
}
*errorp = 0;
} else {
}
/* Could not get or create database */
return (NULL);
}
errorp, "db_lookup");
/* Update fhrec atime if needed */
"db_lookup");
}
return (fhrecp);
}
/*
* db_lookup_link - search the database for the file identified by (dfh,name).
* If the link was found, use it to search for the primary record.
* Return 0 and set the entry in *fhrecpp if found, return error otherwise.
*/
int *errorp)
{
char *fhkey;
return (NULL);
}
*errorp = 0;
} else {
}
/* Could not get or create database */
return (NULL);
}
/* Get the link record */
"db_lookup_link link");
/* Now use link to search for fh entry */
/* Update fhrec atime if needed */
"db_lookup_link fhrec");
}
/* Update link atime if needed */
"db_lookup_link link");
} else {
}
return (fhrecp);
}
/*
* delete_link - delete the requested link from the database. If it's the
* last link in the database for that file then remove the primary record
* as well. *errorp contains the returned error code.
* Return ENOENT if link not in database and 0 otherwise.
*/
static int
{
*errorp = 0;
/* Get the link record */
/*
* Link not in database.
*/
if (debug > 2) {
"link not in database\n",
}
*linksizep = 0;
return (ENOENT);
}
/*
* Possibilities:
* 1. Normal case - only one link to delete: the link next and
* as the link. Remove the link and fhrec.
* 2. Multiple hard links, and the deleted link is the head of
* the list. Remove the link and replace the link key in
* the primary record to point to the new head.
* 3. Multiple hard links, and the deleted link is not the
* head of the list (not the same as in fhrec) - just
* delete the link and update the previous and next records
* in the links linked list.
*/
/* Get next and prev keys for linked list updates */
/* Update the linked list for the file */
*linksizep = 0;
return (0);
}
/* Delete link record */
/* Get the primary key */
/* Should never happen */
if (debug > 1) {
}
/* This is the head of the list update primary record */
} else {
/* Update fhrec atime if needed */
errstr);
}
if (nextsize > 0)
return (0);
}
/*
* delete_link - delete the requested link from the database. If it's the
* last link in the database for that file then remove the primary record
* as well. If nextlinkkey/sizep are non-null, copy the key and key size of
* the next link in the chain into them (this would save a dbm_fetch op).
* Return ENOENT if link not in database and 0 otherwise, with *errorp
* containing the returned error if any from the delete_link ops.
*/
static int
{
int linkerr;
*errorp = 0;
} else {
int linksize;
}
return (linkerr);
}
/*
* db_delete_link - search the database for the file system for link.
* Delete the link from the database. If this is the "primary" link,
* set the primary record for the next link. If it's the last one,
* delete the primary record.
* Return 0 for success, error code otherwise.
*/
int
{
int error = 0;
return (EINVAL);
}
} else {
}
"db_delete_link link");
}
return (error);
}
#ifdef DEBUG
/*
* db_delete - Deletes the fhrec corresponding to the fh. Use only
* for repairing the fhtable, not for normal handling.
* Return 0 for success, error code otherwise.
*/
int
{
int error = 0;
return (EINVAL);
}
} else {
}
/* Get the link record */
"db_delete: fh delete");
}
return (error);
}
#endif /* DEBUG */
/*
* db_rename_link - search the database for the file system for link.
* Add the new link and delete the old link from the database.
* Return 0 for success, error code otherwise.
*/
int
{
int error;
return (EINVAL);
}
} else {
}
/* find existing link */
&error);
/* Could not find the link */
continue;
}
/* Delete the old link (if last primary record not deleted) */
if (error == 0) {
}
}
return (error);
}
/*
* db_print_all_keys: prints all keys for a given filesystem. If fsidp is
* NULL, print for all filesystems covered by fhpath.
*/
void
{
void *ptr;
return;
dbp = db_fs_list;
} else {
}
/* Could not get or create database */
return;
}
continue;
"\nStart print database for fsid 0x%x 0x%x\n",
if (debug < 2)
continue;
continue;
/* fhrec */
/* linkinfo */
}
}
}
}
void
{
int bufoffset = 0;
return;
}
/*
* links_timedout() takes a primary records and searches all of its
* links to see if they all have access times that are older than
* the 'prune_timeout' value. TRUE if all links are old and FALSE
* if there is just one link that has an access time which is recent.
*/
static int
{
int error;
int linksize;
void *cookie;
/* Get the link record */
do {
/* update primary record to have an uptodate time */
"links_timedout");
"links_timedout: fetch fhrec error %s\n"),
} else {
/* update fhrec atime */
(void) store_record(pdb,
}
}
return (FALSE);
}
} while (linksize > 0);
return (TRUE);
}
/*
* prune_dbs() will search all of the open databases looking for records
* that have not been accessed in the last 'prune_timeout' seconds.
* This search is done on the primary records and a list of potential
* timeout candidates is built. The reason for doing this is to not
* disturb the underlying dbm_firstkey()/dbm_nextkey() sequence; we
* want to search all of the records in the database.
* Once we have our candidate list built, we examine each of those
* item's links to check if the links have been accessed within the
* 'prune_timeout' seconds. If neither the primary nor any its links
* from the database.
*/
int
{
struct thelist {
int cnt = 0;
/*
* Search each of the open databases
*/
do {
/* Check each record in the database */
/* We're only interested in primary records */
continue; /* probably a link record */
continue;
/*
* If this record is a primary record and it is
* not an export point or a public file handle path,
* check it for a ancient access time.
*/
(EXPORT_POINT | PUBLIC_PATH)) ||
prune_timeout)) {
/* Keep this record in the database */
} else {
/* Found one? Save off info about it */
"prune_dbs: malloc failed, error %s\n"),
break;
}
cnt++; /* count how many records allocated */
if (cnt > MAX_PRUNE_REC_CNT) {
/* Limit number of records malloc'd */
if (debug)
"prune_dbs: halt search - too many records\n");
break;
}
}
}
/*
* Take the saved records and check their links to make
* sure that they have not been accessed as well.
*/
/* Everything timed out? */
/*
* Iterate until we run out of links.
* We have to do this since there can be
* multiple links to a primary record and
* we need to delete one at a time.
*/
/* Delete the link and get the next */
/* Delete the link and get the next */
&error, "dump_db");
break;
}
}
if (linkerr) {
/* link not in database, primary is */
/* Should never happen */
if (debug > 1) {
"prune_dbs: Error primary exists ");
}
if (debug)
"prune_dbs: Error primary exists\n"));
(void) delete_record(pdb,
"prune_dbs: fh delete");
}
}
/* Make sure to free the pointers used in the list */
cnt--;
}
}
return (0);
}