From 725a91ea5ee4914a5187f26f18372062733bf036 Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Sun, 23 Dec 2007 20:31:23 +0200 Subject: ubi-utils: move ubi_jffs2_test.sh to better place ... to tests/ubi-tests/ Signed-off-by: Artem Bityutskiy --- tests/ubi-tests/Makefile | 64 +++--- tests/ubi-tests/helpers/ubiupdatevol.c | 335 +++++++++++++++++++++++++++ tests/ubi-tests/io_update.c | 2 +- tests/ubi-tests/ubi_jffs2_test.sh | 407 +++++++++++++++++++++++++++++++++ 4 files changed, 778 insertions(+), 30 deletions(-) create mode 100644 tests/ubi-tests/helpers/ubiupdatevol.c create mode 100755 tests/ubi-tests/ubi_jffs2_test.sh (limited to 'tests') diff --git a/tests/ubi-tests/Makefile b/tests/ubi-tests/Makefile index a2a49d0..a15d93c 100644 --- a/tests/ubi-tests/Makefile +++ b/tests/ubi-tests/Makefile @@ -1,41 +1,47 @@ -UBIUTILS=../../ubi-utils -INCLUDE1=$(UBIUTILS)/inc -INCLUDE2=../../include -LIB=. +LIBUBI_PATH=../../ubi-utils/ +LIBUBI_SRC_PATH=../../ubi-utils/src +LIBUBI_HEADER_PATH=../../ubi-utils/inc +UBIUTILS_PATH=../../ubi-utils/ CC := $(CROSS)gcc -ALL_FILES=libubi io_update rmvol integ -ALL_FILES+=io_paral io_read io_basic mkvol_basic mkvol_bad mkvol_paral rsvol +TESTS=io_update rmvol integ io_paral io_read io_basic \ + mkvol_basic mkvol_bad mkvol_paral rsvol -CFLAGS += -Wall -I$(INCLUDE1) -I$(INCLUDE2) -L$(LIB) -ggdb +HELPER_NAMES=ubiupdatevol +HELPERS=$(addprefix helpers/, $(HELPER_NAMES)) -all: $(ALL_FILES) +# Because of implicite rules we use make treats .o files as intermediate, thus +# it removes the. If you want to prevent the removal, uncomment the below +#.SECONDARY: $(addsuffix .o, $(TESTS)) $(addsuffix .o, $(HELPERS)) -libubi: $(UBIUTILS)/src/libubi.c $(UBIUTILS)/inc/libubi.h $(UBIUTILS)/src/libubi_int.h - $(CC) $(CFLAGS) -DUDEV_SETTLE_HACK -c $(UBIUTILS)/src/libubi.c -o libubi.o +CFLAGS += -Wall -I$(LIBUBI_HEADER_PATH) -L $(LIBUBI_PATH) -O0 -ggdb + +all: ubi-utils libubi $(TESTS) $(HELPERS) + +# Compile ubilib with the udevsettle hack +libubi: $(LIBUBI_SRC_PATH)/libubi.c $(LIBUBI_HEADER_PATH)/libubi.h $(LIBUBI_SRC_PATH)/libubi_int.h + $(CC) $(CFLAGS) -I $(LIBUBI_SRC_PATH) -DUDEV_SETTLE_HACK -c $(LIBUBI_SRC_PATH)/libubi.c -o libubi.o ar cr libubi.a libubi.o -io_paral: io_paral.c common.c - $(CC) $(CFLAGS) $^ -lubi -lpthread -o $@ -io_update: io_update.c common.c - $(CC) $(CFLAGS) $^ -lubi -o $@ -io_read: io_read.c common.c - $(CC) $(CFLAGS) $^ -lubi -o $@ -io_basic: io_basic.c common.c - $(CC) $(CFLAGS) $^ -lubi -o $@ -mkvol_basic: mkvol_basic.c common.c - $(CC) $(CFLAGS) $^ -lubi -o $@ -mkvol_bad: mkvol_bad.c common.c +# The below cancels existing implicite rule to make programs from .c files, +# in order to force make using our rule defined below +%: %.c + +# The below is the rule to get an .o file from a .c file +%.o: %.c + $(CC) $(CFLAGS) $< -c -o $@ + +# And the below is the rule to get final test executable from its .o and common.o +%: %.o common.o $(CC) $(CFLAGS) $^ -lubi -o $@ -mkvol_paral: mkvol_paral.c common.c + +# *paral tests require libpthread, thus the below rule for them +%paral: %paral.o common.o $(CC) $(CFLAGS) $^ -lubi -lpthread -o $@ -rsvol: rsvol.c common.c - $(CC) $(CFLAGS) $^ -lubi -o $@ -rmvol: rmvol.c common.c - $(CC) $(CFLAGS) $^ -lubi -o $@ -integ: integ.c - $(CC) $(CFLAGS) $^ -lubi -o $@ + +ubi-utils: + make -C $(UBIUTILS_PATH) clean: - rm -rf $(ALL_FILES) $(addsuffix .o, $(ALL_FILES)) + rm -f $(TESTS) $(addsuffix .o, $(TESTS)) libubi.* $(HELPERS) $(addsuffix .o, $(HELPERS)) diff --git a/tests/ubi-tests/helpers/ubiupdatevol.c b/tests/ubi-tests/helpers/ubiupdatevol.c new file mode 100644 index 0000000..6863e2a --- /dev/null +++ b/tests/ubi-tests/helpers/ubiupdatevol.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * An utility to update UBI volumes. + * + * Author: Frank Haverkamp + * Joshua W. Boyer + * + * 1.0 Reworked the userinterface to use argp. + * 1.1 Removed argp parsing because we want to use uClib. + * 1.2 Minor cleanups + * 1.3 Use a different libubi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PROGRAM_VERSION "1.3" + +#define MAXPATH 1024 +#define BUFSIZE 128 * 1024 +#define MIN(x,y) ((x)<(y)?(x):(y)) + +struct args { + int devn; + int vol_id; + int truncate; + int broken_update; + int bufsize; + + /* special stuff needed to get additional arguments */ + char *arg1; + char **options; /* [STRING...] */ +}; + +static struct args myargs = { + .devn = -1, + .vol_id = -1, + .truncate = 0, + .broken_update = 0, + .bufsize = BUFSIZE, + .arg1 = NULL, + .options = NULL, +}; + +static int verbose = 0; + +static char doc[] = "\nVersion: " PROGRAM_VERSION "\n" + "ubiupdatevol - write to UBI Volume.\n"; + +static const char *optionsstr = +" -B, --broken-update broken update, this is for testing\n" +" -d, --devn= UBI device\n" +" -n, --vol_id= UBI volume id\n" +" -t, --truncate truncate volume\n" +" -?, --help Give this help list\n" +" --usage Give a short usage message\n" +" -V, --version Print program version\n"; + +static const char *usage = +"Usage: ubiupdatevol [-Bt?V] [-d ] [-n ] [--broken-update]\n" +" [--devn=] [--vol_id=] [--truncate] [--help]\n" +" [--usage] [--version] \n"; + +struct option long_options[] = { + { .name = "broken-update", .has_arg = 0, .flag = NULL, .val = 'B' }, + { .name = "devn", .has_arg = 1, .flag = NULL, .val = 'd' }, + { .name = "vol_id", .has_arg = 1, .flag = NULL, .val = 'n' }, + { .name = "truncate", .has_arg = 0, .flag = NULL, .val = 't' }, + { .name = "help", .has_arg = 0, .flag = NULL, .val = '?' }, + { .name = "usage", .has_arg = 0, .flag = NULL, .val = 0 }, + { .name = "version", .has_arg = 0, .flag = NULL, .val = 'V' }, + { NULL, 0, NULL, 0} +}; + +/* + * @brief Parse the arguments passed into the test case. + */ +static int +parse_opt(int argc, char **argv, struct args *args) +{ + while (1) { + int key; + + key = getopt_long(argc, argv, "Bd:n:t?V", long_options, NULL); + if (key == -1) + break; + + switch (key) { + case 'v': /* --verbose= */ + verbose = strtoul(optarg, (char **)NULL, 0); + break; + + case 'n': /* --vol_id= */ + args->vol_id = strtol(optarg, (char **)NULL, 0); + break; + + case 'd': /* --devn= */ + args->devn = strtol(optarg, (char **)NULL, 0); + break; + + case 'b': /* --bufsize= */ + args->bufsize = strtol(optarg, (char **)NULL, 0); + if (args->bufsize <= 0) + args->bufsize = BUFSIZE; + break; + + case 't': /* --truncate */ + args->truncate = 1; + break; + + case 'B': /* --broken-update */ + args->broken_update = 1; + break; + + case '?': /* help */ + fprintf(stderr, "Usage: " + "ubiupdatevol [OPTION...] \n%s%s\n", + doc, optionsstr); + exit(EXIT_SUCCESS); + break; + + case 'V': + fprintf(stderr, "%s\n", PROGRAM_VERSION); + exit(0); + break; + + default: + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + } + + if (optind < argc) { + /* only one additional argument required */ + args->arg1 = argv[optind++]; + } + return 0; +} + +/** + * @bytes bytes must be always 0, if not 0 this is a testcase for a + * broken volume update where data is promissed to be written, but for + * some reason nothing is written. The volume is unusable after this. + */ +static int +ubi_truncate_volume(struct args *args, int64_t bytes,libubi_t libubi) +{ + int rc, ofd; + char path[MAXPATH]; + int old_errno; + + snprintf(path, MAXPATH-1, "/dev/ubi%d_%d", args->devn, args->vol_id); + path[MAXPATH-1] = '\0'; + + ofd = open(path, O_RDWR); + if (ofd < 0) { + fprintf(stderr, "Cannot open volume %s\n", path); + perror("open"); + exit(EXIT_FAILURE); + } + rc = ubi_update_start(libubi, ofd, bytes); + old_errno = errno; + if (rc < 0) { + perror("UBI volume update ioctl"); + fprintf(stderr, " rc=%d errno=%d\n", rc, old_errno); + exit(EXIT_FAILURE); + } + close(ofd); + return 0; +} + +static ssize_t ubi_write(int fd, const void *buf, size_t count) +{ + int rc; + int len = count; + + while (len) { + rc = write(fd, buf, len); + if (rc == -1) { + if (errno == EINTR) + continue; /* try again */ + perror("write error"); + return rc; + } + + len -= rc; + buf += rc; + } + return count; +} + +static int +ubi_update_volume(struct args *args, libubi_t libubi) +{ + int rc, ofd; + FILE *ifp = NULL; + struct stat st; + int size = 0; + char *fname = args->arg1; + char path[MAXPATH]; + char *buf; + int64_t bytes = 0; + int old_errno; + + buf = malloc(args->bufsize); + if (!buf) { + perror("Out of memory"); + exit(EXIT_FAILURE); + } + + if (fname == NULL) { + fprintf(stderr, "Please specify an existing image file.\n"); + exit(EXIT_FAILURE); + } + + rc = stat(fname, &st); + if (rc < 0) { + fprintf(stderr, "Cannot stat input file %s\n", fname); + exit(EXIT_FAILURE); + } + bytes = size = st.st_size; + + ifp = fopen(fname, "r"); + if (!ifp) + exit(EXIT_FAILURE); + + snprintf(path, MAXPATH-1, "/dev/ubi%d_%d", args->devn, args->vol_id); + path[MAXPATH-1] = '\0'; + + ofd = open(path, O_RDWR); + if (ofd < 0) { + fprintf(stderr, "Cannot open UBI volume %s\n", path); + exit(EXIT_FAILURE); + } + + rc = ubi_update_start(libubi, ofd, bytes); + old_errno = errno; + if (rc < 0) { + perror("UBI volume update ioctl"); + fprintf(stderr, " rc=%d errno=%d\n", rc, old_errno); + exit(EXIT_FAILURE); + } + + while (size > 0) { + ssize_t tocopy = MIN(args->bufsize, size); + + rc = fread(buf, tocopy, 1, ifp); + if (rc != 1) { + perror("Could not read everything."); + exit(EXIT_FAILURE); + } + + rc = ubi_write(ofd, buf, tocopy); + old_errno = errno; + if (rc != tocopy) { + perror("Could not write to device"); + fprintf(stderr, " rc=%d errno=%d\n", rc, old_errno); + exit(EXIT_FAILURE); + } + size -= tocopy; + } + + free(buf); + fclose(ifp); + rc = close(ofd); + if (rc != 0) { + perror("UBI volume close failed"); + exit(EXIT_FAILURE); + } + return 0; +} + +int +main(int argc, char *argv[]) +{ + int rc; + libubi_t libubi; + + parse_opt(argc, argv, &myargs); + + libubi = libubi_open(); + if (libubi == NULL) { + perror("Cannot open libubi"); + return -1; + } + + if (myargs.truncate) { + rc = ubi_truncate_volume(&myargs, 0LL, libubi); + if (rc < 0) + goto out_libubi; + } + else if (myargs.broken_update) { + rc = ubi_truncate_volume(&myargs, 1LL, libubi); + if (rc < 0) + goto out_libubi; + } else { + rc = ubi_update_volume(&myargs, libubi); + if (rc < 0) + goto out_libubi; + } + + libubi_close(libubi); + return 0; + +out_libubi: + libubi_close(libubi); + return -1; +} diff --git a/tests/ubi-tests/io_update.c b/tests/ubi-tests/io_update.c index 543cb8c..4f8a374 100644 --- a/tests/ubi-tests/io_update.c +++ b/tests/ubi-tests/io_update.c @@ -185,7 +185,7 @@ static int test_update1(struct ubi_vol_info *vol_info) } for (i = 0; i < SEQ_SZ; i++) { - int ret, stop = 0, len; + int ret, stop = 0, len = 0; off_t off = 0; unsigned char buf1[vol_info->rsvd_bytes]; diff --git a/tests/ubi-tests/ubi_jffs2_test.sh b/tests/ubi-tests/ubi_jffs2_test.sh new file mode 100755 index 0000000..a6172ba --- /dev/null +++ b/tests/ubi-tests/ubi_jffs2_test.sh @@ -0,0 +1,407 @@ +#!/bin/sh +# +# UBI Volume creation/deletion/write/read and JFFS2 on top of UBI +# testcases. +# +# Written in shell language to reduce dependencies to more sophisticated +# interpreters, which may not be available on some stupid platforms. +# +# Author: Frank Haverkamp +# +# 1.0 Initial version +# 1.1 Added fixup for delayed device node creation by udev +# This points to a problem in the tools, mabe in the desing +# Tue Oct 31 14:14:54 CET 2006 +# + +# Make sure we have UBI utilities in the PATH +export PATH=../../ubi-utils:$PATH + +VERSION="1.1" +ITERATIONS=250 +ALIGNMENT=2048 + +UBIMKVOL="ubimkvol" +UBIRMVOL="ubirmvol" +# This test script uses special program to update volumes +UBIUPDATEVOL="helpers/ubiupdatevol" + +SIZE_512K=524288 +SIZE_1M=1310720 + +MINVOL=10 +MAXVOL=12 + +TLOG=/dev/null + +# +# To have a standardized output I define the following function to be +# used when a test was ok or when it failed. +# +failed () +{ + echo "FAILED" +} + +passed () +{ + echo "PASSED" +} + +# +# Print sucess message. Consider to exit with zero as return code. +# +exit_success () +{ + echo "SUCCESS" + exit 0 +} + +# +# Print failure message. Consider to exit with non zero return code. +# +exit_failure () +{ + echo "FAILED" + exit 1 +} + +############################################################################### +# +# START +# +############################################################################### + +fix_sysfs_issue () +{ + echo "*** Fixing the sysfs issue with the /dev nodes ... " + + minor=0 + major=`grep ubi0 /proc/devices | sed -e 's/\(.*\) ubi0/\1/'` + + rm -rf /dev/ubi0 + mknod /dev/ubi0 c $major 0 + + for minor in `seq $MINVOL $MAXVOL`; do + echo " -> mknod /dev/ubi0_$minor c $major $(($minor + 1))" + rm -rf /dev/ubi0_$minor + mknod /dev/ubi0_$minor c $major $(($minor + 1)) + done + passed +} + +# +# Wait for while udev creates device nodes +# +udev_wait () +{ + udevsettle || sleep 2; +} + +# delete_volume - Delete a volume. If it does not exist, do not try +# to delete it. +# @id: volume id +# +delete_volume () +{ + volume=$1 + + ### FIXME broken sysfs!!!! + if [ -e /sys/class/ubi/$volume -o \ + -e /sys/class/ubi/ubi0/$volume -o \ + -e /sys/class/ubi/ubi0_$volume ]; then + + echo "*** Truncate volume if it exists ... " + echo " $UBIUPDATEVOL -d0 -n$volume -t" + $UBIUPDATEVOL -d0 -n$volume -t + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo -n "*** Delete volume if it exists ... " + $UBIRMVOL $UBI_DEVICE -n$volume + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + # udev_wait + fi +} + +# writevol_test - Tests volume creation and writing data to it. +# +# @volume: Volume number +# @size: Size of random data to write +# @type: Volume type static or dynamic +# +writevol_test () +{ + volume=$1 + size=$2 + type=$3 + + echo "*** Write volume test with size $size" + +### Make sure that volume exist, delete existing volume, create new + + delete_volume $volume + + echo "*** Try to create volume" + echo " $UBIMKVOL $UBI_DEVICE -a $ALIGNMENT -n$volume -t$type -NNEW$volume -s $size ... " + $UBIMKVOL $UBI_DEVICE -n$volume -t$type -N"NEW$volume" -s $size + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + udev_wait + +### Try to create same volume again + echo -n "*** Try to create some volume again, this must fail ... " + $UBIMKVOL $UBI_DEVICE -a $ALIGNMENT -n$volume -t$type -N"NEW$volume" -s $size + if [ $? -eq "0" ] ; then + exit_failure + fi + passed + +### Now create test data, write it, read it, compare it + echo -n "*** Create test data ... " + dd if=/dev/urandom of=testdata.bin bs=$size count=1 + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo "*** Now writing data to volume ... " + echo " $UBIUPDATEVOL -d0 -n$volume testdata.bin" + ls -l testdata.bin + $UBIUPDATEVOL -d0 -n$volume testdata.bin + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo "*** Download data with dd bs=1 ... " + dd if=/dev/ubi0_$volume of=readdata.bin bs=$size count=1 + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo -n "*** Comparing data ... " + cmp readdata.bin testdata.bin + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo -n "*** Now truncate volume ... " + $UBIUPDATEVOL -d0 -n$volume -t + if [ $? -ne "0" ] ; then + exit_failure + fi + passed +} + +jffs2_torture () +{ + cat /dev/null > TLOG + + echo "*** Torture test ... " + + for i in `seq $iterations`; do + dd if=/dev/urandom of=test.bin bs=$i count=1 2>> $TLOG + if [ $? -ne "0" ] ; then + echo "Testing $i byte (dd if=/dev/urandom of=foo bs=$i count=1) ... " + exit_failure + fi + #passed + + dd if=test.bin of=new.bin bs=$i count=1 2>> $TLOG + if [ $? -ne "0" ] ; then + echo "dd if=test.bin of=new.bin bs=$i count=1 2>> $TLOG" + exit_failure + fi + #passed + + #echo "Comparing files ... " + cmp test.bin new.bin + dd if=test.bin of=new.bin bs=$i count=1 2>> $TLOG + if [ $? -ne "0" ] ; then + exit_failure + fi + #passed + #echo -n "." + done + + echo -n "step0:ok " + + for i in `seq $iterations`; do + dd if=/dev/urandom of=foo bs=$i count=1 2>> $TLOG + if [ $? -ne "0" ] ; then + echo "Testing $i byte (dd if=/dev/urandom of=foo bs=$i count=1) ... " + exit_failure + fi + #passed + done + + echo -n "step1:ok " + + for i in `seq $iterations`; do + dd if=/dev/zero of=foo bs=1 count=$i 2>> $TLOG + if [ $? -ne "0" ] ; then + echo "Testing $i byte (dd if=/dev/zero of=foo bs=1 count=$i) ... " + exit_failure + fi + #passed + done + + echo -n "step2:ok " + + for i in `seq $iterations`; do + dd if=/dev/zero of=foo bs=$i count=16 2>> $TLOG + if [ $? -ne "0" ] ; then + echo "Testing $i byte (dd if=/dev/zero of=foo bs=$i count=1024) ... " + exit_failure + fi + #passed + done + + echo -n "step3:ok " + + passed +} + +# writevol_test - Tests volume creation and writing data to it. +# +# @volume: Volume number +# @size: Size of random data to write +# @type: Volume type static or dynamic +# +jffs2_test () +{ + name=$1 + iterations=$2 + directory=`pwd` + + ### Setup + ulimit -c unlimited + + echo -n "*** Create directory /mnt/$name ... " + mkdir -p /mnt/$name + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo -n "*** mount -t jffs2 mtd:$name /mnt/$name ... " + mount -t jffs2 mtd:$name /mnt/$name + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + echo -n "*** change directory ... " + cd /mnt/$name + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + ls + echo "*** list directory ... " + ls -la + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + ### Torture + echo -n "*** touch I_WAS_HERE ... " + touch I_WAS_HERE + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + jffs2_torture + + echo "*** list directory ... " + ls -la + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + ### Cleanup + echo -n "*** go back ... " + cd $directory + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + ### Still mounted, ubiupdatevol must fail! + + echo -n "*** $UBIUPDATEVOL -d0 -n$volume -t must fail! ..." + $UBIUPDATEVOL -d0 -n$volume -t + if [ $? -eq "0" ] ; then + exit_failure + fi + passed + + echo -n "*** umount /mnt/$name ... " + umount /mnt/$name + if [ $? -ne "0" ] ; then + exit_failure + fi + passed + + return +} + +echo "***********************************************************************" +echo "* UBI JFFS2 Testing starts now ... *" +echo "* Good luck! *" +echo "***********************************************************************" +echo "VERSION: $VERSION" + +# Set to zero if not running on example hardware +grep ubi /proc/devices > /dev/null +if [ $? -ne "0" ]; then + echo "No UBI found in /proc/devices! I am broken!" + exit_failure +fi + +if [ "x$1" == "x" ]; then + echo "Please, specify ubi device to test, e.g., $0 /dev/ubi0"; + exit_failure; +fi + +UBI_DEVICE=$1 + +# Set to zero if not running on example hardware +grep 1142 /proc/cpuinfo > /dev/null +if [ $? -eq "0" ]; then + echo "Running on example hardware" + mount -o remount,rw / / + sleep 1 + fix_sysfs_issue +else + echo "Running on Artems hardware" +fi + +for volume in `seq $MINVOL $MAXVOL`; do + echo -n "************ VOLUME $volume NEW$volume " + echo "******************************************" + writevol_test $volume $SIZE_1M dynamic + jffs2_test NEW$volume $ITERATIONS + delete_volume $volume +done + +echo "***********************************************************************" +echo "* Congratulations, no errors found! *" +echo "* Have fun with your cool UBI system! *" +echo "***********************************************************************" + +exit_success -- cgit v1.2.3