/*
* 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
*/
/*
*/
/*
* zfs diff support
*/
#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <string.h>
#include <fcntl.h>
#include <attr.h>
#include <stddef.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stropts.h>
#include <pthread.h>
#include <sys/zfs_ioctl.h>
#include <strings.h>
#include <libzfs.h>
#include "libzfs_impl.h"
typedef struct differ_info {
char *fromsnap;
char *frommnt;
char *tosnap;
char *tomnt;
char *ds;
char *dsmnt;
char *tmpsnap;
int zerr;
int cleanupfd;
int outputfd;
int datafd;
void *cb_arg;
/*
* Given a {dsname, object id}, get the object path
*/
static int
{
int error;
char *name;
} else if (error == ZFS_NAMEERR_NO_NAME_REQUESTED) {
&zc) != 0) {
}
}
if (error == 0) {
return (0);
}
return (0);
}
"The sys_config privilege or diff delegated permission "
"is needed\nto discover path names"));
return (-1);
} else {
"Unable to determine path for "
return (-1);
}
}
static int
{
int change;
/*
* Unallocated object sharing the same meta dnode block
*/
return (0);
}
}
change = 0;
else
if (fobjerr) {
return (0);
} else if (tobjerr) {
/* Account for removing this link itself */
else
if (!di->enumerate_only) {
}
return (0);
}
/* Simple modification or no change */
/*
* No apparent changes. This occurs when this unchanged
* dnode is included in a dnode of dnodes that has
* other changes.
*/
return (0);
}
return (0);
} else {
/* file re-created or object re-used */
if (!di->enumerate_only) {
}
return (0);
}
}
static int
{
char *tosnap;
return (0);
fobjerr = -1;
fobjname[0] = '\0';
} else {
if (zs2_from) {
if (di->needoldname) {
}
} else {
fobjerr = -1;
}
}
if (zs2_to) {
}
} else {
tobjerr = -1;
}
}
static int
{
char *tosnap;
if (buffer_tods == NULL)
return (ENOMEM);
tozp->zc_fromobj = 0;
if (buffer_fromds == NULL)
return (ENOMEM);
sizeof (zfs_stat_t) * NUM_RECORDS;
fromzp->zc_fromobj = 0;
}
return (0);
}
/*ARGSUSED*/
static void
{
}
static int
{
int err;
} else {
}
/*
* from_o -- current object in "from" dataset.
* fromzp->zc_fromobj -- the first object in next "from" chunk.
* fromzp->zc_cookie -- number of records left in "from" chunk
* to_o -- current object in "to" dataset.
* tozp->zc_fromobj -- the first object in next "to" chunk.
* tozp->zc_cookie - number of records left in "to" chunk
*/
/* Get next stats chunk from "from" dataset */
return (err);
}
zs_from =
}
}
/* Get next stats chunk from the "to" dataset */
return (err);
}
}
/*
* At this point, from_o and to_o refer to the next object in
* their respective datasets. If the next object in both is
* past the requested range, then this range is complete. Note
* that we record the next object number in the zfs_cmd_t
* structure. We can short-circuit unnecessary stat calls where
* we already have the answer.
*/
break;
/*
* At least one dataset has an object in range.
* Go tell the user what kind of diff it is.
*/
/*
* Object exists in fromds but not tods.
* This object has been DELETED.
*/
/* Consume one object from the "from" list */
zs_from++;
/*
* Object exists in tods but not fromds.
* This object has been ADDED.
*/
/* Consume one object from the "to" list */
zs_to++;
} else {
/*
* Object exists in both tods and fromds.
* Either this object has been MODIFIED,
* or else the object has been DELETED and a new
* object has been ADDED with the same object number.
*/
/* Consume one object from each list */
zs_from++;
zs_to++;
}
if (err) {
return (err);
}
}
return (0);
}
static int
{
if (di->needoldname &&
return (-1);
}
return (0);
}
static int
{
void * buffer_fromds;
int err;
if (di->enumerate_only) {
/* -e enumerate mode does not display deletes */
return (0);
}
/* we already know this range is empty */
return (0);
}
}
/* Get next stats chunk from the "from" dataset */
return (err);
}
}
break;
zs_from++;
if (err) {
return (err);
}
}
return (0);
}
static void *
{
int err;
if (err != 0)
return ((void *)-1);
for (;;) {
int rv;
do {
break;
} else if (rv == 0) {
/* end of file at a natural breaking point */
break;
}
case DDR_FREE:
break;
case DDR_INUSE:
break;
default:
break;
}
break;
}
if (err)
return ((void *)-1);
"Internal error: bad data from diff IOCTL"));
return ((void *)-1);
}
return ((void *)0);
}
static void
{
else
/* some very old pools won't have shares object */
}
static int
{
ZDIFF_PREFIX, getpid());
"permission is needed in order\nto create a "
"just-in-time snapshot for diffing\n"));
} else {
}
}
return (0);
}
static void
{
}
static int
const char *tosnap)
{
/*
* Can accept in the from-epoch case
* dataset
* dataset@snap1
*/
return (make_temp_snapshot(di));
} else {
return (0);
}
}
/*
* Can accept
* dataset@snap1
* dataset@snap1 dataset@snap2
* dataset@snap1 @snap2
* dataset@snap1 dataset
* @snap1 dataset@snap2
*/
/* only a from snapshot given, must be valid */
"Badly formed snapshot name %s"), fromsnap);
B_FALSE)) {
}
/* the to snap will be a just-in-time snap of the head */
return (make_temp_snapshot(di));
}
"Acceptable requests are"
"\n\tdataset@snap1\n\tdataset@snap1 dataset@snap2"
"\n\tdataset@snap1 @snap2\n\tdataset@snap1 dataset"
"\n\t@snap1 dataset@snap2"));
/*
* not the same dataset name, might be okay if
* tosnap is a clone of a fromsnap descendant.
*/
int err;
if (err != 0) {
break;
break;
}
}
"%s is not a descendant dataset of %.*s\n"),
} else {
}
if (tsnlen) {
} else {
return (make_temp_snapshot(di));
}
} else {
if (tsnlen) {
} else {
return (make_temp_snapshot(di));
}
}
return (0);
}
static int
{
"Cannot diff an unmounted snapshot"));
}
/* Avoid a double slash at the beginning of root-mounted datasets */
**mntpt = '\0';
return (0);
}
static int
{
char *strptr;
char *frommntpt;
/*
* first get the mountpoint for the parent dataset
*/
return (-1);
ZDIFF_SNAPDIR, ++strptr);
return (0);
char *mntpt;
int err;
*strptr = '\0';
*strptr = '@';
if (err != 0)
return (-1);
}
ZDIFF_SNAPDIR, ++strptr);
return (0);
}
static int
{
return (-1);
if (get_mountpoints(di) != 0)
return (-1);
return (0);
}
int
{
int iocerr;
return (-1);
}
}
}
/* do the ioctl() */
if (iocerr != 0) {
"\n The sys_mount privilege or diff delegated "
"permission is needed\n to execute the "
"diff ioctl"));
"%s is not a descendant dataset of %s\n"),
}
(void) pthread_cancel(tid);
} else {
}
}
}
return (0);
}
/* The rest of this file is support for zfs_show_diffs */
typedef enum {
ZFIELD_NAME = 0,
/* last item */
char *zdiff_field_names[] = {
"name",
"size",
"ctime",
"atime",
"mtime",
"crtime",
"linkschange",
"user",
"group",
"links",
"object",
"parent",
"oldname"
};
typedef struct zdiff_info {
int zerr;
int nfields;
} zdiff_info_t;
/*
* stream_bytes
*
* Prints a file name out a character at a time. If the character is
* not in the range of what we consider "printable" ASCII, display it
* as an escaped 3-digit octal value. ASCII values less than a space
* are all control characters and we declare the upper end as the
* DELete character. This also is the last 7-bit ASCII character.
* We choose to treat all 8-bit ASCII as not printable for this
* application.
*/
static void
{
while (*string) {
else
}
}
int
{
int symbol;
case S_IFBLK:
symbol = 'B';
break;
case S_IFCHR:
symbol = 'C';
break;
case S_IFDIR:
symbol = '/';
break;
case S_IFDOOR:
symbol = '>';
break;
case S_IFIFO:
symbol = '|';
break;
case S_IFLNK:
symbol = '@';
break;
case S_IFPORT:
symbol = 'P';
break;
case S_IFSOCK:
symbol = '=';
break;
case S_IFREG:
symbol = 'F';
break;
default:
symbol = '?';
break;
}
return (symbol);
}
static void
{
}
static void
{
}
static void
{
int i;
if (i > 0) {
}
switch (field) {
case ZFIELD_NAME:
break;
case ZFIELD_OLD_NAME:
if (oldfile)
else
break;
case ZFIELD_DELTA:
break;
case ZFIELD_INODE:
break;
case ZFIELD_PARENT_INODE:
break;
case ZFIELD_LINKS:
break;
case ZFIELD_SIZE:
break;
case ZFIELD_UID:
break;
case ZFIELD_GID:
break;
case ZFIELD_ATIME:
break;
case ZFIELD_CTIME:
break;
case ZFIELD_MTIME:
break;
case ZFIELD_CRTIME:
break;
}
}
}
static int
{
char *propstr;
int count, j;
/* Process each field */
count = 0;
for (j = 0; j < ZFIELD_MAX_FIELD; j++) {
/* same name */
break;
}
}
goto failfield;
count++;
}
return (0);
return (-1);
}
static void
{
}
} else {
else
if (delta != 0)
}
}
static void
{
}
} else {
if (delta != 0)
}
}
/*ARGSUSED*/
void
{
switch (type) {
case ZDIFF_MODIFIED:
/* same name */
} else {
}
break;
case ZDIFF_REMOVED:
break;
case ZDIFF_ADDED:
}
}
int
{
int rc;
}
/* di.errbuf is already set up */
}
/*
* In normal (non-enumerate) mode, we always need both names
* (to distinguish rename from modify). In enumerate mode,
* we need just the new name. In enumerate mode with specific
* fields, we only need the names if they ask for it.
*/
} else {
int i;
if (field == ZFIELD_NAME)
if (field == ZFIELD_OLD_NAME)
}
if (!needname)
flags |= ZFS_DIFF_NONAME;
if (!needoldname)
}
}
return (rc);
}
#include <pwd.h>
#include <grp.h>
#include <utmpx.h>
};
static struct cachenode *
{
while (c != NULL) {
/* found it */
return (c);
c = c->lesschild;
} else {
c = c->grtrchild;
}
}
/* not in the cache, make a new entry for it */
if (c == NULL) {
perror("ls");
exit(2);
}
*parent = c;
return (c);
}
/*
* get name from cache, or passwd file for a given uid;
* lastuid is set to uid.
*/
static char *
{
struct cachenode *c;
return (lastuname);
if (c->initted == 0) {
} else {
}
c->initted = 1;
}
return (lastuname);
}
/*
* get name from cache, or group file for a given gid;
* lastgid is set to gid.
*/
static char *
{
struct cachenode *c;
return (lastgname);
if (c->initted == 0) {
} else {
}
c->initted = 1;
}
return (lastgname);
}