mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-02 12:49:03 +00:00
system/xipfs: Add a command to compact a xipfs volume and report it.
xipfs has a defragmentation ioctl and no way to reach it from a shell. The only caller was a demo in examples/nxflatxip, which built its own block map to show what compaction had done. xipfs [-n] [-t <ms>] [<mountpoint>] -n surveys and reports without moving anything: where each file sits, a map of the volume, and how much of the free space a single allocation can reach. That last number is the one a caller facing -ENOSPC actually wants; at 0% the largest possible file already fits however scattered the map looks. The compaction is asked for through a descriptor for the mountpoint directory, so no file inside the volume is open while it runs and a single pass can reach every extent. The walk descends into the directories xipfs synthesises from names, and reports each file by its path relative to the mount, so a volume that uses them is described in full rather than down to its first level. Block totals come from statfs rather than XIPFSIOC_EXTENTINFO, because that one does name a file and an empty volume has none, yet its geometry is still worth reporting. Assisted-by: Claude Code:claude-opus-4-8 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
parent
ce49ac9053
commit
b695ec057f
5 changed files with 742 additions and 0 deletions
35
system/xipfs/CMakeLists.txt
Normal file
35
system/xipfs/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# ##############################################################################
|
||||||
|
# apps/system/xipfs/CMakeLists.txt
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||||
|
# license agreements. See the NOTICE file distributed with this work for
|
||||||
|
# additional information regarding copyright ownership. The ASF licenses this
|
||||||
|
# file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
# use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
# the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations under
|
||||||
|
# the License.
|
||||||
|
#
|
||||||
|
# ##############################################################################
|
||||||
|
|
||||||
|
if(CONFIG_SYSTEM_XIPFS)
|
||||||
|
nuttx_add_application(
|
||||||
|
MODULE
|
||||||
|
${CONFIG_SYSTEM_XIPFS}
|
||||||
|
NAME
|
||||||
|
${CONFIG_SYSTEM_XIPFS_PROGNAME}
|
||||||
|
STACKSIZE
|
||||||
|
${CONFIG_SYSTEM_XIPFS_STACKSIZE}
|
||||||
|
PRIORITY
|
||||||
|
${CONFIG_SYSTEM_XIPFS_PRIORITY}
|
||||||
|
SRCS
|
||||||
|
xipfs_main.c)
|
||||||
|
endif()
|
||||||
38
system/xipfs/Kconfig
Normal file
38
system/xipfs/Kconfig
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#
|
||||||
|
# For a description of the syntax of this configuration file,
|
||||||
|
# see the file kconfig-language.txt in the NuttX tools repository.
|
||||||
|
#
|
||||||
|
|
||||||
|
config SYSTEM_XIPFS
|
||||||
|
tristate "xipfs defragmentation command"
|
||||||
|
default n
|
||||||
|
depends on FS_XIPFS
|
||||||
|
---help---
|
||||||
|
Enable the 'xipfs' command, which compacts a xipfs volume and
|
||||||
|
reports how its erase blocks are laid out. With -n it only
|
||||||
|
reports -- block usage, a map of the volume, and how much of the
|
||||||
|
free space a single allocation can reach -- and moves nothing.
|
||||||
|
|
||||||
|
if SYSTEM_XIPFS
|
||||||
|
|
||||||
|
config SYSTEM_XIPFS_PROGNAME
|
||||||
|
string "Program name"
|
||||||
|
default "xipfs"
|
||||||
|
---help---
|
||||||
|
The name of the command as it appears in NSH.
|
||||||
|
|
||||||
|
config SYSTEM_XIPFS_MOUNTPOINT
|
||||||
|
string "Default mountpoint"
|
||||||
|
default "/mnt/xipfs"
|
||||||
|
---help---
|
||||||
|
The volume operated on when none is given on the command line.
|
||||||
|
|
||||||
|
config SYSTEM_XIPFS_PRIORITY
|
||||||
|
int "Task priority"
|
||||||
|
default 100
|
||||||
|
|
||||||
|
config SYSTEM_XIPFS_STACKSIZE
|
||||||
|
int "Stack size"
|
||||||
|
default DEFAULT_TASK_STACKSIZE
|
||||||
|
|
||||||
|
endif # SYSTEM_XIPFS
|
||||||
25
system/xipfs/Make.defs
Normal file
25
system/xipfs/Make.defs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
############################################################################
|
||||||
|
# apps/system/xipfs/Make.defs
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
# contributor license agreements. See the NOTICE file distributed with
|
||||||
|
# this work for additional information regarding copyright ownership. The
|
||||||
|
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance with the
|
||||||
|
# License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
ifneq ($(CONFIG_SYSTEM_XIPFS),)
|
||||||
|
CONFIGURED_APPS += $(APPDIR)/system/xipfs
|
||||||
|
endif
|
||||||
34
system/xipfs/Makefile
Normal file
34
system/xipfs/Makefile
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
############################################################################
|
||||||
|
# apps/system/xipfs/Makefile
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
# contributor license agreements. See the NOTICE file distributed with
|
||||||
|
# this work for additional information regarding copyright ownership. The
|
||||||
|
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance with the
|
||||||
|
# License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
include $(APPDIR)/Make.defs
|
||||||
|
|
||||||
|
# xipfs defragmentation command
|
||||||
|
|
||||||
|
PROGNAME = $(CONFIG_SYSTEM_XIPFS_PROGNAME)
|
||||||
|
PRIORITY = $(CONFIG_SYSTEM_XIPFS_PRIORITY)
|
||||||
|
STACKSIZE = $(CONFIG_SYSTEM_XIPFS_STACKSIZE)
|
||||||
|
MODULE = $(CONFIG_SYSTEM_XIPFS)
|
||||||
|
|
||||||
|
MAINSRC = xipfs_main.c
|
||||||
|
|
||||||
|
include $(APPDIR)/Application.mk
|
||||||
610
system/xipfs/xipfs_main.c
Normal file
610
system/xipfs/xipfs_main.c
Normal file
|
|
@ -0,0 +1,610 @@
|
||||||
|
/****************************************************************************
|
||||||
|
* apps/system/xipfs/xipfs_main.c
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership. The
|
||||||
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the
|
||||||
|
* License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Included Files
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <nuttx/config.h>
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/statfs.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <nuttx/fs/ioctl.h>
|
||||||
|
#include <nuttx/fs/xipfs.h>
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Pre-processor Definitions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#define MAP_COLS 50
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Private Types
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/* One row of the file table, gathered before anything is printed so that a
|
||||||
|
* failure part way through does not leave half a report behind.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct xipfs_entry_s
|
||||||
|
{
|
||||||
|
/* A path relative to the mountpoint, not one component, so that a file in
|
||||||
|
* a subdirectory is reported the way the volume names it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
char name[XIPFS_PATH_MAX + 1];
|
||||||
|
uint32_t start; /* Relative to the data region */
|
||||||
|
uint32_t nblocks;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t pincount;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct xipfs_survey_s
|
||||||
|
{
|
||||||
|
FAR char *map; /* One char per erase block */
|
||||||
|
FAR struct xipfs_entry_s *files;
|
||||||
|
int nfiles;
|
||||||
|
uint32_t nblocks; /* Blocks in the data region */
|
||||||
|
uint32_t blocksize;
|
||||||
|
uint32_t used;
|
||||||
|
uint32_t freeblocks;
|
||||||
|
uint32_t largestrun;
|
||||||
|
uint32_t nruns; /* Distinct free runs */
|
||||||
|
uint32_t pinned;
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Private Functions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static void show_usage(FAR const char *progname, int exitcode)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "USAGE: %s [-n] [-t <ms>] [<mountpoint>]\n", progname);
|
||||||
|
fprintf(stderr, "\nCompact a xipfs volume, or report what one looks "
|
||||||
|
"like.\n");
|
||||||
|
fprintf(stderr, "\nWhere:\n");
|
||||||
|
fprintf(stderr, "\t-n: Dry run. Report block usage and fragmentation "
|
||||||
|
"and exit\n");
|
||||||
|
fprintf(stderr, "\t without moving anything.\n");
|
||||||
|
fprintf(stderr, "\t-t <ms>: Time budget for the compaction pass. "
|
||||||
|
"0, the default,\n");
|
||||||
|
fprintf(stderr, "\t means run to completion.\n");
|
||||||
|
fprintf(stderr, "\t<mountpoint>: Default: %s\n",
|
||||||
|
CONFIG_SYSTEM_XIPFS_MOUNTPOINT);
|
||||||
|
exit(exitcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: survey_free
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static void survey_free(FAR struct xipfs_survey_s *s)
|
||||||
|
{
|
||||||
|
free(s->map);
|
||||||
|
free(s->files);
|
||||||
|
s->map = NULL;
|
||||||
|
s->files = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: parse_max_ms
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int parse_max_ms(FAR const char *arg, FAR uint32_t *max_ms)
|
||||||
|
{
|
||||||
|
FAR char *endptr;
|
||||||
|
unsigned long value;
|
||||||
|
|
||||||
|
if (arg[0] == '-')
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
value = strtoul(arg, &endptr, 10);
|
||||||
|
if (arg == endptr || *endptr != '\0' || errno == ERANGE ||
|
||||||
|
value > UINT32_MAX)
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*max_ms = value;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: survey_walk
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Record where every file below 'dir' physically sits, descending into
|
||||||
|
* the directories xipfs synthesises from the names. 'rel' is the path of
|
||||||
|
* 'dir' relative to the mount, which is what a file is recorded under so
|
||||||
|
* that the table names it the way the volume does.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int survey_walk(FAR const char *dir, FAR const char *rel,
|
||||||
|
FAR struct xipfs_survey_s *s, FAR int *capacity)
|
||||||
|
{
|
||||||
|
struct xipfs_extent_info_s info;
|
||||||
|
FAR struct dirent *de;
|
||||||
|
FAR DIR *dirp;
|
||||||
|
uint32_t i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dirp = opendir(dir);
|
||||||
|
if (dirp == NULL)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: opendir %s failed: %d\n", dir, errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((de = readdir(dirp)) != NULL)
|
||||||
|
{
|
||||||
|
FAR struct xipfs_entry_s *e;
|
||||||
|
char path[PATH_MAX];
|
||||||
|
char sub[XIPFS_PATH_MAX + 1];
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
|
||||||
|
|
||||||
|
if (rel[0] == '\0')
|
||||||
|
{
|
||||||
|
strlcpy(sub, de->d_name, sizeof(sub));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(sub, sizeof(sub), "%s/%s", rel, de->d_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (de->d_type == DTYPE_DIRECTORY)
|
||||||
|
{
|
||||||
|
ret = survey_walk(path, sub, s, capacity);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
closedir(dirp);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ioctl(fd, XIPFSIOC_EXTENTINFO, (unsigned long)(uintptr_t)&info);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->nfiles == *capacity)
|
||||||
|
{
|
||||||
|
FAR void *tmp;
|
||||||
|
|
||||||
|
*capacity *= 2;
|
||||||
|
tmp = realloc(s->files, *capacity * sizeof(struct xipfs_entry_s));
|
||||||
|
if (tmp == NULL)
|
||||||
|
{
|
||||||
|
closedir(dirp);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->files = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
e = &s->files[s->nfiles++];
|
||||||
|
strlcpy(e->name, sub, sizeof(e->name));
|
||||||
|
e->start = info.start_block - info.data_start;
|
||||||
|
e->nblocks = info.nblocks;
|
||||||
|
e->size = info.size;
|
||||||
|
e->pincount = info.pincount;
|
||||||
|
|
||||||
|
if (e->pincount > 0)
|
||||||
|
{
|
||||||
|
s->pinned += e->nblocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < e->nblocks; i++)
|
||||||
|
{
|
||||||
|
if (e->start + i < s->nblocks)
|
||||||
|
{
|
||||||
|
s->map[e->start + i] = e->pincount > 0 ? 'P' : '#';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dirp);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: survey_collect
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Walk the mount and record where every file physically sits. The block
|
||||||
|
* counts come from statfs so that an empty volume still reports its
|
||||||
|
* geometry -- XIPFSIOC_EXTENTINFO can only be issued against a regular
|
||||||
|
* file, so with no files there is nothing to ask.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int survey_collect(FAR const char *mount,
|
||||||
|
FAR struct xipfs_survey_s *s)
|
||||||
|
{
|
||||||
|
struct statfs sbuf;
|
||||||
|
uint32_t run = 0;
|
||||||
|
uint32_t i;
|
||||||
|
int capacity = 8;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
memset(s, 0, sizeof(*s));
|
||||||
|
|
||||||
|
if (statfs(mount, &sbuf) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: statfs %s failed: %d\n", mount, errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->nblocks = sbuf.f_blocks;
|
||||||
|
s->blocksize = sbuf.f_bsize;
|
||||||
|
|
||||||
|
if (s->nblocks == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: %s reports no blocks; not a xipfs mount?\n",
|
||||||
|
mount);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->map = malloc(s->nblocks);
|
||||||
|
if (s->map == NULL)
|
||||||
|
{
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(s->map, '.', s->nblocks);
|
||||||
|
|
||||||
|
s->files = malloc(capacity * sizeof(struct xipfs_entry_s));
|
||||||
|
if (s->files == NULL)
|
||||||
|
{
|
||||||
|
survey_free(s);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = survey_walk(mount, "", s, &capacity);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
survey_free(s);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduce the map to the numbers a caller actually decides on: how much is
|
||||||
|
* free, and how much of that is reachable by a single allocation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (i = 0; i < s->nblocks; i++)
|
||||||
|
{
|
||||||
|
if (s->map[i] == '.')
|
||||||
|
{
|
||||||
|
s->freeblocks++;
|
||||||
|
if (run == 0)
|
||||||
|
{
|
||||||
|
s->nruns++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++run > s->largestrun)
|
||||||
|
{
|
||||||
|
s->largestrun = run;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s->used++;
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: survey_report
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static void survey_report(FAR const char *mount,
|
||||||
|
FAR struct xipfs_survey_s *s)
|
||||||
|
{
|
||||||
|
uint32_t frag;
|
||||||
|
uint32_t i;
|
||||||
|
int width;
|
||||||
|
int f;
|
||||||
|
|
||||||
|
printf("%s: %" PRIu32 " blocks of %" PRIu32 " bytes (%" PRIu64
|
||||||
|
" KB)\n", mount, s->nblocks, s->blocksize,
|
||||||
|
(uint64_t)s->nblocks * s->blocksize / 1024);
|
||||||
|
|
||||||
|
if (s->nfiles > 0)
|
||||||
|
{
|
||||||
|
/* A name here is a path relative to the mountpoint, so it can be
|
||||||
|
* longer than one component. Widen the column to the longest one
|
||||||
|
* rather than let it push the rest of the row out of alignment.
|
||||||
|
*/
|
||||||
|
|
||||||
|
width = XIPFS_NAME_MAX;
|
||||||
|
for (f = 0; f < s->nfiles; f++)
|
||||||
|
{
|
||||||
|
int len = (int)strlen(s->files[f].name);
|
||||||
|
|
||||||
|
if (len > width)
|
||||||
|
{
|
||||||
|
width = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n %-*s %6s %7s %8s %4s\n", width, "file",
|
||||||
|
"start", "blocks", "bytes", "pin");
|
||||||
|
|
||||||
|
for (f = 0; f < s->nfiles; f++)
|
||||||
|
{
|
||||||
|
FAR struct xipfs_entry_s *e = &s->files[f];
|
||||||
|
|
||||||
|
printf(" %-*s %6" PRIu32 " %7" PRIu32 " %8" PRIu32
|
||||||
|
" %4" PRIu32 "\n", width, e->name, e->start, e->nblocks,
|
||||||
|
e->size, e->pincount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for (i = 0; i < s->nblocks; i += MAP_COLS)
|
||||||
|
{
|
||||||
|
uint32_t n = s->nblocks - i;
|
||||||
|
char row[MAP_COLS + 1];
|
||||||
|
|
||||||
|
if (n > MAP_COLS)
|
||||||
|
{
|
||||||
|
n = MAP_COLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(row, &s->map[i], n);
|
||||||
|
row[n] = '\0';
|
||||||
|
printf(" %4" PRIu32 " |%s|\n", i, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n '.' free '#' in use 'P' pinned by a live mapping\n");
|
||||||
|
|
||||||
|
/* The share of free space that a single allocation cannot reach. This is
|
||||||
|
* the number that says whether compacting is worth doing: at 0% the
|
||||||
|
* largest possible file already fits, whatever the map looks like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
frag = 0;
|
||||||
|
if (s->freeblocks > 0)
|
||||||
|
{
|
||||||
|
frag = (uint32_t)(((uint64_t)(s->freeblocks - s->largestrun) * 100) /
|
||||||
|
s->freeblocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n used %" PRIu32 ", free %" PRIu32
|
||||||
|
", largest free run %" PRIu32 " blocks (%" PRIu64 " KB)\n",
|
||||||
|
s->used, s->freeblocks, s->largestrun,
|
||||||
|
(uint64_t)s->largestrun * s->blocksize / 1024);
|
||||||
|
printf(" %" PRIu32 " free run%s, fragmentation %" PRIu32 "%%\n",
|
||||||
|
s->nruns, s->nruns == 1 ? "" : "s", frag);
|
||||||
|
|
||||||
|
if (s->pinned > 0)
|
||||||
|
{
|
||||||
|
printf(" %" PRIu32 " block%s pinned and cannot be relocated\n",
|
||||||
|
s->pinned, s->pinned == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frag == 0)
|
||||||
|
{
|
||||||
|
printf(" nothing to gain: all free space is already contiguous\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf(" compacting could raise the largest run to %" PRIu32
|
||||||
|
" blocks (%" PRIu64 " KB)\n", s->freeblocks,
|
||||||
|
(uint64_t)s->freeblocks * s->blocksize / 1024);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: reason_str
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static FAR const char *reason_str(int reason)
|
||||||
|
{
|
||||||
|
switch (reason)
|
||||||
|
{
|
||||||
|
case XIPFS_DEFRAG_DONE:
|
||||||
|
return "done, nothing left to compact";
|
||||||
|
case XIPFS_DEFRAG_BLOCKED_PINS:
|
||||||
|
return "blocked by a live XIP mapping";
|
||||||
|
case XIPFS_DEFRAG_BLOCKED_RAM:
|
||||||
|
return "blocked by a transient resource shortage";
|
||||||
|
case XIPFS_DEFRAG_TIME_BUDGET:
|
||||||
|
return "ran out of the time budget";
|
||||||
|
case XIPFS_DEFRAG_ERROR:
|
||||||
|
return "stopped on a media error";
|
||||||
|
case XIPFS_DEFRAG_BLOCKED_OPEN:
|
||||||
|
return "blocked by an open file";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: run_defrag
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Compact the volume. The ioctl goes through a descriptor for the
|
||||||
|
* mountpoint directory rather than for a file inside the volume: no file
|
||||||
|
* is open, so nothing is held immovable by the act of asking and one pass
|
||||||
|
* can reach every extent.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int run_defrag(FAR const char *mount, FAR struct xipfs_survey_s *s,
|
||||||
|
uint32_t max_ms)
|
||||||
|
{
|
||||||
|
struct xipfs_defrag_arg_s arg;
|
||||||
|
int fd;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (s->nfiles == 0)
|
||||||
|
{
|
||||||
|
printf("\n%s is empty; nothing to compact\n", mount);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&arg, 0, sizeof(arg));
|
||||||
|
arg.max_ms = max_ms;
|
||||||
|
|
||||||
|
fd = open(mount, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: open %s failed: %d\n", mount, errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\ncompacting %s ...\n", mount);
|
||||||
|
|
||||||
|
ret = ioctl(fd, XIPFSIOC_DEFRAG, (unsigned long)(uintptr_t)&arg);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: defrag failed: %d\n", errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" %s: moved %" PRIu32 " extent%s, reclaimed %" PRIu32
|
||||||
|
" block%s, %" PRIu32 " pinned\n", reason_str(arg.result.reason),
|
||||||
|
arg.result.extents_moved,
|
||||||
|
arg.result.extents_moved == 1 ? "" : "s",
|
||||||
|
arg.result.blocks_reclaimed,
|
||||||
|
arg.result.blocks_reclaimed == 1 ? "" : "s",
|
||||||
|
arg.result.blocks_pinned);
|
||||||
|
|
||||||
|
printf(" largest free run now %zu bytes\n",
|
||||||
|
arg.result.largest_free_run);
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Public Functions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
int main(int argc, FAR char *argv[])
|
||||||
|
{
|
||||||
|
FAR const char *mount = CONFIG_SYSTEM_XIPFS_MOUNTPOINT;
|
||||||
|
struct xipfs_survey_s survey;
|
||||||
|
bool dryrun = false;
|
||||||
|
uint32_t max_ms = 0;
|
||||||
|
int option;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
while ((option = getopt(argc, argv, "nt:h")) != ERROR)
|
||||||
|
{
|
||||||
|
switch (option)
|
||||||
|
{
|
||||||
|
case 'n':
|
||||||
|
dryrun = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
ret = parse_max_ms(optarg, &max_ms);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: invalid time budget: %s\n", optarg);
|
||||||
|
show_usage(argv[0], EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
show_usage(argv[0], EXIT_SUCCESS);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
show_usage(argv[0], EXIT_FAILURE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind < argc)
|
||||||
|
{
|
||||||
|
mount = argv[optind];
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = survey_collect(mount, &survey);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
survey_report(mount, &survey);
|
||||||
|
|
||||||
|
if (!dryrun)
|
||||||
|
{
|
||||||
|
ret = run_defrag(mount, &survey, max_ms);
|
||||||
|
survey_free(&survey);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Re-survey so the map shown last is the one on the media now. */
|
||||||
|
|
||||||
|
ret = survey_collect(mount, &survey);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\nafter compaction:\n");
|
||||||
|
survey_report(mount, &survey);
|
||||||
|
}
|
||||||
|
|
||||||
|
survey_free(&survey);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue