16cd6a6acSopenharmony_ci/*
26cd6a6acSopenharmony_ci * Media contexts backend for DB objects
36cd6a6acSopenharmony_ci *
46cd6a6acSopenharmony_ci * Author: KaiGai Kohei <kaigai@ak.jp.nec.com>
56cd6a6acSopenharmony_ci */
66cd6a6acSopenharmony_ci
76cd6a6acSopenharmony_ci#include <sys/stat.h>
86cd6a6acSopenharmony_ci#include <string.h>
96cd6a6acSopenharmony_ci#include <stdio.h>
106cd6a6acSopenharmony_ci#include <stdio_ext.h>
116cd6a6acSopenharmony_ci#include <ctype.h>
126cd6a6acSopenharmony_ci#include <errno.h>
136cd6a6acSopenharmony_ci#include <limits.h>
146cd6a6acSopenharmony_ci#include <fnmatch.h>
156cd6a6acSopenharmony_ci#include "callbacks.h"
166cd6a6acSopenharmony_ci#include "label_internal.h"
176cd6a6acSopenharmony_ci
186cd6a6acSopenharmony_ci/*
196cd6a6acSopenharmony_ci * Regular database object's security context interface
206cd6a6acSopenharmony_ci *
216cd6a6acSopenharmony_ci * It provides applications a regular security context for the given
226cd6a6acSopenharmony_ci * database objects. The pair of object's name and a security context
236cd6a6acSopenharmony_ci * are described in the specfile. In the default, it shall be stored
246cd6a6acSopenharmony_ci * in the /etc/selinux/$POLICYTYPE/contexts/sepgsql_contexts .
256cd6a6acSopenharmony_ci * (It assumes SE-PostgreSQL in the default. For other RDBMS, use the
266cd6a6acSopenharmony_ci * SELABEL_OPT_PATH option to specify different specfile.)
276cd6a6acSopenharmony_ci *
286cd6a6acSopenharmony_ci * Each line has the following format:
296cd6a6acSopenharmony_ci *   <object class> <object name/identifier> <security context>
306cd6a6acSopenharmony_ci *
316cd6a6acSopenharmony_ci * For example:
326cd6a6acSopenharmony_ci * ----------------------------------------
336cd6a6acSopenharmony_ci * #
346cd6a6acSopenharmony_ci * # It is an example specfile for database objects
356cd6a6acSopenharmony_ci * #
366cd6a6acSopenharmony_ci * db_database  template1           system_u:object_r:sepgsql_db_t:s0
376cd6a6acSopenharmony_ci *
386cd6a6acSopenharmony_ci * db_schema    *.pg_catalog        system_u:object_r:sepgsql_sys_schema_t:s0
396cd6a6acSopenharmony_ci *
406cd6a6acSopenharmony_ci * db_table     *.pg_catalog.*	    system_u:object_r:sepgsql_sysobj_t:s0
416cd6a6acSopenharmony_ci * db_column    *.pg_catalog.*.*    system_u:object_r:sepgsql_sysobj_t:s0
426cd6a6acSopenharmony_ci * ----------------------------------------
436cd6a6acSopenharmony_ci *
446cd6a6acSopenharmony_ci * All the characters after the '#' are dealt as comments.
456cd6a6acSopenharmony_ci *
466cd6a6acSopenharmony_ci * The first token is object class. SELABEL_DB_* declared in label.h are
476cd6a6acSopenharmony_ci * corresponding to a certain database object.
486cd6a6acSopenharmony_ci *
496cd6a6acSopenharmony_ci * The object name/identifier is compared to the given key.
506cd6a6acSopenharmony_ci * A database object can have its own namespace hierarchy.
516cd6a6acSopenharmony_ci * In the case of SE-PgSQL, database is the top level object, and schema
526cd6a6acSopenharmony_ci * is deployed just under a database. A schema can contains various kind
536cd6a6acSopenharmony_ci * of objects, such as tables, procedures and so on.
546cd6a6acSopenharmony_ci * Thus, when we lookup an expected security context for a table of
556cd6a6acSopenharmony_ci * "pg_class", it is necessary to assume selabel_lookup() is called with
566cd6a6acSopenharmony_ci * "postgres.pg_catalog.pg_class", not just a "pg_class".
576cd6a6acSopenharmony_ci *
586cd6a6acSopenharmony_ci * Wildcards ('*' or '?') are available on the patterns, so if you want
596cd6a6acSopenharmony_ci * to match a table within any schema, you should set '*' on the upper
606cd6a6acSopenharmony_ci * namespaces of the table.
616cd6a6acSopenharmony_ci *
626cd6a6acSopenharmony_ci * The structure of namespace depends on RDBMS.
636cd6a6acSopenharmony_ci * For example, Trusted-RUBIX has an idea of "catalog" which performs
646cd6a6acSopenharmony_ci * as a namespace between a database and individual schemas. In this
656cd6a6acSopenharmony_ci * case, a table has upper three layers.
666cd6a6acSopenharmony_ci */
676cd6a6acSopenharmony_ci
686cd6a6acSopenharmony_ci/*
696cd6a6acSopenharmony_ci * spec_t : It holds a pair of a key and an expected security context
706cd6a6acSopenharmony_ci */
716cd6a6acSopenharmony_citypedef struct spec {
726cd6a6acSopenharmony_ci	struct selabel_lookup_rec lr;
736cd6a6acSopenharmony_ci	char	       *key;
746cd6a6acSopenharmony_ci	int		type;
756cd6a6acSopenharmony_ci	int		matches;
766cd6a6acSopenharmony_ci} spec_t;
776cd6a6acSopenharmony_ci
786cd6a6acSopenharmony_ci/*
796cd6a6acSopenharmony_ci * catalog_t : An array of spec_t
806cd6a6acSopenharmony_ci */
816cd6a6acSopenharmony_citypedef struct catalog {
826cd6a6acSopenharmony_ci	unsigned int	nspec;	/* number of specs in use */
836cd6a6acSopenharmony_ci	unsigned int	limit;	/* physical limitation of specs[] */
846cd6a6acSopenharmony_ci	spec_t		specs[0];
856cd6a6acSopenharmony_ci} catalog_t;
866cd6a6acSopenharmony_ci
876cd6a6acSopenharmony_ci/*
886cd6a6acSopenharmony_ci * Helper function to parse a line read from the specfile
896cd6a6acSopenharmony_ci */
906cd6a6acSopenharmony_cistatic int
916cd6a6acSopenharmony_ciprocess_line(const char *path, char *line_buf, unsigned int line_num,
926cd6a6acSopenharmony_ci	     catalog_t *catalog)
936cd6a6acSopenharmony_ci{
946cd6a6acSopenharmony_ci	spec_t	       *spec = &catalog->specs[catalog->nspec];
956cd6a6acSopenharmony_ci	char	       *type, *key, *context, *temp;
966cd6a6acSopenharmony_ci	int		items;
976cd6a6acSopenharmony_ci
986cd6a6acSopenharmony_ci	/* Cut off comments */
996cd6a6acSopenharmony_ci	temp = strchr(line_buf, '#');
1006cd6a6acSopenharmony_ci	if (temp)
1016cd6a6acSopenharmony_ci		*temp = '\0';
1026cd6a6acSopenharmony_ci
1036cd6a6acSopenharmony_ci	/*
1046cd6a6acSopenharmony_ci	 * Every entry must have the following format
1056cd6a6acSopenharmony_ci	 *   <object class> <object name> <security context>
1066cd6a6acSopenharmony_ci	 */
1076cd6a6acSopenharmony_ci	type = key = context = temp = NULL;
1086cd6a6acSopenharmony_ci	items = sscanf(line_buf, "%ms %ms %ms %ms",
1096cd6a6acSopenharmony_ci		       &type, &key, &context, &temp);
1106cd6a6acSopenharmony_ci	if (items != 3) {
1116cd6a6acSopenharmony_ci		if (items > 0)
1126cd6a6acSopenharmony_ci			selinux_log(SELINUX_WARNING,
1136cd6a6acSopenharmony_ci				    "%s:  line %u has invalid format, skipped",
1146cd6a6acSopenharmony_ci				    path, line_num);
1156cd6a6acSopenharmony_ci		goto skip;
1166cd6a6acSopenharmony_ci	}
1176cd6a6acSopenharmony_ci
1186cd6a6acSopenharmony_ci	/*
1196cd6a6acSopenharmony_ci	 * Set up individual spec entry
1206cd6a6acSopenharmony_ci	 */
1216cd6a6acSopenharmony_ci	memset(spec, 0, sizeof(spec_t));
1226cd6a6acSopenharmony_ci
1236cd6a6acSopenharmony_ci	if (!strcmp(type, "db_database"))
1246cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_DATABASE;
1256cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_schema"))
1266cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_SCHEMA;
1276cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_table"))
1286cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_TABLE;
1296cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_column"))
1306cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_COLUMN;
1316cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_sequence"))
1326cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_SEQUENCE;
1336cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_view"))
1346cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_VIEW;
1356cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_procedure"))
1366cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_PROCEDURE;
1376cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_blob"))
1386cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_BLOB;
1396cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_tuple"))
1406cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_TUPLE;
1416cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_language"))
1426cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_LANGUAGE;
1436cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_exception"))
1446cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_EXCEPTION;
1456cd6a6acSopenharmony_ci	else if (!strcmp(type, "db_datatype"))
1466cd6a6acSopenharmony_ci		spec->type = SELABEL_DB_DATATYPE;
1476cd6a6acSopenharmony_ci	else {
1486cd6a6acSopenharmony_ci		selinux_log(SELINUX_WARNING,
1496cd6a6acSopenharmony_ci			    "%s:  line %u has invalid object type %s\n",
1506cd6a6acSopenharmony_ci			    path, line_num, type);
1516cd6a6acSopenharmony_ci		goto skip;
1526cd6a6acSopenharmony_ci	}
1536cd6a6acSopenharmony_ci
1546cd6a6acSopenharmony_ci	free(type);
1556cd6a6acSopenharmony_ci	spec->key = key;
1566cd6a6acSopenharmony_ci	spec->lr.ctx_raw = context;
1576cd6a6acSopenharmony_ci
1586cd6a6acSopenharmony_ci	catalog->nspec++;
1596cd6a6acSopenharmony_ci
1606cd6a6acSopenharmony_ci	return 0;
1616cd6a6acSopenharmony_ci
1626cd6a6acSopenharmony_ciskip:
1636cd6a6acSopenharmony_ci	free(type);
1646cd6a6acSopenharmony_ci	free(key);
1656cd6a6acSopenharmony_ci	free(context);
1666cd6a6acSopenharmony_ci	free(temp);
1676cd6a6acSopenharmony_ci
1686cd6a6acSopenharmony_ci	return 0;
1696cd6a6acSopenharmony_ci}
1706cd6a6acSopenharmony_ci
1716cd6a6acSopenharmony_ci/*
1726cd6a6acSopenharmony_ci * selabel_close() handler
1736cd6a6acSopenharmony_ci */
1746cd6a6acSopenharmony_cistatic void
1756cd6a6acSopenharmony_cidb_close(struct selabel_handle *rec)
1766cd6a6acSopenharmony_ci{
1776cd6a6acSopenharmony_ci	catalog_t      *catalog = (catalog_t *)rec->data;
1786cd6a6acSopenharmony_ci	spec_t	       *spec;
1796cd6a6acSopenharmony_ci	unsigned int	i;
1806cd6a6acSopenharmony_ci
1816cd6a6acSopenharmony_ci	for (i = 0; i < catalog->nspec; i++) {
1826cd6a6acSopenharmony_ci		spec = &catalog->specs[i];
1836cd6a6acSopenharmony_ci		free(spec->key);
1846cd6a6acSopenharmony_ci		free(spec->lr.ctx_raw);
1856cd6a6acSopenharmony_ci		free(spec->lr.ctx_trans);
1866cd6a6acSopenharmony_ci	}
1876cd6a6acSopenharmony_ci	free(catalog);
1886cd6a6acSopenharmony_ci}
1896cd6a6acSopenharmony_ci
1906cd6a6acSopenharmony_ci/*
1916cd6a6acSopenharmony_ci * selabel_lookup() handler
1926cd6a6acSopenharmony_ci */
1936cd6a6acSopenharmony_cistatic struct selabel_lookup_rec *
1946cd6a6acSopenharmony_cidb_lookup(struct selabel_handle *rec, const char *key, int type)
1956cd6a6acSopenharmony_ci{
1966cd6a6acSopenharmony_ci	catalog_t      *catalog = (catalog_t *)rec->data;
1976cd6a6acSopenharmony_ci	spec_t	       *spec;
1986cd6a6acSopenharmony_ci	unsigned int	i;
1996cd6a6acSopenharmony_ci
2006cd6a6acSopenharmony_ci	for (i = 0; i < catalog->nspec; i++) {
2016cd6a6acSopenharmony_ci		spec = &catalog->specs[i];
2026cd6a6acSopenharmony_ci
2036cd6a6acSopenharmony_ci		if (spec->type != type)
2046cd6a6acSopenharmony_ci			continue;
2056cd6a6acSopenharmony_ci		if (!fnmatch(spec->key, key, 0)) {
2066cd6a6acSopenharmony_ci			spec->matches++;
2076cd6a6acSopenharmony_ci
2086cd6a6acSopenharmony_ci			return &spec->lr;
2096cd6a6acSopenharmony_ci		}
2106cd6a6acSopenharmony_ci	}
2116cd6a6acSopenharmony_ci
2126cd6a6acSopenharmony_ci	/* No found */
2136cd6a6acSopenharmony_ci	errno = ENOENT;
2146cd6a6acSopenharmony_ci	return NULL;
2156cd6a6acSopenharmony_ci}
2166cd6a6acSopenharmony_ci
2176cd6a6acSopenharmony_ci/*
2186cd6a6acSopenharmony_ci * selabel_stats() handler
2196cd6a6acSopenharmony_ci */
2206cd6a6acSopenharmony_cistatic void
2216cd6a6acSopenharmony_cidb_stats(struct selabel_handle *rec)
2226cd6a6acSopenharmony_ci{
2236cd6a6acSopenharmony_ci	catalog_t      *catalog = (catalog_t *)rec->data;
2246cd6a6acSopenharmony_ci	unsigned int	i, total = 0;
2256cd6a6acSopenharmony_ci
2266cd6a6acSopenharmony_ci	for (i = 0; i < catalog->nspec; i++)
2276cd6a6acSopenharmony_ci		total += catalog->specs[i].matches;
2286cd6a6acSopenharmony_ci
2296cd6a6acSopenharmony_ci	selinux_log(SELINUX_INFO, "%u entries, %u matches made\n",
2306cd6a6acSopenharmony_ci		    catalog->nspec, total);
2316cd6a6acSopenharmony_ci}
2326cd6a6acSopenharmony_ci
2336cd6a6acSopenharmony_ci/*
2346cd6a6acSopenharmony_ci * selabel_open() handler
2356cd6a6acSopenharmony_ci */
2366cd6a6acSopenharmony_cistatic catalog_t *
2376cd6a6acSopenharmony_cidb_init(const struct selinux_opt *opts, unsigned nopts,
2386cd6a6acSopenharmony_ci			    struct selabel_handle *rec)
2396cd6a6acSopenharmony_ci{
2406cd6a6acSopenharmony_ci	catalog_t      *catalog;
2416cd6a6acSopenharmony_ci	FILE	       *filp;
2426cd6a6acSopenharmony_ci	const char     *path = NULL;
2436cd6a6acSopenharmony_ci	char	       *line_buf = NULL;
2446cd6a6acSopenharmony_ci	size_t		line_len = 0;
2456cd6a6acSopenharmony_ci	unsigned int	line_num = 0;
2466cd6a6acSopenharmony_ci	unsigned int	i;
2476cd6a6acSopenharmony_ci	struct stat sb;
2486cd6a6acSopenharmony_ci
2496cd6a6acSopenharmony_ci	/*
2506cd6a6acSopenharmony_ci	 * Initialize catalog data structure
2516cd6a6acSopenharmony_ci	 */
2526cd6a6acSopenharmony_ci	catalog = malloc(sizeof(catalog_t) + 32 * sizeof(spec_t));
2536cd6a6acSopenharmony_ci	if (!catalog)
2546cd6a6acSopenharmony_ci		return NULL;
2556cd6a6acSopenharmony_ci	catalog->limit = 32;
2566cd6a6acSopenharmony_ci	catalog->nspec = 0;
2576cd6a6acSopenharmony_ci
2586cd6a6acSopenharmony_ci	/*
2596cd6a6acSopenharmony_ci	 * Process arguments
2606cd6a6acSopenharmony_ci	 *
2616cd6a6acSopenharmony_ci	 * SELABEL_OPT_PATH:
2626cd6a6acSopenharmony_ci	 *   It allows to specify an alternative specification file instead of
2636cd6a6acSopenharmony_ci	 *   the default one. If RDBMS is not SE-PostgreSQL, it may need to
2646cd6a6acSopenharmony_ci	 *   specify an explicit specfile for database objects.
2656cd6a6acSopenharmony_ci	 */
2666cd6a6acSopenharmony_ci	while (nopts--) {
2676cd6a6acSopenharmony_ci		switch (opts[nopts].type) {
2686cd6a6acSopenharmony_ci		case SELABEL_OPT_PATH:
2696cd6a6acSopenharmony_ci			path = opts[nopts].value;
2706cd6a6acSopenharmony_ci			break;
2716cd6a6acSopenharmony_ci		}
2726cd6a6acSopenharmony_ci	}
2736cd6a6acSopenharmony_ci
2746cd6a6acSopenharmony_ci	/*
2756cd6a6acSopenharmony_ci	 * Open the specification file
2766cd6a6acSopenharmony_ci	 */
2776cd6a6acSopenharmony_ci	if (!path)
2786cd6a6acSopenharmony_ci		path = selinux_sepgsql_context_path();
2796cd6a6acSopenharmony_ci
2806cd6a6acSopenharmony_ci	if ((filp = fopen(path, "re")) == NULL) {
2816cd6a6acSopenharmony_ci		free(catalog);
2826cd6a6acSopenharmony_ci		return NULL;
2836cd6a6acSopenharmony_ci	}
2846cd6a6acSopenharmony_ci	if (fstat(fileno(filp), &sb) < 0) {
2856cd6a6acSopenharmony_ci		free(catalog);
2866cd6a6acSopenharmony_ci		fclose(filp);
2876cd6a6acSopenharmony_ci		return NULL;
2886cd6a6acSopenharmony_ci	}
2896cd6a6acSopenharmony_ci	if (!S_ISREG(sb.st_mode)) {
2906cd6a6acSopenharmony_ci		free(catalog);
2916cd6a6acSopenharmony_ci		fclose(filp);
2926cd6a6acSopenharmony_ci		errno = EINVAL;
2936cd6a6acSopenharmony_ci		return NULL;
2946cd6a6acSopenharmony_ci	}
2956cd6a6acSopenharmony_ci	rec->spec_file = strdup(path);
2966cd6a6acSopenharmony_ci	if (!rec->spec_file) {
2976cd6a6acSopenharmony_ci                free(catalog);
2986cd6a6acSopenharmony_ci                fclose(filp);
2996cd6a6acSopenharmony_ci                return NULL;
3006cd6a6acSopenharmony_ci	}
3016cd6a6acSopenharmony_ci
3026cd6a6acSopenharmony_ci	/*
3036cd6a6acSopenharmony_ci	 * Parse for each lines
3046cd6a6acSopenharmony_ci	 */
3056cd6a6acSopenharmony_ci	while (getline(&line_buf, &line_len, filp) > 0) {
3066cd6a6acSopenharmony_ci		/*
3076cd6a6acSopenharmony_ci		 * Expand catalog array, if necessary
3086cd6a6acSopenharmony_ci		 */
3096cd6a6acSopenharmony_ci		if (catalog->limit == catalog->nspec) {
3106cd6a6acSopenharmony_ci			size_t		length;
3116cd6a6acSopenharmony_ci			unsigned int	new_limit = 2 * catalog->limit;
3126cd6a6acSopenharmony_ci			catalog_t      *new_catalog;
3136cd6a6acSopenharmony_ci
3146cd6a6acSopenharmony_ci			length = sizeof(catalog_t)
3156cd6a6acSopenharmony_ci				+ new_limit * sizeof(spec_t);
3166cd6a6acSopenharmony_ci			new_catalog = realloc(catalog, length);
3176cd6a6acSopenharmony_ci			if (!new_catalog)
3186cd6a6acSopenharmony_ci				goto out_error;
3196cd6a6acSopenharmony_ci
3206cd6a6acSopenharmony_ci			catalog = new_catalog;
3216cd6a6acSopenharmony_ci			catalog->limit = new_limit;
3226cd6a6acSopenharmony_ci		}
3236cd6a6acSopenharmony_ci
3246cd6a6acSopenharmony_ci		/*
3256cd6a6acSopenharmony_ci		 * Parse a line
3266cd6a6acSopenharmony_ci		 */
3276cd6a6acSopenharmony_ci		if (process_line(path, line_buf, ++line_num, catalog) < 0)
3286cd6a6acSopenharmony_ci			goto out_error;
3296cd6a6acSopenharmony_ci	}
3306cd6a6acSopenharmony_ci
3316cd6a6acSopenharmony_ci	if (digest_add_specfile(rec->digest, filp, NULL, sb.st_size, path) < 0)
3326cd6a6acSopenharmony_ci		goto out_error;
3336cd6a6acSopenharmony_ci
3346cd6a6acSopenharmony_ci	digest_gen_hash(rec->digest);
3356cd6a6acSopenharmony_ci
3366cd6a6acSopenharmony_ci	free(line_buf);
3376cd6a6acSopenharmony_ci	fclose(filp);
3386cd6a6acSopenharmony_ci
3396cd6a6acSopenharmony_ci	return catalog;
3406cd6a6acSopenharmony_ci
3416cd6a6acSopenharmony_ciout_error:
3426cd6a6acSopenharmony_ci	free(line_buf);
3436cd6a6acSopenharmony_ci	for (i = 0; i < catalog->nspec; i++) {
3446cd6a6acSopenharmony_ci		spec_t	       *spec = &catalog->specs[i];
3456cd6a6acSopenharmony_ci
3466cd6a6acSopenharmony_ci		free(spec->key);
3476cd6a6acSopenharmony_ci		free(spec->lr.ctx_raw);
3486cd6a6acSopenharmony_ci		free(spec->lr.ctx_trans);
3496cd6a6acSopenharmony_ci	}
3506cd6a6acSopenharmony_ci	free(catalog);
3516cd6a6acSopenharmony_ci	fclose(filp);
3526cd6a6acSopenharmony_ci
3536cd6a6acSopenharmony_ci	return NULL;
3546cd6a6acSopenharmony_ci}
3556cd6a6acSopenharmony_ci
3566cd6a6acSopenharmony_ci/*
3576cd6a6acSopenharmony_ci * Initialize selabel_handle and load the entries of specfile
3586cd6a6acSopenharmony_ci */
3596cd6a6acSopenharmony_ciint selabel_db_init(struct selabel_handle *rec,
3606cd6a6acSopenharmony_ci		    const struct selinux_opt *opts, unsigned nopts)
3616cd6a6acSopenharmony_ci{
3626cd6a6acSopenharmony_ci	rec->func_close = &db_close;
3636cd6a6acSopenharmony_ci	rec->func_lookup = &db_lookup;
3646cd6a6acSopenharmony_ci	rec->func_stats = &db_stats;
3656cd6a6acSopenharmony_ci	rec->data = db_init(opts, nopts, rec);
3666cd6a6acSopenharmony_ci
3676cd6a6acSopenharmony_ci	return !rec->data ? -1 : 0;
3686cd6a6acSopenharmony_ci}
369