yaff2util的解析1--mkyaffs2工具

下载源码

目前在网络上还是以google提供的为主,其他的看上去好像不维护的样子,当然如果有读者看到的话,请帮忙提供一下。比这目前参考的是gitee上同步下来的code.google.com/p/yaffs2utils。目前笔者的VPN到期,只能拿别人的代码来分析了。当然放心,分析完,我也将源码附上。

编译

编译还是超级简单的。直接在项目根目录下make all即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一般来说会生成3个重要的tool,分别是mkyaffs2、unspare2、unyaffs2

mkyaffs2的工具解析

先来看一下Makefile是杂么编译的。

mkyaffs2: $(YAFFS2OBJS) $(LIBOBJS) $(MKYAFFS2OBJS)
	$(CC) -o $@ $(YAFFS2OBJS) $(LIBOBJS) $(MKYAFFS2OBJS) $(LDFLAGS)

其需要依赖:

YAFFS2SRCS	= yaffs2/yaffs_hweight.c yaffs2/yaffs_ecc.c \
		  yaffs2/yaffs_packedtags1.c yaffs2/yaffs_packedtags2.c
YAFFS2OBJS	= $(YAFFS2SRCS:.c=.o)

LIBSRCS		= safe_rw.c endian_convert.c progress_bar.c
LIBOBJS		= $(LIBSRCS:.c=.o)

MKYAFFS2SRCS	= mkyaffs2.c
MKYAFFS2OBJS	= $(MKYAFFS2SRCS:.c=.o)

LDFLAGS		+= -lm

于是基本上可以确认其主要依赖的一些C文件也就出来了,分别是yaffs_hweight.c、 yaffs_ecc.c、yaffs_packedtags1.c、yaffs_packedtags2.c、safe_rw.c、endian_convert.c、progress_bar.c、mkyaffs2.c

main函数

先可以看到getopt_long的一些定义。

	static const char *short_options = "hvep:s:o:";
	static const struct option long_options[] = {
		{"pagesize", 		required_argument, 	0, 'p'},
		{"sparesize", 		required_argument, 	0, 's'},
		{"oobimg",		required_argument,	0, 'o'},
		{"endian", 		no_argument, 		0, 'e'},
		{"verbose", 		no_argument, 		0, 'v'},
		{"all-root",		no_argument,		0, '0'},
		{"yaffs-ecclayout",	no_argument,		0, 'y'},
		{"help", 		no_argument, 		0, 'h'},
		{NULL,			no_argument,		0, '\0'},
	};

这边先复习一下getopt_long

getopt_long

#include <unistd.h>  
extern char *optarg;  
extern int optind, opterr, optopt;  
#include <getopt.h>
int getopt(int argc, char * const argv[],const char *optstring);  
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); 

1.argc和argv和main函数的两个参数一致。
2.optstring: 表示短选项字符串

形式如a🅱️💿,分别表示程序支持的命令行短选项有-a、-b、-c、-d,冒号含义如下:
(1)只有一个字符,不带冒号——只表示选项, 如-c
(2)一个字符,后接一个冒号——表示选项后面带一个参数,如-a 100
(3)一个字符,后接两个冒号——表示选项后面带一个可选参数,即参数可有可无,如果带参数,则选项与参数直接不能有空格如-b200

3.longopts 表示长选项结构体。
struct option 
{  
     const char *name;  
     int         has_arg;  
     int        *flag;  
     int         val;  
}; 

(1)name:表示选项的名称,比如daemon,dir,out等。

(2)has_arg:表示选项后面是否携带参数。该参数有三个不同值,如下:

a: no_argument(或者是0)时 ——参数后面不跟参数值,eg: --version,–help
b: required_argument(或者是1)时 ——参数输入格式为:–参数 值 或者 --参数=值。eg:–dir=/home
c: optional_argument(或者是2)时 ——参数输入格式只能为:–参数=值

(3)flag:这个参数有两个意思,空或者非空。

a:如果参数为空NULL,那么当选中某个长选项的时候,getopt_long将返回val值。eg:可执行程序 --help,getopt_long的返回值为h.
b:如果参数不为空,那么当选中某个长选项的时候,getopt_long将返回0,并且将flag指针参数指向val值。eg: 可执行程序 --http-proxy=127.0.0.1:80 那么getopt_long返回值为0,并且lopt值为1。

(4)val:表示指定函数找到该选项时的返回值,或者当flag非空时指定flag指向的数据的值val。

4.longindex:longindex非空,它指向的变量将记录当前找到参数符合longopts里的第几个元素的描述,即是longopts的下标值。
5.全局变量:

(1)optarg:表示当前选项对应的参数值。

(2)optind:表示的是下一个将被处理到的参数在argv中的下标值。

(3)opterr:如果opterr = 0,在getopt、getopt_long、getopt_long_only遇到错误将不会输出错误信息到标准输出流。opterr在非0时,向屏幕输出错误。

(4)optopt:表示没有被未标识的选项。

6.返回值:

(1)如果短选项找到,那么将返回短选项对应的字符。

(2)如果长选项找到,如果flag为NULL,返回val。如果flag不为空,返回0

(3)如果遇到一个选项没有在短字符、长字符里面。或者在长字符里面存在二义性的,返回“?”

(4)如果解析完所有字符没有找到(一般是输入命令参数格式错误,eg: 连斜杠都没有加的选项),返回“-1”

(5)如果选项需要参数,忘了添加参数。返回值取决于optstring,如果其第一个字符是“:”,则返回“:”,否则返回“?”。

参数的使用

OK,现在主要搞清楚了一些参数配置问题哦,那我们来解释一下参数。首先我们来看一下美光的MT29F1G的128MB的flash。

在yaffs2中把基本的存储单位称为chunk,其实它跟page是一样大小的,在大多数情况下其和page就是一个意思。那么这边的参数就要注意了,千万不要将这个128bytes算进chunk去,我们的chunk一直都是2K=2048,128字节是缓存区。

mkyaffs2_chunksize = DEFAULT_CHUNKSIZE;

DEFAULT_CHUNKSIZE就是2048,所以在这边就用配置-p这个选项了。

1.先检查参数够不够
	if (argc - optind < 2)
		return mkyaffs2_helper();

一定要有一个文件目录,一个image的名字

2.检查是否有root权限
	if (getuid() != 0) {
		mkyaffs2_flags |= MKYAFFS2_FLAGS_NONROOT;
		MKYAFFS2_WARN("warning: non-root users.\n");
	}
3.是否有oob分区

有些平台需要刷入带OOB的外置分区,也就是ECC的分区,特别是各个平台上有所不同,这块的话,后续遇到的话,我将在这个地方继续更新一下。

那么-o也就不需要我们来关心

4.oob layout配置

在这边如果没有特殊的要求,也就是要使用yaffs2的ecclayout,一般我们默认使用nand的oob

	switch (mkyaffs2_chunksize) {
	case 512:
		mkyaffs2_flags |= MKYAFFS2_FLAGS_YAFFS1;
		mkyaffs2_assemble_ptags = &mkyaffs2_assemble_ptags1;
		if (oobfile == NULL)
			mkyaffs2_ecclayout = &nand_oob_16;
		break;
	case 2048:
		if (mkyaffs2_ecclayout == NULL)
			mkyaffs2_ecclayout = MKYAFFS2_ISYAFFSECC ?
					     &yaffs_nand_oob_64 : &nand_oob_64;
		break;
            ...

直接选择的是2048也就是2K,那么此时你需要关注的是nand_ecclayout.h

static nand_ecclayout_t nand_oob_64 = {
	.eccbytes	= 24,
	.eccpos		= {40, 41, 42, 43, 44, 45, 46, 47,
			   48, 49, 50, 51, 52, 53, 54, 55,
			   56, 57, 58, 59, 60, 61, 62, 63},
	.oobfree	= {{.offset = 2, .length = 38}},
};

static nand_ecclayout_t yaffs_nand_oob_64 = {
	.eccbytes	= 24,
	.eccpos		= {40, 41, 42, 43, 44, 45, 46, 47,
			   48, 49, 50, 51, 52, 53, 54, 55,
			   56, 57, 58, 59, 60, 61, 62, 63},
	.oobfree	= {{.offset = 0, .length = 40}},
};

很明显 -y在默认情况下,我们也不会使用

检查交换空间

mkyaffs2_sparesize这个笔者自己也不清楚是干什么用的,不过看上去像是交换空间,对于美光来讲只有128个字节,也就是前面所说的缓冲区128字节。看来-s是要用上了,一般默认的交换空间是chunksize的32分之一,而美光的是128.当然这边我不太确定,我这边理解的对不对。

mkyaffs2_create_image

这个函数是制作image的关键。下面就是要分析这个函数具体又是如何工作的。

函数原型如下:

static int mkyaffs2_create_image (const char *dirpath, const char *imgfile)

检查参数
if (stat(dirpath, &statbuf) < 0 && !S_ISDIR(statbuf.st_mode)) {
		MKYAFFS2_ERROR("ROOT is not a directory '%s'.\n", dirpath);
		return -1;
	}

在这边其实还是很学到不少东西的。stat(dirpath, &statbuf)将目录文件路径放到stat结构体里面,然后判断这个inode是不是目录。用stat函数来检查文件路径是否为目录或者类似于块设备,字符设备等等,这点是以后笔者需要学习的地方。

这边有个博客写得还行。介绍了这个。

allocate root obj first 建立根目录

其代码为:

	/* allocate root obj first */
	root = mkyaffs2_obj_alloc();
	if (root == NULL) {
		MKYAFFS2_ERROR("allocate object failed for '%s': %s.\n",
				dirpath, strerror(errno));
		return -1;
	}

我们看其源码:

static struct mkyaffs2_obj *
mkyaffs2_obj_alloc (void)
{
	struct mkyaffs2_obj *obj;

	obj = calloc(sizeof(struct mkyaffs2_obj), sizeof(unsigned char));//初始化内存
	if (obj == NULL)
		return NULL;

	obj->parent_obj = obj;

	INIT_LIST_HEAD(&obj->hashlist);
	INIT_LIST_HEAD(&obj->children);
	INIT_LIST_HEAD(&obj->siblings);

	return obj;
}

其mkyaffs2_obj结构体是我们要关注的。先贴出,后面一点点分析

typedef struct mkyaffs2_obj {
	dev_t dev;
	ino_t ino;

	unsigned obj_id;
	struct mkyaffs2_obj *parent_obj;

	unsigned type;

	char name[NAME_MAX + 1];

	struct list_head children;	/* for a directory */
	struct list_head siblings;	/* neighbors in the same directory */
	struct list_head hashlist;	/* hash table */
} mkyaffs2_obj_t;

mkyaffs2_obj_alloc这边已经将数据列表全部初始化,INIT_LIST_HEAD将所有的链表都指向于自己。其实只要对于内核有一定认知的,都会有各种指向于列表本身嘛。

table initiailzation

其主要是这两个函数做文件序列

mkyaffs2_objtable_init();
mkyaffs2_objtree_init2(&mkyaffs2_objtree, root);

静态全局变量与相关的结构体

typedef struct mkyaffs2_fstree {
	unsigned objs;
	struct mkyaffs2_obj *root;
} mkyaffs2_fstree_t;
static struct mkyaffs2_fstree mkyaffs2_objtree = {0};
static struct list_head mkyaffs2_objtable[MKYAFFS2_OBJTABLE_SIZE];

mkyaffs2_objtable_init主要初始化mkyaffs2_objtable

static inline void
mkyaffs2_objtable_init (void)
{
	unsigned n;

	for (n = 0; n < MKYAFFS2_OBJTABLE_SIZE; n++)
		INIT_LIST_HEAD(&mkyaffs2_objtable[n]);
}

mkyaffs2_objtree_init2主要先把全局变量初始化后,给他们相关的内存空间,并绑定根目录

static struct mkyaffs2_fstree *
mkyaffs2_objtree_init (struct mkyaffs2_fstree *fst)
{
	struct mkyaffs2_fstree *f = fst;

	if (f == NULL) {
		f = malloc(sizeof(struct mkyaffs2_fstree));
		if (f == NULL)
			return NULL;
	}

	/* initialize */
	memset(f, 0, sizeof(struct mkyaffs2_fstree));

	return f;
}


static struct mkyaffs2_fstree *
mkyaffs2_objtree_init2 (struct mkyaffs2_fstree *fst, struct mkyaffs2_obj *root)
{
	struct mkyaffs2_fstree *f = fst;

	f = mkyaffs2_objtree_init(f);
	if (f && root) {
		f->root = root;
		f->objs++;
	}

	return f;
}
allocate working buffer

下面就是要提供一个buffer用于写数据了

	mkyaffs2_bufsize = mkyaffs2_chunksize + mkyaffs2_sparesize;
	mkyaffs2_databuf = (unsigned char *)malloc(mkyaffs2_bufsize);
	if (mkyaffs2_databuf == NULL) {
		MKYAFFS2_ERROR("cannot allocate working buffer (%u bytes): %s",
				mkyaffs2_chunksize + mkyaffs2_sparesize,
				strerror(errno));
		retval = -1;
		goto exit_and_out;
	}
	
	mkyaffs2_image_fd = open(imgfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);//打开我们要生成的image.bin
	if (mkyaffs2_image_fd < 0) {
		MKYAFFS2_ERROR("cannot open the image file: '%s'.\n", imgfile);
		retval = -1;
		goto free_and_out;
	}

后面开始正式创建yaffs2了

stage 1: scanning direcotry
snprintf(mkyaffs2_curfile, PATH_MAX, "%s", dirpath);
MKYAFFS2_PRINTF("\n");
MKYAFFS2_PRINTF("stage 1: scanning directory '%s'... [*]",mkyaffs2_curfile);
retval = mkyaffs2_scan_dir(mkyaffs2_objtree.root);//开始扫描整个待包入的文件夹
if (retval < 0)
		goto free_and_out;
MKYAFFS2_PRINTF("\b\b\b[done]\nscanning complete, total objects: %u.\n",
			mkyaffs2_objtree.objs);

其主要的函数是mkyaffs2_scan_dir

static int mkyaffs2_scan_dir (struct mkyaffs2_obj *parent)
{
	int retval = 0;
	DIR *dir;
	struct stat s;
	struct dirent *dent;
	struct mkyaffs2_obj *obj = NULL;

	dir = opendir(mkyaffs2_curfile[0] == '\0' ? "." : mkyaffs2_curfile);//这边如果你没有输入文件名的话,是直接在当前目录下
	if (dir == NULL) {
		MKYAFFS2_ERROR("cannot open dir '%s': %s.",
				mkyaffs2_curfile, strerror(errno));
		return -1;
	}

	while(!retval && (dent = readdir(dir)) != NULL) {
		if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))//这边是当前目录和上级目录是跳过
			continue;

		if (mkyaffs2_curfile[0] != '\0' && mkyaffs2_curfile[strlen(mkyaffs2_curfile) - 1] != '/')//这边是看是否有目录名的,且最后一个目录名没有写“/“这里直接追加。
			strncat(mkyaffs2_curfile, "/", 
				sizeof(mkyaffs2_curfile) -
				strlen(mkyaffs2_curfile) - 1);

		strncat(mkyaffs2_curfile, dent->d_name,
			sizeof(mkyaffs2_curfile) - strlen(mkyaffs2_curfile) - 1);

		obj = mkyaffs2_obj_alloc();
		if (obj == NULL) {
			MKYAFFS2_ERROR("allocate object failed for '%s': %sn\n",
					mkyaffs2_curfile, strerror(errno));
			return -1;
		}

		strncpy(obj->name, dent->d_name, NAME_MAX);
		obj->parent_obj = parent;
		list_add_tail(&obj->siblings, &parent->children);//建立列表

		mkyaffs2_scan_dir_status(++mkyaffs2_objtree.objs);

		if (!lstat(mkyaffs2_curfile, &s) && S_ISDIR(s.st_mode))
			retval = mkyaffs2_scan_dir(obj);

		if (!strcmp(dirname(mkyaffs2_curfile), "."))
			mkyaffs2_curfile[0] = '\0';
	}

	closedir(dir);

	return retval;
}
stage 2: making a image
	MKYAFFS2_PRINTF("\n");
	MKYAFFS2_PRINTF("stage 2: creating image '%s'\n", imgfile);

	MKYAFFS2_PROGRESS_INIT();

	snprintf(mkyaffs2_curfile, PATH_MAX, "%s", dirpath);
	retval = mkyaffs2_assemble_objtree(mkyaffs2_objtree.root);

这边最要紧的还是mkyaffs2_assemble_objtree这个函数,这个函数的套路大约是自己再调自己直到把目录内的所有文件转换成obj,然后调用mkyaffs2_write_obj,随后调用mkyaffs2_write_oh将数据写到mkyaffs2_databuf中,注意哦,这边的内存部分是直接赋值0xff的,为撒,flash空数据时就是全ff的状态,最后调用mkyaffs2_write_chunk的safe_write,将数据写到image.bin中。

static int
mkyaffs2_assemble_objtree (struct mkyaffs2_obj *obj)
{
	int retval = 0;
	struct stat s;

	struct list_head *p;
	struct mkyaffs2_obj *child;

	enum yaffs_obj_type type = YAFFS_OBJECT_TYPE_UNKNOWN;
	static const char *type_str[] = {"????", "FILE", "SLNK", "DIR", "HLNK",
					 "CHR", "BLK", "FIFO", "SOCK"};
	/* root object *///检查根目录是否合规
	if (obj == mkyaffs2_objtree.root) {
		if (stat(mkyaffs2_curfile[0] == '\0' ?
			 "." : mkyaffs2_curfile, &s) < 0 ||
		    !S_ISDIR(s.st_mode)) {
			MKYAFFS2_ERROR("ROOT is NOT a directory '%s' "
				       "(permission denied?)\n",
					mkyaffs2_curfile);
			return -1;
		}

		obj->dev = s.st_dev;
		obj->ino = s.st_ino;
		obj->obj_id = YAFFS_OBJECTID_ROOT;
		obj->type = YAFFS_OBJECT_TYPE_DIRECTORY;

		mkyaffs2_objtable_insert(obj);//加入文件表内

		goto next;
	}

	if (!strlen(obj->name)) {
		/* it should NOT happen! */
		MKYAFFS2_DEBUG("skip obj with empty name\n");
		return 0;
	}

	/* format file path */
	if (mkyaffs2_curfile[0] != '\0' &&
	    mkyaffs2_curfile[strlen(mkyaffs2_curfile) - 1] != '/')
		strncat(mkyaffs2_curfile, "/",
			sizeof(mkyaffs2_curfile) -
			strlen(mkyaffs2_curfile) - 1);

	strncat(mkyaffs2_curfile, obj->name,
		sizeof(mkyaffs2_curfile) - strlen(mkyaffs2_curfile) - 1);

	MKYAFFS2_VERBOSE("NOW: '%s'. ", mkyaffs2_curfile);

	retval = mkyaffs2_write_obj(mkyaffs2_curfile, obj);
	if (!retval &&
	    obj->type != YAFFS_OBJECT_TYPE_HARDLINK &&
	    obj->type != YAFFS_OBJECT_TYPE_UNKNOWN)
		mkyaffs2_objtable_insert(obj);

next:
	if (retval) {
		MKYAFFS2_ERROR("object %u: [%4s] '%s' (FAILED).\n",
				obj->obj_id, type_str[type], mkyaffs2_curfile);
	}
	else {
		mkyaffs2_image_objs++;
		MKYAFFS2_PROGRESS_BAR(mkyaffs2_image_objs,
				      mkyaffs2_objtree.objs);

		MKYAFFS2_VERBOSE("\robject %u: [%4s] '%s'%s.\n",
				  obj->obj_id, type_str[obj->type], mkyaffs2_curfile,
				  obj->type == YAFFS_OBJECT_TYPE_UNKNOWN ? 
				  " (skip)" : "");

		if (obj->type == YAFFS_OBJECT_TYPE_DIRECTORY) {
			list_for_each(p, &obj->children) {
				child = list_entry(p, mkyaffs2_obj_t, siblings);
				retval = mkyaffs2_assemble_objtree(child);
				if (retval)
					break;
			}
		}
	}

	/* restore current file path */
	if (!strcmp(dirname(mkyaffs2_curfile), "."))
		mkyaffs2_curfile[0] = '\0';

	return retval;
}

列举调用了一些代码。感觉实在是太多没必要一一列举了。

static inline unsigned
mkyaffs2_objtable_hash (unsigned hash)
{
	return hash % MKYAFFS2_OBJTABLE_SIZE;
}

static inline void
mkyaffs2_objtable_insert (struct mkyaffs2_obj *object)
{
	unsigned n = mkyaffs2_objtable_hash(object->ino);
	list_add_tail(&object->hashlist, &mkyaffs2_objtable[n]);
}

static int
mkyaffs2_write_obj (const char *fpath, struct mkyaffs2_obj *obj)
{
	int retval = 0;
	ssize_t r;
	struct stat s;

	struct mkyaffs2_obj *equiv_obj;
	struct yaffs_obj_hdr oh;

	memset(&oh, 0xff, sizeof(struct yaffs_obj_hdr));

	retval = lstat(mkyaffs2_curfile, &s);
	if (retval) {
		MKYAFFS2_DEBUG("obtain attribute failed: %s.\n",
				strerror(errno));
		return retval;
	}

	/* update the obj */
	obj->dev = s.st_dev;
	obj->ino = s.st_ino;

	/* hardlink? */
	equiv_obj = mkyaffs2_objtable_find(obj->dev, obj->ino);
	if (equiv_obj) {
		obj->type = YAFFS_OBJECT_TYPE_HARDLINK;
		oh.equiv_id = equiv_obj->obj_id;
		goto write_obj;
	}

	switch (s.st_mode & S_IFMT) {
	case S_IFREG:
		obj->type = YAFFS_OBJECT_TYPE_FILE;
		oh.file_size_low = s.st_size & 0xFFFFFFFF;
#if __WORDSIZE == 64 || !defined __USE_FILE_OFFSET64
		oh.file_size_high = 0xffffffff;
#else
		oh.file_size_high = (s.st_size >> 32) & 0xFFFFFFFF;
#endif
		break;
	case S_IFLNK:
		obj->type = YAFFS_OBJECT_TYPE_SYMLINK;

		memset(oh.alias, 0, sizeof(oh.alias));

		r = readlink((char *)fpath, oh.alias, sizeof(oh.alias));
		if (r < 0) {
			MKYAFFS2_ERROR("read symbol link failed: %s\n",
					strerror(errno));
			return -1;
		}
		else if (r == sizeof(oh.alias)) {
			MKYAFFS2_ERROR("symbolic link is too long (max: %u)",
					(unsigned)sizeof(oh.alias) - 1);
			return -1;
		}
		break;
	case S_IFDIR:
		obj->type = YAFFS_OBJECT_TYPE_DIRECTORY;
		break;
	case S_IFCHR:
		obj->type = YAFFS_OBJECT_TYPE_CHR;
		break;
	case S_IFBLK:
		obj->type = YAFFS_OBJECT_TYPE_BLK;
		break;
	case S_IFIFO:
		obj->type = YAFFS_OBJECT_TYPE_FIFO;
		break;
	case S_IFSOCK:
		obj->type = YAFFS_OBJECT_TYPE_SOCK;
		break;
	default:
		/* skipping un-supported files silently */
		obj->type = YAFFS_OBJECT_TYPE_UNKNOWN;
		MKYAFFS2_DEBUG("skipped (unsupported file)\n");
		return 0;
	}

write_obj:
	oh.parent_obj_id = obj->parent_obj->obj_id;
	strncpy(oh.name, obj->name, YAFFS_MAX_NAME_LENGTH);
	oh.type = (obj->type > YAFFS_OBJECT_TYPE_SPECIAL) ?
			YAFFS_OBJECT_TYPE_SPECIAL : obj->type;
	
	if (oh.type != YAFFS_OBJECT_TYPE_HARDLINK) {
		if (MKYAFFS2_ISALLROOT) {
			oh.yst_uid = 0;
			oh.yst_gid = 0;
		}
		else {
			oh.yst_uid = s.st_uid;
			oh.yst_gid = s.st_gid;
		}
		oh.yst_mode = s.st_mode;
		oh.yst_atime = s.st_atime;
		oh.yst_mtime = s.st_mtime;
		oh.yst_ctime = s.st_ctime;
		oh.yst_rdev  = s.st_rdev;
	}

	obj->obj_id = ++mkyaffs2_image_obj_id;

	if (obj->obj_id > YAFFS_MAX_OBJECT_ID)
		MKYAFFS2_WARN("warning: too many files\n");

	retval = mkyaffs2_write_oh(&oh, obj);

	if (obj->type == YAFFS_OBJECT_TYPE_FILE && !retval)
		retval = mkyaffs2_write_regfile(fpath, obj);

	return retval;
}
释放文件描述符并退出

free_and_out:
	if (mkyaffs2_image_fd >= 0)
		close(mkyaffs2_image_fd);
	free(mkyaffs2_databuf);
exit_and_out:
	mkyaffs2_objtree_exit(&mkyaffs2_objtree);
	mkyaffs2_objtable_exit();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值