mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 18:40:43 +03:00
Illumos 4368, 4369.
4369 implement zfs bookmarks 4368 zfs send filesystems from readonly pools Reviewed by: Christopher Siden <christopher.siden@delphix.com> Reviewed by: George Wilson <george.wilson@delphix.com> Approved by: Garrett D'Amore <garrett@damore.org> References: https://www.illumos.org/issues/4369 https://www.illumos.org/issues/4368 https://github.com/illumos/illumos-gate/commit/78f1710 Ported by: Tim Chase <tim@chase2k.com> Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Closes #2530
This commit is contained in:
committed by
Brian Behlendorf
parent
b0bc7a84d9
commit
da536844d5
+114
-34
@@ -259,7 +259,7 @@ zpool_handle(zfs_handle_t *zhp)
|
||||
int len;
|
||||
zpool_handle_t *zph;
|
||||
|
||||
len = strcspn(zhp->zfs_name, "/@") + 1;
|
||||
len = strcspn(zhp->zfs_name, "/@#") + 1;
|
||||
pool_name = zfs_alloc(zhp->zfs_hdl, len);
|
||||
(void) strlcpy(pool_name, zhp->zfs_name, len);
|
||||
|
||||
@@ -546,6 +546,70 @@ zfs_handle_dup(zfs_handle_t *zhp_orig)
|
||||
return (zhp);
|
||||
}
|
||||
|
||||
boolean_t
|
||||
zfs_bookmark_exists(const char *path)
|
||||
{
|
||||
nvlist_t *bmarks;
|
||||
nvlist_t *props;
|
||||
char fsname[ZFS_MAXNAMELEN];
|
||||
char *bmark_name;
|
||||
char *pound;
|
||||
int err;
|
||||
boolean_t rv;
|
||||
|
||||
|
||||
(void) strlcpy(fsname, path, sizeof (fsname));
|
||||
pound = strchr(fsname, '#');
|
||||
if (pound == NULL)
|
||||
return (B_FALSE);
|
||||
|
||||
*pound = '\0';
|
||||
bmark_name = pound + 1;
|
||||
props = fnvlist_alloc();
|
||||
err = lzc_get_bookmarks(fsname, props, &bmarks);
|
||||
nvlist_free(props);
|
||||
if (err != 0) {
|
||||
nvlist_free(bmarks);
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
rv = nvlist_exists(bmarks, bmark_name);
|
||||
nvlist_free(bmarks);
|
||||
return (rv);
|
||||
}
|
||||
|
||||
zfs_handle_t *
|
||||
make_bookmark_handle(zfs_handle_t *parent, const char *path,
|
||||
nvlist_t *bmark_props)
|
||||
{
|
||||
zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
|
||||
|
||||
if (zhp == NULL)
|
||||
return (NULL);
|
||||
|
||||
/* Fill in the name. */
|
||||
zhp->zfs_hdl = parent->zfs_hdl;
|
||||
(void) strlcpy(zhp->zfs_name, path, sizeof (zhp->zfs_name));
|
||||
|
||||
/* Set the property lists. */
|
||||
if (nvlist_dup(bmark_props, &zhp->zfs_props, 0) != 0) {
|
||||
free(zhp);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Set the types. */
|
||||
zhp->zfs_head_type = parent->zfs_head_type;
|
||||
zhp->zfs_type = ZFS_TYPE_BOOKMARK;
|
||||
|
||||
if ((zhp->zpool_hdl = zpool_handle(zhp)) == NULL) {
|
||||
nvlist_free(zhp->zfs_props);
|
||||
free(zhp);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
return (zhp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Opens the given snapshot, filesystem, or volume. The 'types'
|
||||
* argument is a mask of acceptable types. The function will print an
|
||||
@@ -2295,6 +2359,9 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
|
||||
case ZFS_TYPE_SNAPSHOT:
|
||||
str = "snapshot";
|
||||
break;
|
||||
case ZFS_TYPE_BOOKMARK:
|
||||
str = "bookmark";
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
@@ -3152,6 +3219,19 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
|
||||
{
|
||||
zfs_cmd_t zc = {"\0"};
|
||||
|
||||
if (zhp->zfs_type == ZFS_TYPE_BOOKMARK) {
|
||||
nvlist_t *nv = fnvlist_alloc();
|
||||
fnvlist_add_boolean(nv, zhp->zfs_name);
|
||||
int error = lzc_destroy_bookmarks(nv, NULL);
|
||||
fnvlist_free(nv);
|
||||
if (error != 0) {
|
||||
return (zfs_standard_error_fmt(zhp->zfs_hdl, errno,
|
||||
dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
|
||||
zhp->zfs_name));
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
(void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
|
||||
|
||||
if (ZFS_IS_VOLUME(zhp)) {
|
||||
@@ -3535,45 +3615,44 @@ typedef struct rollback_data {
|
||||
const char *cb_target; /* the snapshot */
|
||||
uint64_t cb_create; /* creation time reference */
|
||||
boolean_t cb_error;
|
||||
boolean_t cb_dependent;
|
||||
boolean_t cb_force;
|
||||
} rollback_data_t;
|
||||
|
||||
static int
|
||||
rollback_destroy_dependent(zfs_handle_t *zhp, void *data)
|
||||
{
|
||||
rollback_data_t *cbp = data;
|
||||
prop_changelist_t *clp;
|
||||
|
||||
/* We must destroy this clone; first unmount it */
|
||||
clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
|
||||
cbp->cb_force ? MS_FORCE: 0);
|
||||
if (clp == NULL || changelist_prefix(clp) != 0) {
|
||||
cbp->cb_error = B_TRUE;
|
||||
zfs_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
if (zfs_destroy(zhp, B_FALSE) != 0)
|
||||
cbp->cb_error = B_TRUE;
|
||||
else
|
||||
changelist_remove(clp, zhp->zfs_name);
|
||||
(void) changelist_postfix(clp);
|
||||
changelist_free(clp);
|
||||
|
||||
zfs_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
rollback_destroy(zfs_handle_t *zhp, void *data)
|
||||
{
|
||||
rollback_data_t *cbp = data;
|
||||
|
||||
if (!cbp->cb_dependent) {
|
||||
if (strcmp(zhp->zfs_name, cbp->cb_target) != 0 &&
|
||||
zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
|
||||
zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
|
||||
cbp->cb_create) {
|
||||
if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
|
||||
cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
|
||||
rollback_destroy_dependent, cbp);
|
||||
|
||||
cbp->cb_dependent = B_TRUE;
|
||||
cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
|
||||
rollback_destroy, cbp);
|
||||
cbp->cb_dependent = B_FALSE;
|
||||
|
||||
cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
|
||||
}
|
||||
} else {
|
||||
/* We must destroy this clone; first unmount it */
|
||||
prop_changelist_t *clp;
|
||||
|
||||
clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
|
||||
cbp->cb_force ? MS_FORCE: 0);
|
||||
if (clp == NULL || changelist_prefix(clp) != 0) {
|
||||
cbp->cb_error = B_TRUE;
|
||||
zfs_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
if (zfs_destroy(zhp, B_FALSE) != 0)
|
||||
cbp->cb_error = B_TRUE;
|
||||
else
|
||||
changelist_remove(clp, zhp->zfs_name);
|
||||
(void) changelist_postfix(clp);
|
||||
changelist_free(clp);
|
||||
cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
|
||||
}
|
||||
|
||||
zfs_close(zhp);
|
||||
@@ -3584,8 +3663,8 @@ rollback_destroy(zfs_handle_t *zhp, void *data)
|
||||
* Given a dataset, rollback to a specific snapshot, discarding any
|
||||
* data changes since then and making it the active dataset.
|
||||
*
|
||||
* Any snapshots more recent than the target are destroyed, along with
|
||||
* their dependents.
|
||||
* Any snapshots and bookmarks more recent than the target are
|
||||
* destroyed, along with their dependents (i.e. clones).
|
||||
*/
|
||||
int
|
||||
zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
|
||||
@@ -3605,7 +3684,8 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
|
||||
cb.cb_force = force;
|
||||
cb.cb_target = snap->zfs_name;
|
||||
cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
|
||||
(void) zfs_iter_children(zhp, rollback_destroy, &cb);
|
||||
(void) zfs_iter_snapshots(zhp, B_FALSE, rollback_destroy, &cb);
|
||||
(void) zfs_iter_bookmarks(zhp, rollback_destroy, &cb);
|
||||
|
||||
if (cb.cb_error)
|
||||
return (-1);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2013 by Delphix. All rights reserved.
|
||||
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@@ -144,7 +144,8 @@ zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
|
||||
zfs_handle_t *nzhp;
|
||||
int ret;
|
||||
|
||||
if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
|
||||
if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT ||
|
||||
zhp->zfs_type == ZFS_TYPE_BOOKMARK)
|
||||
return (0);
|
||||
|
||||
zc.zc_simple = simple;
|
||||
@@ -170,6 +171,60 @@ zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
|
||||
return ((ret < 0) ? ret : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over all bookmarks
|
||||
*/
|
||||
int
|
||||
zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data)
|
||||
{
|
||||
zfs_handle_t *nzhp;
|
||||
nvlist_t *props = NULL;
|
||||
nvlist_t *bmarks = NULL;
|
||||
int err;
|
||||
nvpair_t *pair;
|
||||
|
||||
if ((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK)) != 0)
|
||||
return (0);
|
||||
|
||||
/* Setup the requested properties nvlist. */
|
||||
props = fnvlist_alloc();
|
||||
fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_GUID));
|
||||
fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATETXG));
|
||||
fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATION));
|
||||
|
||||
/* Allocate an nvlist to hold the bookmarks. */
|
||||
bmarks = fnvlist_alloc();
|
||||
|
||||
if ((err = lzc_get_bookmarks(zhp->zfs_name, props, &bmarks)) != 0)
|
||||
goto out;
|
||||
|
||||
for (pair = nvlist_next_nvpair(bmarks, NULL);
|
||||
pair != NULL; pair = nvlist_next_nvpair(bmarks, pair)) {
|
||||
char name[ZFS_MAXNAMELEN];
|
||||
char *bmark_name;
|
||||
nvlist_t *bmark_props;
|
||||
|
||||
bmark_name = nvpair_name(pair);
|
||||
bmark_props = fnvpair_value_nvlist(pair);
|
||||
|
||||
(void) snprintf(name, sizeof (name), "%s#%s", zhp->zfs_name,
|
||||
bmark_name);
|
||||
|
||||
nzhp = make_bookmark_handle(zhp, name, bmark_props);
|
||||
if (nzhp == NULL)
|
||||
continue;
|
||||
|
||||
if ((err = func(nzhp, data)) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
fnvlist_free(props);
|
||||
fnvlist_free(bmarks);
|
||||
|
||||
return (err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Routines for dealing with the sorted snapshot functionality
|
||||
*/
|
||||
@@ -404,13 +459,13 @@ static int
|
||||
iter_dependents_cb(zfs_handle_t *zhp, void *arg)
|
||||
{
|
||||
iter_dependents_arg_t *ida = arg;
|
||||
int err;
|
||||
int err = 0;
|
||||
boolean_t first = ida->first;
|
||||
ida->first = B_FALSE;
|
||||
|
||||
if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
|
||||
err = zfs_iter_clones(zhp, iter_dependents_cb, ida);
|
||||
} else {
|
||||
} else if (zhp->zfs_type != ZFS_TYPE_BOOKMARK) {
|
||||
iter_stack_frame_t isf;
|
||||
iter_stack_frame_t *f;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2013 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
|
||||
* Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
|
||||
* All rights reserved
|
||||
@@ -1615,6 +1615,60 @@ err_out:
|
||||
return (err);
|
||||
}
|
||||
|
||||
int
|
||||
zfs_send_one(zfs_handle_t *zhp, const char *from, int fd)
|
||||
{
|
||||
int err;
|
||||
libzfs_handle_t *hdl = zhp->zfs_hdl;
|
||||
|
||||
char errbuf[1024];
|
||||
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
|
||||
"warning: cannot send '%s'"), zhp->zfs_name);
|
||||
|
||||
err = lzc_send(zhp->zfs_name, from, fd);
|
||||
if (err != 0) {
|
||||
switch (errno) {
|
||||
case EXDEV:
|
||||
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
|
||||
"not an earlier snapshot from the same fs"));
|
||||
return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
|
||||
|
||||
case ENOENT:
|
||||
case ESRCH:
|
||||
if (lzc_exists(zhp->zfs_name)) {
|
||||
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
|
||||
"incremental source (%s) does not exist"),
|
||||
from);
|
||||
}
|
||||
return (zfs_error(hdl, EZFS_NOENT, errbuf));
|
||||
|
||||
case EBUSY:
|
||||
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
|
||||
"target is busy; if a filesystem, "
|
||||
"it must not be mounted"));
|
||||
return (zfs_error(hdl, EZFS_BUSY, errbuf));
|
||||
|
||||
case EDQUOT:
|
||||
case EFBIG:
|
||||
case EIO:
|
||||
case ENOLINK:
|
||||
case ENOSPC:
|
||||
case ENOSTR:
|
||||
case ENXIO:
|
||||
case EPIPE:
|
||||
case ERANGE:
|
||||
case EFAULT:
|
||||
case EROFS:
|
||||
zfs_error_aux(hdl, strerror(errno));
|
||||
return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
|
||||
|
||||
default:
|
||||
return (zfs_standard_error(hdl, errno, errbuf));
|
||||
}
|
||||
}
|
||||
return (err != 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Routines specific to "zfs recv"
|
||||
*/
|
||||
|
||||
@@ -439,18 +439,30 @@ lzc_get_holds(const char *snapname, nvlist_t **holdsp)
|
||||
}
|
||||
|
||||
/*
|
||||
* If fromsnap is NULL, a full (non-incremental) stream will be sent.
|
||||
*
|
||||
* "snapname" is the full name of the snapshot to send (e.g. "pool/fs@snap")
|
||||
*
|
||||
* If "from" is NULL, a full (non-incremental) stream will be sent.
|
||||
* If "from" is non-NULL, it must be the full name of a snapshot or
|
||||
* bookmark to send an incremental from (e.g. "pool/fs@earlier_snap" or
|
||||
* "pool/fs#earlier_bmark"). If non-NULL, the specified snapshot or
|
||||
* bookmark must represent an earlier point in the history of "snapname").
|
||||
* It can be an earlier snapshot in the same filesystem or zvol as "snapname",
|
||||
* or it can be the origin of "snapname"'s filesystem, or an earlier
|
||||
* snapshot in the origin, etc.
|
||||
*
|
||||
* "fd" is the file descriptor to write the send stream to.
|
||||
*/
|
||||
int
|
||||
lzc_send(const char *snapname, const char *fromsnap, int fd)
|
||||
lzc_send(const char *snapname, const char *from, int fd)
|
||||
{
|
||||
nvlist_t *args;
|
||||
int err;
|
||||
|
||||
args = fnvlist_alloc();
|
||||
fnvlist_add_int32(args, "fd", fd);
|
||||
if (fromsnap != NULL)
|
||||
fnvlist_add_string(args, "fromsnap", fromsnap);
|
||||
if (from != NULL)
|
||||
fnvlist_add_string(args, "fromsnap", from);
|
||||
err = lzc_ioctl(ZFS_IOC_SEND_NEW, snapname, args, NULL);
|
||||
nvlist_free(args);
|
||||
return (err);
|
||||
@@ -605,3 +617,97 @@ lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen)
|
||||
}
|
||||
return (err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates bookmarks.
|
||||
*
|
||||
* The bookmarks nvlist maps from name of the bookmark (e.g. "pool/fs#bmark") to
|
||||
* the name of the snapshot (e.g. "pool/fs@snap"). All the bookmarks and
|
||||
* snapshots must be in the same pool.
|
||||
*
|
||||
* The returned results nvlist will have an entry for each bookmark that failed.
|
||||
* The value will be the (int32) error code.
|
||||
*
|
||||
* The return value will be 0 if all bookmarks were created, otherwise it will
|
||||
* be the errno of a (undetermined) bookmarks that failed.
|
||||
*/
|
||||
int
|
||||
lzc_bookmark(nvlist_t *bookmarks, nvlist_t **errlist)
|
||||
{
|
||||
nvpair_t *elem;
|
||||
int error;
|
||||
char pool[MAXNAMELEN];
|
||||
|
||||
/* determine the pool name */
|
||||
elem = nvlist_next_nvpair(bookmarks, NULL);
|
||||
if (elem == NULL)
|
||||
return (0);
|
||||
(void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
|
||||
pool[strcspn(pool, "/#")] = '\0';
|
||||
|
||||
error = lzc_ioctl(ZFS_IOC_BOOKMARK, pool, bookmarks, errlist);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve bookmarks.
|
||||
*
|
||||
* Retrieve the list of bookmarks for the given file system. The props
|
||||
* parameter is an nvlist of property names (with no values) that will be
|
||||
* returned for each bookmark.
|
||||
*
|
||||
* The following are valid properties on bookmarks, all of which are numbers
|
||||
* (represented as uint64 in the nvlist)
|
||||
*
|
||||
* "guid" - globally unique identifier of the snapshot it refers to
|
||||
* "createtxg" - txg when the snapshot it refers to was created
|
||||
* "creation" - timestamp when the snapshot it refers to was created
|
||||
*
|
||||
* The format of the returned nvlist as follows:
|
||||
* <short name of bookmark> -> {
|
||||
* <name of property> -> {
|
||||
* "value" -> uint64
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
int
|
||||
lzc_get_bookmarks(const char *fsname, nvlist_t *props, nvlist_t **bmarks)
|
||||
{
|
||||
return (lzc_ioctl(ZFS_IOC_GET_BOOKMARKS, fsname, props, bmarks));
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroys bookmarks.
|
||||
*
|
||||
* The keys in the bmarks nvlist are the bookmarks to be destroyed.
|
||||
* They must all be in the same pool. Bookmarks are specified as
|
||||
* <fs>#<bmark>.
|
||||
*
|
||||
* Bookmarks that do not exist will be silently ignored.
|
||||
*
|
||||
* The return value will be 0 if all bookmarks that existed were destroyed.
|
||||
*
|
||||
* Otherwise the return value will be the errno of a (undetermined) bookmark
|
||||
* that failed, no bookmarks will be destroyed, and the errlist will have an
|
||||
* entry for each bookmarks that failed. The value in the errlist will be
|
||||
* the (int32) error code.
|
||||
*/
|
||||
int
|
||||
lzc_destroy_bookmarks(nvlist_t *bmarks, nvlist_t **errlist)
|
||||
{
|
||||
nvpair_t *elem;
|
||||
int error;
|
||||
char pool[MAXNAMELEN];
|
||||
|
||||
/* determine the pool name */
|
||||
elem = nvlist_next_nvpair(bmarks, NULL);
|
||||
if (elem == NULL)
|
||||
return (0);
|
||||
(void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
|
||||
pool[strcspn(pool, "/#")] = '\0';
|
||||
|
||||
error = lzc_ioctl(ZFS_IOC_DESTROY_BOOKMARKS, pool, bmarks, errlist);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ libzpool_la_SOURCES = \
|
||||
$(top_srcdir)/module/zfs/dmu_zfetch.c \
|
||||
$(top_srcdir)/module/zfs/dnode.c \
|
||||
$(top_srcdir)/module/zfs/dnode_sync.c \
|
||||
$(top_srcdir)/module/zfs/dsl_bookmark.c \
|
||||
$(top_srcdir)/module/zfs/dsl_dataset.c \
|
||||
$(top_srcdir)/module/zfs/dsl_deadlist.c \
|
||||
$(top_srcdir)/module/zfs/dsl_deleg.c \
|
||||
|
||||
Reference in New Issue
Block a user