static unsigned int walk_directory(struct imgspec* const spec,
	struct hostprog_path* const path, struct entry** previous)
{
	struct dirent** dirlst;
	int dirlst_count;
	int dirlst_index;
	
	__u64 dir_sz = 0;
	__u64 dir_lvl = hostprog_path_lvls(path);
	
	dirlst_count = scandir(path->p_path, &dirlst, NULL, hostprog_scandirsort);
	if (dirlst_count < 0)
		error("failed to read \"%s\": %s", path->p_path, strerror(errno));
	
	for (dirlst_index = 0; dirlst_index < dirlst_count; dirlst_index++) {
		struct dirent* dent = dirlst[dirlst_index];
		
		/* Skip "." and ".." directories, just like mkcramfs.
		 */
		if (hostprog_path_dotdir(dent->d_name))
			continue;
		
		hostprog_path_dirnamelvl(path, dir_lvl);
		if (hostprog_path_append(path, dent->d_name) != 0)
			error("failed to add a filename to the hostprog_path");
		
		struct stat st;
		if (lstat(path->p_path, &st) < 0) {
			/* Maybe this should be an error? Files missing in the image
			 * could possibly seem like an error to the user.
			 */
			warning("skipping \"unlstatable\" file \"%s\": %s",
				path->p_path, strerror(errno));
			spec->sp_skipnodes++;
			continue;
		}
		
		if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
			/* If the file is a regular file which can not be read, then
			 * it might as well be skipped.
			 * 
			 * This should also possibly be an error.
			 */
			if (access(path->p_path, R_OK) < 0) {
				warning("skipping unreadable file \"%s\": %s",
					path->p_path, strerror(errno));
				spec->sp_skipnodes++;
				continue;
			}
			/* Completely empty files seems pretty pointless to include
			 * in the image.
			 */
			if (!st.st_size) {
				message(VERBOSITY_1, ">>> skipping empty file \"%s\"", path->p_path);
				spec->sp_skipnodes++;
				continue;
			}
		} else if (!spec->sp_incsocks && S_ISSOCK(st.st_mode)) {
			warning("skipping socket \"%s\"", path->p_path);
			spec->sp_skipnodes++;
			continue;
		}
		
		/* Files should never be skipped after this point since that
		 * would mess up the image size estimation.
		 */
		
		struct entry* ent = malloc(sizeof(*ent));
		if (!ent)
			error("failed to alloc an entry for \"%s\"", path->p_path);
		
		memset(ent, 0, sizeof(*ent));
		
		ent->e_name = strdup(dent->d_name);
		if (!ent->e_name)
			error("failed to copy the entry name for \"%s\"", path->p_path);
		ent->e_mode = st.st_mode;
		ent->e_size = st.st_size;
		ent->e_fd = -1;
		
		ENTRY_SET_XID(spec, ent, e_uid, st.st_uid, MICROFS_IUID_WIDTH);
		ENTRY_SET_XID(spec, ent, e_gid, st.st_gid, MICROFS_IGID_WIDTH);
		
		if (S_ISDIR(ent->e_mode)) {
			ent->e_size = walk_directory(spec, path, &ent->e_firstchild);
		} else if (S_ISREG(ent->e_mode) || S_ISLNK(ent->e_mode)) {
			if (ent->e_size > MICROFS_MAXFILESIZE) {
				error("\"%s\" is too big, max file size is %llu bytes",
					path->p_path, MICROFS_MAXFILESIZE);
			} else if (ent->e_size > MICROFS_MAXCRAMSIZE) {
				warning("\"%s\" is a big file, microfs works best with files"
					" smaller than %llu bytes",
					path->p_path, MICROFS_MAXCRAMSIZE);
			}
			ent->e_path = strdup(path->p_path);
			if (!ent->e_path) {
				error("failed to copy the entry path for \"%s\"",
					path->p_path);
			}
			if (spec->sp_shareblocks) {
				if (hostprog_stack_push(spec->sp_regstack, ent) < 0)
					error("failed to push an entry to the regular file stack: %s",
						strerror(errno));
			}
		} else if (S_ISCHR(ent->e_mode) || S_ISBLK(ent->e_mode)) {
			ent->e_size = makedev_lim(major(st.st_rdev), minor(st.st_rdev),
				MICROFS_ISIZE_WIDTH);
		} else if (S_ISFIFO(ent->e_mode) || S_ISSOCK(ent->e_mode)) {
			ent->e_size = 0;
		} else {
			error("unexpected file mode encountered");
		}
		
		/* %namelen() could actually fail here if the d_name is too
		 * long, but that will terminate the program, so that is fine.
		 */
		dir_sz += update_upperbound(spec, ent, namelen(dent->d_name));
		
		update_stats(spec, ent);
		
		message(VERBOSITY_1, "+ %c %s", nodtype(ent->e_mode), path->p_path);
		
		*previous = ent;
		previous = &ent->e_sibling;
	}
	
	free(dirlst);
	hostprog_path_dirnamelvl(path, dir_lvl);
	
	/* This should never happen, but if it ever does, it is
	 * clearly an error.
	 */
	if (dir_sz > MICROFS_MAXDIRSIZE) {
		error("achievement unlocked: the directory size for \"%s\""
			" is %llu bytes, the maximum supported size is %llu bytes"
			" - this is impressive in a very scary way",
			path->p_path, dir_sz, MICROFS_MAXDIRSIZE);
	}
	
	return dir_sz;
}
static void devtable_modify_entry(struct imgspec* const spec,
	const char* path, unsigned long uid, unsigned long gid,
	mode_t mode, dev_t rdev)
{
	char* path_dir = strdup(path);
	char* path_name = strdup(path);
	
	if (!path_dir || !path_name)
		error("failed to duplicate the path: %s", strerror(errno));
	
	char* dir = dirname(path_dir);
	char* name = basename(path_name);
	
	struct entry* parent;
	struct entry* target;
	
	if (strcmp(dir, "/") == 0) {
		parent = spec->sp_root;
	} else if (!(parent = devtable_find_entry(spec->sp_root, dir + 1, S_IFDIR))) {
		error("could not find the parent for path \"%s\"", path);
	}
	
	if ((target = devtable_find_entry(parent, name, mode & S_IFMT))) {
		target->e_mode = mode;
		ENTRY_SET_XID(spec, target, e_uid, uid, MICROFS_IUID_WIDTH);
		ENTRY_SET_XID(spec, target, e_gid, gid, MICROFS_IGID_WIDTH);
		message(VERBOSITY_1, "(src:%s) %% %c %s", spec->sp_devtable,
			nodtype(target->e_mode), path);
	} else {
		target = malloc(sizeof(*target));
		if (!target)
			error("failed to alloc an entry for \"%s\"", path);
		
		memset(target, 0, sizeof(*target));
		
		if (S_ISREG(mode)) {
			error("regular file \"%s\" must exist on the disk"
				" - and it can not be empty", path);
		}
		
		target->e_fd = -1;
		target->e_mode = mode;
		ENTRY_SET_XID(spec, target, e_uid, uid, MICROFS_IUID_WIDTH);
		ENTRY_SET_XID(spec, target, e_gid, gid, MICROFS_IGID_WIDTH);
		
		size_t targetnamelen = namelen(name);
		target->e_name = strdup(name);
		if (!target->e_name)
			error("failed to copy the entry name for \"%s\"", path);
		
		target->e_size = rdev;
		parent->e_size += update_upperbound(spec, target, targetnamelen);
		
		update_stats(spec, target);
		
		struct entry* prev = NULL;
		struct entry* curr = parent->e_firstchild;
		
		while (curr && strcmp(name, curr->e_name) > 0) {
			prev = curr;
			curr = curr->e_sibling;
		}
		if (!prev)
			parent->e_firstchild = target;
		else
			prev->e_sibling = target;
		
		target->e_sibling = curr;
		target->e_firstchild = NULL;
		
		message(VERBOSITY_1, "(src:%s) + %c %s", spec->sp_devtable,
			nodtype(target->e_mode), path);
	}
	
	free(path_name);
	free(path_dir);
}
OracleConnection::ObjectType OracleConnection::ReadSPDescriptionFromDB( const String &command, Anything &desc )
{
	StartTrace(OracleConnection.ReadSPDescriptionFromDB);

	String strErr( "ReadSPDescriptionFromDB: " );

	MemChecker aCheckerLocal( "OracleConnection.ReadSPDescriptionFromDB", getEnvironment().getAllocator() );
	MemChecker aCheckerGlobal( "OracleConnection.ReadSPDescriptionFromDB", coast::storage::Global());

	sword attrStat;
	DscHandleType aDschp;
	if ( checkError( ( attrStat = OCIHandleAlloc( getEnvironment().EnvHandle(), aDschp.getVoidAddr(),
								  OCI_HTYPE_DESCRIBE, 0, 0 ) ) ) ) {
		throw OracleException( *this, attrStat );
	}
	Trace("after HandleAlloc, local allocator:" << reinterpret_cast<long>(getEnvironment().getAllocator()));
	Trace("after HandleAlloc, global allocator:" << reinterpret_cast<long>(coast::storage::Global()));

	OCIParam *parmh( 0 );
	ObjectType aStmtType = DescribeObjectByName(command, aDschp, parmh);
	if ( aStmtType == TYPE_SYN ) {
		Trace("as we identified a synonym, we need to collect the scheme name and the referenced object name to ask for description");
		text *name(0);
		ub4 namelen(0);
		String strSchemaName;
		attrStat = OCIAttrGet( (dvoid *) parmh, OCI_DTYPE_PARAM, (dvoid *) &name, (ub4 *) &namelen, OCI_ATTR_SCHEMA_NAME, ErrorHandle() );
		if ( checkError( ( attrStat ) ) ) {
			throw OracleException( *this, attrStat );
		}
		strSchemaName.Append(String( (char *) name, namelen ));
		Trace("SchemaName: " << strSchemaName);

		attrStat = OCIAttrGet( (dvoid *) parmh, OCI_DTYPE_PARAM, (dvoid *) &name, (ub4 *) &namelen, OCI_ATTR_NAME, ErrorHandle() );
		if ( checkError( ( attrStat ) ) ) {
			throw OracleException( *this, attrStat );
		}
		if ( strSchemaName.Length() ) {
			strSchemaName.Append('.');
		}
		strSchemaName.Append(String( (char *) name, namelen ));

		Trace("trying to get descriptions for " << strSchemaName);
		aStmtType = DescribeObjectByName(strSchemaName, aDschp, parmh);
	}
	bool bIsFunction = ( aStmtType == TYPE_FUNC );

	Trace("get the number of arguments and the arg list for stored " << (bIsFunction ? "function" : "procedure"))
	OCIParam *arglst( 0 );
	ub2 numargs = 0;
	if ( checkError( ( attrStat = OCIAttrGet( (dvoid *) parmh, OCI_DTYPE_PARAM, (dvoid *) &arglst, (ub4 *) 0,
								  OCI_ATTR_LIST_ARGUMENTS, ErrorHandle() ) ) ) ) {
		throw OracleException( *this, attrStat );
	}
	if ( checkError( ( attrStat = OCIAttrGet( (dvoid *) arglst, OCI_DTYPE_PARAM, (dvoid *) &numargs, (ub4 *) 0,
								  OCI_ATTR_NUM_PARAMS, ErrorHandle() ) ) ) ) {
		throw OracleException( *this, attrStat );
	}
	Trace(String("number of arguments: ") << numargs);

	OCIParam *arg( 0 );
	text *name;
	ub4 namelen;
	ub2 dtype;
	OCITypeParamMode iomode;
	ub4 data_len;

	// For a procedure, we begin with i = 1; for a function, we begin with i = 0.
	int start = 0;
	int end = numargs;
	if ( !bIsFunction ) {
		++start;
		++end;
	}

	for ( int i = start; i < end; ++i ) {
		if ( checkError( ( attrStat = OCIParamGet( (dvoid *) arglst, OCI_DTYPE_PARAM, ErrorHandle(), (dvoid **) &arg,
									  (ub4) i ) ) ) ) {
			throw OracleException( *this, attrStat );
		}
		namelen = 0;
		name = 0;
		data_len = 0;

		if ( checkError( ( attrStat = OCIAttrGet( (dvoid *) arg, OCI_DTYPE_PARAM, (dvoid *) &dtype, (ub4 *) 0,
									  OCI_ATTR_DATA_TYPE, ErrorHandle() ) ) ) ) {
			throw OracleException( *this, attrStat );
		}
		Trace("Data type: " << dtype)

		if ( checkError( ( attrStat = OCIAttrGet( (dvoid *) arg, OCI_DTYPE_PARAM, (dvoid *) &name, (ub4 *) &namelen,
									  OCI_ATTR_NAME, ErrorHandle() ) ) ) ) {
			throw OracleException( *this, attrStat );
		}
		String strName( (char *) name, namelen );
		// the first param of a function is the return param
		if ( bIsFunction && i == start ) {
			strName = command;
			Trace("Name: " << strName)
		}

		// 0 = IN (OCI_TYPEPARAM_IN), 1 = OUT (OCI_TYPEPARAM_OUT), 2 = IN/OUT (OCI_TYPEPARAM_INOUT)
		if ( checkError( ( attrStat = OCIAttrGet( (dvoid *) arg, OCI_DTYPE_PARAM, (dvoid *) &iomode, (ub4 *) 0,
									  OCI_ATTR_IOMODE, ErrorHandle() ) ) ) ) {
			throw OracleException( *this, attrStat );
		}
		Trace("IO type: " << iomode)

		if ( checkError( ( attrStat = OCIAttrGet( (dvoid *) arg, OCI_DTYPE_PARAM, (dvoid *) &data_len, (ub4 *) 0,
									  OCI_ATTR_DATA_SIZE, ErrorHandle() ) ) ) ) {
			throw OracleException( *this, attrStat );
		}
		Trace("Size: " << (int)data_len)

		Anything param( desc.GetAllocator() );
		param["Name"] = strName;
		param["Type"] = dtype;
		param["Length"] = (int) data_len;
		param["IoMode"] = iomode;
		param["Idx"] = (long) ( bIsFunction ? i + 1 : i );
		desc.Append( param );
		if ( checkError( ( attrStat = OCIDescriptorFree( arg, OCI_DTYPE_PARAM ) ) ) ) {
			throw OracleException( *this, attrStat );
		}
	}