2011-11-28 16:12:30 +00:00
|
|
|
/*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public
|
|
|
|
* License v2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* 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, write to the
|
|
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
|
|
* Boston, MA 021110-1307, USA.
|
|
|
|
*/
|
|
|
|
|
2022-09-14 15:06:52 +00:00
|
|
|
#include "kerncompat.h"
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
2011-11-28 16:12:30 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <libgen.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <ctype.h>
|
2022-09-14 15:06:52 +00:00
|
|
|
#include <uuid/uuid.h>
|
2011-11-28 16:12:30 +00:00
|
|
|
#include <gd.h>
|
2023-04-19 21:17:12 +00:00
|
|
|
#include "kernel-shared/uapi/btrfs.h"
|
2020-08-18 13:56:04 +00:00
|
|
|
#include "kernel-shared/ctree.h"
|
2019-06-19 23:46:21 +00:00
|
|
|
#include "common/utils.h"
|
2011-11-28 16:12:30 +00:00
|
|
|
|
|
|
|
static int use_color;
|
|
|
|
static void
|
|
|
|
push_im(gdImagePtr im, char *name, char *dir)
|
|
|
|
{
|
|
|
|
char fullname[2000];
|
|
|
|
FILE *pngout;
|
|
|
|
|
|
|
|
if (!im)
|
|
|
|
return;
|
|
|
|
|
|
|
|
snprintf(fullname, sizeof(fullname), "%s/%s", dir, name);
|
|
|
|
pngout = fopen(fullname, "w");
|
|
|
|
if (!pngout) {
|
|
|
|
printf("unable to create file %s\n", fullname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
gdImagePng(im, pngout);
|
|
|
|
|
|
|
|
fclose(pngout);
|
|
|
|
gdImageDestroy(im);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
chunk_type(u64 flags)
|
|
|
|
{
|
|
|
|
switch (flags & (BTRFS_BLOCK_GROUP_SYSTEM | BTRFS_BLOCK_GROUP_DATA |
|
|
|
|
BTRFS_BLOCK_GROUP_METADATA)) {
|
|
|
|
case BTRFS_BLOCK_GROUP_SYSTEM:
|
|
|
|
return "system";
|
|
|
|
case BTRFS_BLOCK_GROUP_DATA:
|
|
|
|
return "data";
|
|
|
|
case BTRFS_BLOCK_GROUP_METADATA:
|
|
|
|
return "metadata";
|
|
|
|
case BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA:
|
|
|
|
return "mixed";
|
|
|
|
default:
|
|
|
|
return "invalid";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_bg(FILE *html, char *name, u64 start, u64 len, u64 used, u64 flags,
|
|
|
|
u64 areas)
|
|
|
|
{
|
|
|
|
double frag = (double)areas / (len / 4096) * 2;
|
|
|
|
|
2019-08-11 22:46:49 +00:00
|
|
|
fprintf(html, "<p>%s chunk starts at %llu, size is %s, %.2f%% used, "
|
2011-11-28 16:12:30 +00:00
|
|
|
"%.2f%% fragmented</p>\n", chunk_type(flags), start,
|
2013-07-09 20:24:43 +00:00
|
|
|
pretty_size(len), 100.0 * used / len, 100.0 * frag);
|
2011-11-28 16:12:30 +00:00
|
|
|
fprintf(html, "<img src=\"%s\" border=\"1\" />\n", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
enum tree_colors {
|
|
|
|
COLOR_ROOT = 0,
|
|
|
|
COLOR_EXTENT,
|
|
|
|
COLOR_CHUNK,
|
|
|
|
COLOR_DEV,
|
|
|
|
COLOR_FS,
|
|
|
|
COLOR_CSUM,
|
|
|
|
COLOR_RELOC,
|
|
|
|
COLOR_DATA,
|
|
|
|
COLOR_UNKNOWN,
|
|
|
|
COLOR_MAX
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
get_color(struct btrfs_extent_item *item, int len)
|
|
|
|
{
|
|
|
|
u64 refs;
|
|
|
|
u64 flags;
|
|
|
|
u8 type;
|
|
|
|
u64 offset;
|
|
|
|
struct btrfs_extent_inline_ref *ref;
|
|
|
|
|
|
|
|
refs = btrfs_stack_extent_refs(item);
|
|
|
|
flags = btrfs_stack_extent_flags(item);
|
|
|
|
|
|
|
|
if (flags & BTRFS_EXTENT_FLAG_DATA)
|
|
|
|
return COLOR_DATA;
|
|
|
|
if (refs > 1) {
|
|
|
|
/* this must be an fs tree */
|
|
|
|
return COLOR_FS;
|
|
|
|
}
|
|
|
|
|
|
|
|
ref = (void *)item + sizeof(struct btrfs_extent_item) +
|
|
|
|
sizeof(struct btrfs_tree_block_info);
|
|
|
|
type = btrfs_stack_extent_inline_ref_type(ref);
|
|
|
|
offset = btrfs_stack_extent_inline_ref_offset(ref);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case BTRFS_EXTENT_DATA_REF_KEY:
|
|
|
|
return COLOR_DATA;
|
|
|
|
case BTRFS_SHARED_BLOCK_REF_KEY:
|
|
|
|
case BTRFS_SHARED_DATA_REF_KEY:
|
|
|
|
return COLOR_FS;
|
|
|
|
case BTRFS_TREE_BLOCK_REF_KEY:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return COLOR_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (offset) {
|
|
|
|
case BTRFS_ROOT_TREE_OBJECTID:
|
|
|
|
return COLOR_ROOT;
|
|
|
|
case BTRFS_EXTENT_TREE_OBJECTID:
|
|
|
|
return COLOR_EXTENT;
|
|
|
|
case BTRFS_CHUNK_TREE_OBJECTID:
|
|
|
|
return COLOR_CHUNK;
|
|
|
|
case BTRFS_DEV_TREE_OBJECTID:
|
|
|
|
return COLOR_DEV;
|
|
|
|
case BTRFS_FS_TREE_OBJECTID:
|
|
|
|
return COLOR_FS;
|
|
|
|
case BTRFS_CSUM_TREE_OBJECTID:
|
|
|
|
return COLOR_CSUM;
|
|
|
|
case BTRFS_DATA_RELOC_TREE_OBJECTID:
|
|
|
|
return COLOR_RELOC;
|
|
|
|
}
|
|
|
|
|
|
|
|
return COLOR_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
init_colors(gdImagePtr im, int *colors)
|
|
|
|
{
|
|
|
|
colors[COLOR_ROOT] = gdImageColorAllocate(im, 255, 0, 0);
|
|
|
|
colors[COLOR_EXTENT] = gdImageColorAllocate(im, 0, 255, 0);
|
|
|
|
colors[COLOR_CHUNK] = gdImageColorAllocate(im, 255, 0, 0);
|
|
|
|
colors[COLOR_DEV] = gdImageColorAllocate(im, 255, 0, 0);
|
|
|
|
colors[COLOR_FS] = gdImageColorAllocate(im, 0, 0, 0);
|
|
|
|
colors[COLOR_CSUM] = gdImageColorAllocate(im, 0, 0, 255);
|
|
|
|
colors[COLOR_RELOC] = gdImageColorAllocate(im, 128, 128, 128);
|
|
|
|
colors[COLOR_DATA] = gdImageColorAllocate(im, 100, 0, 0);
|
|
|
|
colors[COLOR_UNKNOWN] = gdImageColorAllocate(im, 50, 50, 50);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
list_fragments(int fd, u64 flags, char *dir)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct btrfs_ioctl_search_args args;
|
|
|
|
struct btrfs_ioctl_search_key *sk = &args.key;
|
|
|
|
int i;
|
|
|
|
struct btrfs_ioctl_search_header *sh;
|
|
|
|
unsigned long off = 0;
|
|
|
|
int bgnum = 0;
|
|
|
|
u64 bgstart = 0;
|
|
|
|
u64 bglen = 0;
|
|
|
|
u64 bgend = 0;
|
|
|
|
u64 bgflags = 0;
|
|
|
|
u64 bgused = 0;
|
|
|
|
u64 saved_extent = 0;
|
|
|
|
u64 saved_len = 0;
|
|
|
|
int saved_color = 0;
|
|
|
|
u64 last_end = 0;
|
|
|
|
u64 areas = 0;
|
|
|
|
long px;
|
|
|
|
char name[1000];
|
|
|
|
FILE *html;
|
|
|
|
int colors[COLOR_MAX];
|
|
|
|
|
|
|
|
gdImagePtr im = NULL;
|
|
|
|
int black = 0;
|
|
|
|
int width = 800;
|
|
|
|
|
|
|
|
snprintf(name, sizeof(name), "%s/index.html", dir);
|
|
|
|
html = fopen(name, "w");
|
|
|
|
if (!html) {
|
|
|
|
printf("unable to create %s\n", name);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(html, "<html><header>\n");
|
|
|
|
fprintf(html, "<title>Btrfs Block Group Allocation Map</title>\n");
|
|
|
|
fprintf(html, "<style type=\"text/css\">\n");
|
|
|
|
fprintf(html, "img {margin-left: 1em; margin-bottom: 2em;}\n");
|
|
|
|
fprintf(html, "</style>\n");
|
|
|
|
fprintf(html, "</header><body>\n");
|
|
|
|
|
|
|
|
memset(&args, 0, sizeof(args));
|
|
|
|
|
2016-10-31 09:59:08 +00:00
|
|
|
sk->tree_id = BTRFS_EXTENT_TREE_OBJECTID;
|
2011-11-28 16:12:30 +00:00
|
|
|
sk->max_type = -1;
|
|
|
|
sk->min_type = 0;
|
|
|
|
sk->max_objectid = (u64)-1;
|
|
|
|
sk->max_offset = (u64)-1;
|
|
|
|
sk->max_transid = (u64)-1;
|
|
|
|
|
|
|
|
/* just a big number, doesn't matter much */
|
|
|
|
sk->nr_items = 4096;
|
|
|
|
|
|
|
|
while(1) {
|
|
|
|
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
|
|
|
|
if (ret < 0) {
|
|
|
|
fprintf(stderr, "ERROR: can't perform the search\n");
|
2014-12-19 15:15:39 +00:00
|
|
|
goto out_close;
|
2011-11-28 16:12:30 +00:00
|
|
|
}
|
|
|
|
/* the ioctl returns the number of item it found in nr_items */
|
|
|
|
if (sk->nr_items == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
off = 0;
|
|
|
|
for (i = 0; i < sk->nr_items; i++) {
|
|
|
|
int j;
|
|
|
|
|
|
|
|
sh = (struct btrfs_ioctl_search_header *)(args.buf +
|
|
|
|
off);
|
|
|
|
off += sizeof(*sh);
|
2016-05-03 14:20:52 +00:00
|
|
|
if (btrfs_search_header_type(sh)
|
|
|
|
== BTRFS_BLOCK_GROUP_ITEM_KEY) {
|
2011-11-28 16:12:30 +00:00
|
|
|
struct btrfs_block_group_item *bg;
|
|
|
|
|
|
|
|
if (im) {
|
|
|
|
push_im(im, name, dir);
|
|
|
|
im = NULL;
|
|
|
|
|
|
|
|
print_bg(html, name, bgstart, bglen,
|
|
|
|
bgused, bgflags, areas);
|
|
|
|
}
|
|
|
|
|
|
|
|
++bgnum;
|
|
|
|
|
|
|
|
bg = (struct btrfs_block_group_item *)
|
|
|
|
(args.buf + off);
|
|
|
|
bgflags = btrfs_block_group_flags(bg);
|
|
|
|
bgused = btrfs_block_group_used(bg);
|
2016-05-03 14:20:52 +00:00
|
|
|
|
2019-08-11 22:46:49 +00:00
|
|
|
printf("found block group %llu len %llu "
|
|
|
|
"flags %llu\n",
|
2016-05-03 14:20:52 +00:00
|
|
|
btrfs_search_header_objectid(sh),
|
|
|
|
btrfs_search_header_offset(sh),
|
|
|
|
bgflags);
|
2011-11-28 16:12:30 +00:00
|
|
|
if (!(bgflags & flags)) {
|
|
|
|
/* skip this block group */
|
2016-05-03 14:20:52 +00:00
|
|
|
sk->min_objectid =
|
|
|
|
btrfs_search_header_objectid(sh) +
|
|
|
|
btrfs_search_header_offset(sh);
|
2011-11-28 16:12:30 +00:00
|
|
|
sk->min_type = 0;
|
|
|
|
sk->min_offset = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
im = gdImageCreate(width,
|
2016-05-03 14:20:52 +00:00
|
|
|
(btrfs_search_header_offset(sh)
|
|
|
|
/ 4096 + 799) / width);
|
2011-11-28 16:12:30 +00:00
|
|
|
|
|
|
|
black = gdImageColorAllocate(im, 0, 0, 0);
|
|
|
|
|
2013-10-07 21:43:03 +00:00
|
|
|
for (j = 0; j < ARRAY_SIZE(colors); ++j)
|
2011-11-28 16:12:30 +00:00
|
|
|
colors[j] = black;
|
|
|
|
|
|
|
|
init_colors(im, colors);
|
2016-05-03 14:20:52 +00:00
|
|
|
bgstart = btrfs_search_header_objectid(sh);
|
|
|
|
bglen = btrfs_search_header_offset(sh);
|
2011-11-28 16:12:30 +00:00
|
|
|
bgend = bgstart + bglen;
|
|
|
|
|
|
|
|
snprintf(name, sizeof(name), "bg%d.png", bgnum);
|
|
|
|
|
|
|
|
last_end = bgstart;
|
|
|
|
if (saved_len) {
|
|
|
|
px = (saved_extent - bgstart) / 4096;
|
|
|
|
for (j = 0; j < saved_len / 4096; ++j) {
|
|
|
|
int x = (px + j) % width;
|
|
|
|
int y = (px + j) / width;
|
|
|
|
gdImageSetPixel(im, x, y,
|
|
|
|
saved_color);
|
|
|
|
}
|
|
|
|
last_end += saved_len;
|
|
|
|
}
|
|
|
|
areas = 0;
|
|
|
|
saved_len = 0;
|
|
|
|
}
|
2016-05-03 14:20:52 +00:00
|
|
|
if (im && btrfs_search_header_type(sh)
|
|
|
|
== BTRFS_EXTENT_ITEM_KEY) {
|
2011-11-28 16:12:30 +00:00
|
|
|
int c;
|
|
|
|
struct btrfs_extent_item *item;
|
|
|
|
|
|
|
|
item = (struct btrfs_extent_item *)
|
|
|
|
(args.buf + off);
|
|
|
|
|
|
|
|
if (use_color)
|
2016-05-03 14:20:52 +00:00
|
|
|
c = colors[get_color(item,
|
|
|
|
btrfs_search_header_len(sh))];
|
2011-11-28 16:12:30 +00:00
|
|
|
else
|
|
|
|
c = black;
|
2016-05-03 14:20:52 +00:00
|
|
|
if (btrfs_search_header_objectid(sh) > bgend) {
|
2019-08-11 22:46:49 +00:00
|
|
|
printf("WARN: extent %llu is without "
|
2016-05-03 14:20:52 +00:00
|
|
|
"block group\n",
|
|
|
|
btrfs_search_header_objectid(sh));
|
2011-11-28 16:12:30 +00:00
|
|
|
goto skip;
|
|
|
|
}
|
2016-05-03 14:20:52 +00:00
|
|
|
if (btrfs_search_header_objectid(sh) == bgend) {
|
|
|
|
saved_extent =
|
|
|
|
btrfs_search_header_objectid(sh);
|
|
|
|
saved_len =
|
|
|
|
btrfs_search_header_offset(sh);
|
2011-11-28 16:12:30 +00:00
|
|
|
saved_color = c;
|
|
|
|
goto skip;
|
|
|
|
}
|
2016-05-03 14:20:52 +00:00
|
|
|
px = (btrfs_search_header_objectid(sh)
|
|
|
|
- bgstart) / 4096;
|
|
|
|
for (j = 0;
|
|
|
|
j < btrfs_search_header_offset(sh) / 4096;
|
|
|
|
++j) {
|
2011-11-28 16:12:30 +00:00
|
|
|
int x = (px + j) % width;
|
|
|
|
int y = (px + j) / width;
|
|
|
|
gdImageSetPixel(im, x, y, c);
|
|
|
|
}
|
2016-05-03 14:20:52 +00:00
|
|
|
if (btrfs_search_header_objectid(sh) != last_end)
|
2011-11-28 16:12:30 +00:00
|
|
|
++areas;
|
2016-05-03 14:20:52 +00:00
|
|
|
last_end = btrfs_search_header_objectid(sh)
|
|
|
|
+ btrfs_search_header_offset(sh);
|
2011-11-28 16:12:30 +00:00
|
|
|
skip:;
|
|
|
|
}
|
2016-05-03 14:20:52 +00:00
|
|
|
off += btrfs_search_header_len(sh);
|
2011-11-28 16:12:30 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* record the mins in sk so we can make sure the
|
|
|
|
* next search doesn't repeat this root
|
|
|
|
*/
|
2016-05-03 14:20:52 +00:00
|
|
|
sk->min_objectid = btrfs_search_header_objectid(sh);
|
|
|
|
sk->min_type = btrfs_search_header_type(sh);
|
|
|
|
sk->min_offset = btrfs_search_header_offset(sh);
|
2011-11-28 16:12:30 +00:00
|
|
|
}
|
|
|
|
sk->nr_items = 4096;
|
|
|
|
|
|
|
|
/* increment by one */
|
|
|
|
if (++sk->min_offset == 0)
|
|
|
|
if (++sk->min_type == 0)
|
|
|
|
if (++sk->min_objectid == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (im) {
|
|
|
|
push_im(im, name, dir);
|
|
|
|
print_bg(html, name, bgstart, bglen, bgused, bgflags, areas);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (use_color) {
|
|
|
|
fprintf(html, "<p>");
|
|
|
|
fprintf(html, "data - dark red, ");
|
|
|
|
fprintf(html, "fs tree - black, ");
|
|
|
|
fprintf(html, "extent tree - green, ");
|
|
|
|
fprintf(html, "csum tree - blue, ");
|
|
|
|
fprintf(html, "reloc tree - grey, ");
|
|
|
|
fprintf(html, "other trees - red, ");
|
|
|
|
fprintf(html, "unknown tree - dark grey");
|
|
|
|
fprintf(html, "</p>");
|
|
|
|
}
|
|
|
|
fprintf(html, "</body></html>\n");
|
2014-12-19 15:15:39 +00:00
|
|
|
|
|
|
|
out_close:
|
|
|
|
fclose(html);
|
|
|
|
|
2011-11-28 16:12:30 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-22 15:06:31 +00:00
|
|
|
void fragments_usage(void)
|
2011-11-28 16:12:30 +00:00
|
|
|
{
|
|
|
|
printf("usage: btrfs-fragments [options] <path>\n");
|
|
|
|
printf(" -c use color\n");
|
|
|
|
printf(" -d print data chunks\n");
|
|
|
|
printf(" -m print metadata chunks\n");
|
|
|
|
printf(" -s print system chunks\n");
|
|
|
|
printf(" (default is data+metadata)\n");
|
|
|
|
printf(" -o <dir> output directory, default is html\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
char *path;
|
|
|
|
int fd;
|
|
|
|
int ret;
|
|
|
|
u64 flags = 0;
|
|
|
|
char *dir = "html";
|
2013-07-15 11:36:50 +00:00
|
|
|
DIR *dirstream = NULL;
|
2011-11-28 16:12:30 +00:00
|
|
|
|
|
|
|
while (1) {
|
|
|
|
int c = getopt(argc, argv, "cmso:h");
|
|
|
|
if (c < 0)
|
|
|
|
break;
|
|
|
|
switch (c) {
|
|
|
|
case 'c':
|
|
|
|
use_color = 1;
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
flags |= BTRFS_BLOCK_GROUP_DATA;
|
|
|
|
break;
|
|
|
|
case 'm':
|
|
|
|
flags |= BTRFS_BLOCK_GROUP_METADATA;
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
flags |= BTRFS_BLOCK_GROUP_SYSTEM;
|
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
dir = optarg;
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
default:
|
2016-03-22 15:06:31 +00:00
|
|
|
fragments_usage();
|
2011-11-28 16:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-16 03:59:46 +00:00
|
|
|
set_argv0(argv);
|
2016-03-22 15:06:31 +00:00
|
|
|
if (check_argc_min(argc - optind, 1))
|
2019-03-04 13:49:15 +00:00
|
|
|
return 1;
|
2011-11-28 16:12:30 +00:00
|
|
|
|
2014-07-16 03:59:46 +00:00
|
|
|
path = argv[optind++];
|
|
|
|
|
2015-10-12 13:23:04 +00:00
|
|
|
fd = btrfs_open_dir(path, &dirstream, 1);
|
|
|
|
if (fd < 0)
|
2011-11-28 16:12:30 +00:00
|
|
|
exit(1);
|
|
|
|
|
|
|
|
if (flags == 0)
|
|
|
|
flags = BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA;
|
|
|
|
|
|
|
|
ret = list_fragments(fd, flags, dir);
|
2013-07-15 11:36:50 +00:00
|
|
|
close_file_or_dir(fd, dirstream);
|
2011-11-28 16:12:30 +00:00
|
|
|
if (ret)
|
|
|
|
exit(1);
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|