From: Heikki Linnakangas Date: Sun, 28 Jun 2015 18:35:46 +0000 (+0300) Subject: Add missing_ok option to the SQL functions for reading files. X-Git-Tag: REL9_5_ALPHA1~12 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=cb2acb1081e13b4b27a76c6b5311115528e49c59;p=postgresql Add missing_ok option to the SQL functions for reading files. This makes it possible to use the functions without getting errors, if there is a chance that the file might be removed or renamed concurrently. pg_rewind needs to do just that, although this could be useful for other purposes too. (The changes to pg_rewind to use these functions will come in a separate commit.) The read_binary_file() function isn't very well-suited for extensions.c's purposes anymore, if it ever was. So bite the bullet and make a copy of it in extension.c, tailored for that use case. This seems better than the accidental code reuse, even if it's a some more lines of code. Michael Paquier, with plenty of kibitzing by me. --- diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 80b551e78c..69ac7c0416 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17811,43 +17811,63 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); - pg_ls_dir(dirname text) + pg_ls_dir(dirname text [, missing_ok boolean, include_dot_dirs boolean]) setof text - List the contents of a directory + + List the contents of a directory. + - pg_read_file(filename text [, offset bigint, length bigint]) + pg_read_file(filename text [, offset bigint, length bigint [, missing_ok boolean] ]) text - Return the contents of a text file + + Return the contents of a text file. + - pg_read_binary_file(filename text [, offset bigint, length bigint]) + pg_read_binary_file(filename text [, offset bigint, length bigint [, missing_ok boolean] ]) bytea - Return the contents of a file + + Return the contents of a file. + - pg_stat_file(filename text) + pg_stat_file(filename text[, missing_ok boolean]) record - Return information about a file + + Return information about a file. + + + All of these functions take an optional missing_ok parameter, + which specifies the behaviour when the file or directory does not exist. + If true, the function returns NULL (except + pg_ls_dir, which returns an empty result set). If + false, an error is raised. The default is false. + + pg_ls_dir - pg_ls_dir returns all the names in the specified - directory, except the special entries . and - ... + pg_ls_dir returns the names of all files (and directories + and other special files) in the specified directory. The + include_dot_dirs indicates whether . and .. are + included in the result set. The default is to exclude them + (false/>), but including them can be useful when + missing_ok is true, to distinguish an + empty directory from an non-existent directory. diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 5cc74d03c1..2b1dcd0d19 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include "access/htup_details.h" @@ -51,6 +53,7 @@ #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/tqual.h" @@ -103,6 +106,7 @@ static void ApplyExtensionUpdates(Oid extensionOid, ExtensionControlFile *pcontrol, const char *initialVersion, List *updateVersions); +static char *read_whole_file(const char *filename, int *length); /* @@ -635,12 +639,11 @@ read_extension_script_file(const ExtensionControlFile *control, const char *filename) { int src_encoding; - bytea *content; char *src_str; char *dest_str; int len; - content = read_binary_file(filename, 0, -1); + src_str = read_whole_file(filename, &len); /* use database encoding if not given */ if (control->encoding < 0) @@ -649,21 +652,15 @@ read_extension_script_file(const ExtensionControlFile *control, src_encoding = control->encoding; /* make sure that source string is valid in the expected encoding */ - len = VARSIZE_ANY_EXHDR(content); - src_str = VARDATA_ANY(content); pg_verify_mbstr_len(src_encoding, src_str, len, false); - /* convert the encoding to the database encoding */ + /* + * Convert the encoding to the database encoding. read_whole_file + * null-terminated the string, so if no conversion happens the string is + * valid as is. + */ dest_str = pg_any_to_server(src_str, len, src_encoding); - /* if no conversion happened, we have to arrange for null termination */ - if (dest_str == src_str) - { - dest_str = (char *) palloc(len + 1); - memcpy(dest_str, src_str, len); - dest_str[len] = '\0'; - } - return dest_str; } @@ -3008,3 +3005,49 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt, return extension; } + +/* + * Read the whole of file into memory. + * + * The file contents are returned as a single palloc'd chunk. For convenience + * of the callers, an extra \0 byte is added to the end. + */ +static char * +read_whole_file(const char *filename, int *length) +{ + char *buf; + FILE *file; + size_t bytes_to_read; + struct stat fst; + + if (stat(filename, &fst) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + + if (fst.st_size > (MaxAllocSize - 1)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("file too large"))); + bytes_to_read = (size_t) fst.st_size; + + if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + + buf = (char *) palloc(bytes_to_read + 1); + + *length = fread(buf, 1, bytes_to_read, file); + + if (ferror(file)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", filename))); + + FreeFile(file); + + buf[*length] = '\0'; + return buf; +} diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index f3f3cca515..c4eb10d3cf 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -35,6 +35,7 @@ typedef struct { char *location; DIR *dirdesc; + bool include_dot_dirs; } directory_fctx; @@ -87,8 +88,9 @@ convert_and_check_filename(text *arg) * * We read the whole of the file when bytes_to_read is negative. */ -bytea * -read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) +static bytea * +read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read, + bool missing_ok) { bytea *buf; size_t nbytes; @@ -103,9 +105,14 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) struct stat fst; if (stat(filename, &fst) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", filename))); + { + if (missing_ok && errno == ENOENT) + return NULL; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } bytes_to_read = fst.st_size - seek_offset; } @@ -118,10 +125,15 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) errmsg("requested length too large"))); if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open file \"%s\" for reading: %m", - filename))); + { + if (missing_ok && errno == ENOENT) + return NULL; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + } if (fseeko(file, (off_t) seek_offset, (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0) @@ -150,17 +162,23 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) * in the database encoding. */ static text * -read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read) +read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read, + bool missing_ok) { bytea *buf; - buf = read_binary_file(filename, seek_offset, bytes_to_read); + buf = read_binary_file(filename, seek_offset, bytes_to_read, missing_ok); - /* Make sure the input is valid */ - pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); + if (buf != NULL) + { + /* Make sure the input is valid */ + pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); - /* OK, we can cast it to text safely */ - return (text *) buf; + /* OK, we can cast it to text safely */ + return (text *) buf; + } + else + return NULL; } /* @@ -170,42 +188,38 @@ Datum pg_read_file(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); - int64 seek_offset = PG_GETARG_INT64(1); - int64 bytes_to_read = PG_GETARG_INT64(2); + int64 seek_offset = 0; + int64 bytes_to_read = -1; + bool missing_ok = false; char *filename; + text *result; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to read files")))); - filename = convert_and_check_filename(filename_t); - - if (bytes_to_read < 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("requested length cannot be negative"))); - - PG_RETURN_TEXT_P(read_text_file(filename, seek_offset, bytes_to_read)); -} - -/* - * Read the whole of a file, returning it as text - */ -Datum -pg_read_file_all(PG_FUNCTION_ARGS) -{ - text *filename_t = PG_GETARG_TEXT_P(0); - char *filename; + /* handle optional arguments */ + if (PG_NARGS() >= 3) + { + seek_offset = PG_GETARG_INT64(1); + bytes_to_read = PG_GETARG_INT64(2); - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); + if (bytes_to_read < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length cannot be negative"))); + } + if (PG_NARGS() >= 4) + missing_ok = PG_GETARG_BOOL(3); filename = convert_and_check_filename(filename_t); - PG_RETURN_TEXT_P(read_text_file(filename, 0, -1)); + result = read_text_file(filename, seek_offset, bytes_to_read, missing_ok); + if (result) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); } /* @@ -215,42 +229,72 @@ Datum pg_read_binary_file(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); - int64 seek_offset = PG_GETARG_INT64(1); - int64 bytes_to_read = PG_GETARG_INT64(2); + int64 seek_offset = 0; + int64 bytes_to_read = -1; + bool missing_ok = false; char *filename; + bytea *result; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to read files")))); - filename = convert_and_check_filename(filename_t); + /* handle optional arguments */ + if (PG_NARGS() >= 3) + { + seek_offset = PG_GETARG_INT64(1); + bytes_to_read = PG_GETARG_INT64(2); - if (bytes_to_read < 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("requested length cannot be negative"))); + if (bytes_to_read < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length cannot be negative"))); + } + if (PG_NARGS() >= 4) + missing_ok = PG_GETARG_BOOL(3); + + filename = convert_and_check_filename(filename_t); - PG_RETURN_BYTEA_P(read_binary_file(filename, seek_offset, bytes_to_read)); + result = read_binary_file(filename, seek_offset, + bytes_to_read, missing_ok); + if (result) + PG_RETURN_BYTEA_P(result); + else + PG_RETURN_NULL(); } + /* - * Read the whole of a file, returning it as bytea + * Wrapper functions for the 1 and 3 argument variants of pg_read_file() + * and pg_binary_read_file(). + * + * These are necessary to pass the sanity check in opr_sanity, which checks + * that all built-in functions that share the implementing C function take + * the same number of arguments. */ Datum -pg_read_binary_file_all(PG_FUNCTION_ARGS) +pg_read_file_off_len(PG_FUNCTION_ARGS) { - text *filename_t = PG_GETARG_TEXT_P(0); - char *filename; + return pg_read_file(fcinfo); +} - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); +Datum +pg_read_file_all(PG_FUNCTION_ARGS) +{ + return pg_read_file(fcinfo); +} - filename = convert_and_check_filename(filename_t); +Datum +pg_read_binary_file_off_len(PG_FUNCTION_ARGS) +{ + return pg_read_binary_file(fcinfo); +} - PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1)); +Datum +pg_read_binary_file_all(PG_FUNCTION_ARGS) +{ + return pg_read_binary_file(fcinfo); } /* @@ -266,18 +310,28 @@ pg_stat_file(PG_FUNCTION_ARGS) bool isnull[6]; HeapTuple tuple; TupleDesc tupdesc; + bool missing_ok = false; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to get file information")))); + /* check the optional argument */ + if (PG_NARGS() == 2) + missing_ok = PG_GETARG_BOOL(1); + filename = convert_and_check_filename(filename_t); if (stat(filename, &fst) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", filename))); + { + if (missing_ok && errno == ENOENT) + PG_RETURN_NULL(); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } /* * This record type had better match the output parameters declared for me @@ -320,6 +374,18 @@ pg_stat_file(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); } +/* + * stat a file (1 argument version) + * + * note: this wrapper is necessary to pass the sanity check in opr_sanity, + * which checks that all built-in functions that share the implementing C + * function take the same number of arguments + */ +Datum +pg_stat_file_1arg(PG_FUNCTION_ARGS) +{ + return pg_stat_file(fcinfo); +} /* * List a directory (returns the filenames only) @@ -330,6 +396,7 @@ pg_ls_dir(PG_FUNCTION_ARGS) FuncCallContext *funcctx; struct dirent *de; directory_fctx *fctx; + MemoryContext oldcontext; if (!superuser()) ereport(ERROR, @@ -338,7 +405,17 @@ pg_ls_dir(PG_FUNCTION_ARGS) if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; + bool missing_ok = false; + bool include_dot_dirs = false; + + /* check the optional arguments */ + if (PG_NARGS() == 3) + { + if (!PG_ARGISNULL(1)) + missing_ok = PG_GETARG_BOOL(1); + if (!PG_ARGISNULL(2)) + include_dot_dirs = PG_GETARG_BOOL(2); + } funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); @@ -346,14 +423,22 @@ pg_ls_dir(PG_FUNCTION_ARGS) fctx = palloc(sizeof(directory_fctx)); fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0)); + fctx->include_dot_dirs = include_dot_dirs; fctx->dirdesc = AllocateDir(fctx->location); if (!fctx->dirdesc) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open directory \"%s\": %m", - fctx->location))); - + { + if (missing_ok && errno == ENOENT) + { + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", + fctx->location))); + } funcctx->user_fctx = fctx; MemoryContextSwitchTo(oldcontext); } @@ -363,8 +448,9 @@ pg_ls_dir(PG_FUNCTION_ARGS) while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) { - if (strcmp(de->d_name, ".") == 0 || - strcmp(de->d_name, "..") == 0) + if (!fctx->include_dot_dirs && + (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0)) continue; SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name)); @@ -374,3 +460,16 @@ pg_ls_dir(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * List a directory (1 argument version) + * + * note: this wrapper is necessary to pass the sanity check in opr_sanity, + * which checks that all built-in functions that share the implementing C + * function take the same number of arguments. + */ +Datum +pg_ls_dir_1arg(PG_FUNCTION_ARGS) +{ + return pg_ls_dir(fcinfo); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index de9018b957..3515b6436a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201506111 +#define CATALOG_VERSION_NO 201506281 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 6b3d1949ef..d1cc8907d7 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3181,17 +3181,25 @@ DESCR("reload configuration files"); DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ )); DESCR("rotate log file"); -DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ _null_ pg_stat_file _null_ _null_ _null_ )); +DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ _null_ pg_stat_file_1arg _null_ _null_ _null_ )); DESCR("get information about file"); -DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); +DATA(insert OID = 3307 ( pg_stat_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2249 "25 16" "{25,16,20,1184,1184,1184,1184,16}" "{i,i,o,o,o,o,o,o}" "{filename,if_not_exists,size,access,modification,change,creation,isdir}" _null_ _null_ pg_stat_file _null_ _null_ _null_ )); +DESCR("get information about file"); +DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file_off_len _null_ _null_ _null_ )); +DESCR("read text from a file"); +DATA(insert OID = 3293 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 0 25 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); DATA(insert OID = 3826 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ )); DESCR("read text from a file"); -DATA(insert OID = 3827 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 17 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file _null_ _null_ _null_ )); +DATA(insert OID = 3827 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 17 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_off_len _null_ _null_ _null_ )); +DESCR("read bytea from a file"); +DATA(insert OID = 3295 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 0 17 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file _null_ _null_ _null_ )); DESCR("read bytea from a file"); DATA(insert OID = 3828 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 17 "25" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_all _null_ _null_ _null_ )); DESCR("read bytea from a file"); -DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 0 f f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); +DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 0 f f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_ls_dir_1arg _null_ _null_ _null_ )); +DESCR("list all files in a directory"); +DATA(insert OID = 3297 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 0 f f f f t t v 3 0 25 "25 16 16" _null_ _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); DESCR("sleep for the specified time in seconds"); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 51f25a2814..98556725c8 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -469,14 +469,16 @@ extern Datum pg_filenode_relation(PG_FUNCTION_ARGS); extern Datum pg_relation_filepath(PG_FUNCTION_ARGS); /* genfile.c */ -extern bytea *read_binary_file(const char *filename, - int64 seek_offset, int64 bytes_to_read); extern Datum pg_stat_file(PG_FUNCTION_ARGS); +extern Datum pg_stat_file_1arg(PG_FUNCTION_ARGS); extern Datum pg_read_file(PG_FUNCTION_ARGS); +extern Datum pg_read_file_off_len(PG_FUNCTION_ARGS); extern Datum pg_read_file_all(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_off_len(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file_all(PG_FUNCTION_ARGS); extern Datum pg_ls_dir(PG_FUNCTION_ARGS); +extern Datum pg_ls_dir_1arg(PG_FUNCTION_ARGS); /* misc.c */ extern Datum current_database(PG_FUNCTION_ARGS);