• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "appspawn_manager.h"
17 #include "appspawn_sandbox.h"
18 #include "appspawn_utils.h"
19 #include "modulemgr.h"
20 #include "parameter.h"
21 #include "securec.h"
22 
23 struct ListNode g_sandboxVarList = {&g_sandboxVarList, &g_sandboxVarList};
24 
VarPackageNameIndexReplace(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)25 static int VarPackageNameIndexReplace(const SandboxContext *context,
26     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
27 {
28     AppSpawnMsgBundleInfo *bundleInfo = (AppSpawnMsgBundleInfo *)GetSandboxCtxMsgInfo(context, TLV_BUNDLE_INFO);
29     APPSPAWN_CHECK(bundleInfo != NULL, return APPSPAWN_TLV_NONE,
30         "No bundle info in msg %{public}s", context->bundleName);
31     int len = 0;
32     if (bundleInfo->bundleIndex > 0) {
33         len = sprintf_s((char *)buffer, bufferLen, "%d_%s", bundleInfo->bundleIndex, bundleInfo->bundleName);
34     } else {
35         len = sprintf_s((char *)buffer, bufferLen, "%s", bundleInfo->bundleName);
36     }
37     APPSPAWN_CHECK(len > 0 && ((uint32_t)len < bufferLen),
38         return -1, "Failed to format path app: %{public}s", context->bundleName);
39     *realLen = (uint32_t)len;
40     return 0;
41 }
42 
VarPackageNameReplace(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)43 APPSPAWN_STATIC int VarPackageNameReplace(const SandboxContext *context,
44     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
45 {
46     int len = sprintf_s((char *)buffer, bufferLen, "%s", context->bundleName);
47     APPSPAWN_CHECK(len > 0 && ((uint32_t)len < bufferLen),
48         return -1, "Failed to format path app: %{public}s", context->bundleName);
49     *realLen = (uint32_t)len;
50     return 0;
51 }
52 
VarCurrentUseIdReplace(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)53 static int VarCurrentUseIdReplace(const SandboxContext *context,
54     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
55 {
56     AppSpawnMsgDacInfo *info = (AppSpawnMsgDacInfo *)GetSandboxCtxMsgInfo(context, TLV_DAC_INFO);
57     APPSPAWN_CHECK(info != NULL, return APPSPAWN_TLV_NONE,
58         "No tlv %{public}d in msg %{public}s", TLV_DAC_INFO, context->bundleName);
59     int len = 0;
60     if (extraData == NULL || !CHECK_FLAGS_BY_INDEX(extraData->operation, SANDBOX_TAG_PERMISSION)) {
61         len = sprintf_s((char *)buffer, bufferLen, "%u", info->uid / UID_BASE);
62     } else {
63         len = sprintf_s((char *)buffer, bufferLen, "%s", "currentUser");
64     }
65     APPSPAWN_CHECK(len > 0 && ((uint32_t)len < bufferLen),
66         return -1, "Failed to format path app: %{public}s", context->bundleName);
67     *realLen = (uint32_t)len;
68     return 0;
69 }
70 
VarCurrentHostUserIdReplace(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)71 static int VarCurrentHostUserIdReplace(const SandboxContext *context,
72     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
73 {
74     int uid = 0;
75     int len = 0;
76     char *hostUid =
77         (char *)GetAppSpawnMsgExtInfo(context->message, MSG_EXT_NAME_PARENT_UID, NULL);
78     if (hostUid != NULL) {
79         uid = atoi(hostUid);
80         len = sprintf_s((char *)buffer, bufferLen, "%d", uid / UID_BASE);
81     } else {
82         len = sprintf_s((char *)buffer, bufferLen, "%s", "hostUserId");
83     }
84     APPSPAWN_CHECK(len > 0 && ((uint32_t)len < bufferLen),
85         return -1, "Failed to format path app: %{public}s", context->bundleName);
86     *realLen = (uint32_t)len;
87     return 0;
88 }
89 
VarArkWebPackageNameReplace(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)90 static int VarArkWebPackageNameReplace(const SandboxContext *context,
91     const char *buffer, uint32_t bufferLen, uint32_t *realLen,
92     const VarExtraData *extraData)
93 {
94     static char arkWebPackageName[PARAM_BUFFER_SIZE] = {0};
95     if (strlen(arkWebPackageName) == 0) {
96         int len = GetParameter(ARK_WEB_PERSIST_PACKAGE_NAME, "",
97                                arkWebPackageName, sizeof(arkWebPackageName));
98         APPSPAWN_CHECK(len > 0, return -1,
99                 "Failed to get param for var %{public}s",
100                 ARK_WEB_PERSIST_PACKAGE_NAME);
101     }
102     APPSPAWN_LOGV("ArkWebPackageNameReplace '%{public}s'", arkWebPackageName);
103 
104     int len = sprintf_s((char*) buffer, bufferLen, "%s", arkWebPackageName);
105     APPSPAWN_CHECK(len > 0 && ((uint32_t)len < bufferLen), return -1,
106             "Failed to format path app: %{public}s", arkWebPackageName);
107     *realLen = (uint32_t) len;
108     return 0;
109 }
110 
VariableNodeCompareName(ListNode * node,void * data)111 static int VariableNodeCompareName(ListNode *node, void *data)
112 {
113     AppSandboxVarNode *varNode = (AppSandboxVarNode *)ListEntry(node, AppSandboxVarNode, node);
114     return strcmp((char *)data, varNode->name);
115 }
116 
GetAppSandboxVarNode(const char * name)117 static AppSandboxVarNode *GetAppSandboxVarNode(const char *name)
118 {
119     ListNode *node = OH_ListFind(&g_sandboxVarList, (void *)name, VariableNodeCompareName);
120     if (node == NULL) {
121         return NULL;
122     }
123     return (AppSandboxVarNode *)ListEntry(node, AppSandboxVarNode, node);
124 }
125 
ReplaceVariableByParameter(const char * varData,SandboxBuffer * sandboxBuffer)126 static int ReplaceVariableByParameter(const char *varData, SandboxBuffer *sandboxBuffer)
127 {
128     // "<param:persist.nweb.sandbox.src_path>"
129     int len = GetParameter(varData + sizeof("<param:") - 1,
130         DEFAULT_NWEB_SANDBOX_SEC_PATH, sandboxBuffer->buffer + sandboxBuffer->current,
131         sandboxBuffer->bufferLen - sandboxBuffer->current - 1);
132     APPSPAWN_CHECK(len > 0, return -1, "Failed to get param for var %{public}s", varData);
133     sandboxBuffer->current += len;
134     return 0;
135 }
136 
ReplaceVariableForDepSandboxPath(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)137 APPSPAWN_STATIC int ReplaceVariableForDepSandboxPath(const SandboxContext *context,
138     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
139 {
140     APPSPAWN_CHECK(extraData != NULL && extraData->data.depNode != NULL, return -1, "Invalid extra data ");
141     uint32_t len = strlen(extraData->data.depNode->target);
142     int ret = memcpy_s((char *)buffer, bufferLen, extraData->data.depNode->target, len);
143     APPSPAWN_CHECK(ret == 0, return -1, "Failed to copy real data");
144     *realLen = len;
145     return 0;
146 }
147 
ReplaceVariableForDepSrcPath(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)148 APPSPAWN_STATIC int ReplaceVariableForDepSrcPath(const SandboxContext *context,
149     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
150 {
151     APPSPAWN_CHECK(extraData != NULL && extraData->data.depNode != NULL, return -1, "Invalid extra data ");
152     uint32_t len = strlen(extraData->data.depNode->source);
153     int ret = memcpy_s((char *)buffer, bufferLen, extraData->data.depNode->source, len);
154     APPSPAWN_CHECK(ret == 0, return -1, "Failed to copy real data");
155     *realLen = len;
156     return 0;
157 }
158 
ReplaceVariableForDepPath(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)159 APPSPAWN_STATIC int ReplaceVariableForDepPath(const SandboxContext *context,
160     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
161 {
162     APPSPAWN_CHECK(extraData != NULL && extraData->data.depNode != NULL, return -1, "Invalid extra data ");
163     char *path = extraData->data.depNode->source;
164     if (CHECK_FLAGS_BY_INDEX(extraData->operation, MOUNT_PATH_OP_REPLACE_BY_SANDBOX)) {
165         path = extraData->data.depNode->target;
166     } else if (CHECK_FLAGS_BY_INDEX(extraData->operation, MOUNT_PATH_OP_REPLACE_BY_SRC) && IsPathEmpty(path)) {
167         path = extraData->data.depNode->target;
168     }
169     APPSPAWN_CHECK(path != NULL, return -1, "Invalid path %{public}x ", extraData->operation);
170     uint32_t len = strlen(path);
171     int ret = memcpy_s((char *)buffer, bufferLen, path, len);
172     APPSPAWN_CHECK(ret == 0, return -1, "Failed to copy real data");
173     *realLen = len;
174     return 0;
175 }
176 
ReplaceVariableForpackageName(const SandboxContext * context,const char * buffer,uint32_t bufferLen,uint32_t * realLen,const VarExtraData * extraData)177 static int ReplaceVariableForpackageName(const SandboxContext *context,
178     const char *buffer, uint32_t bufferLen, uint32_t *realLen, const VarExtraData *extraData)
179 {
180     APPSPAWN_CHECK(context != NULL, return -1, "Invalid context");
181     AppSpawnMsgBundleInfo *bundleInfo = (AppSpawnMsgBundleInfo *)GetSandboxCtxMsgInfo(context, TLV_BUNDLE_INFO);
182     APPSPAWN_CHECK(bundleInfo != NULL, return APPSPAWN_TLV_NONE,
183         "No bundle info in msg %{public}s", context->bundleName);
184 
185     uint32_t flags = 0;
186     char *extension = NULL;
187     if (CheckAppSpawnMsgFlag(context->message, TLV_MSG_FLAGS, APP_FLAGS_ATOMIC_SERVICE)) {
188         flags |= SANDBOX_PACKAGENAME_ATOMIC_SERVICE;
189     } else {
190         flags |= (CheckAppSpawnMsgFlag(context->message, TLV_MSG_FLAGS, APP_FLAGS_CLONE_ENABLE) &&
191             bundleInfo->bundleIndex > 0) ? SANDBOX_PACKAGENAME_CLONE : 0;
192         flags |= CheckAppSpawnMsgFlag(context->message, TLV_MSG_FLAGS, APP_FLAGS_EXTENSION_SANDBOX)
193             ? SANDBOX_PACKAGENAME_EXTENSION : 0;
194         extension = (char *)GetAppSpawnMsgExtInfo(context->message, MSG_EXT_NAME_APP_EXTENSION, NULL);
195     }
196 
197     int32_t len = 0;
198     switch (flags) {
199         case SANDBOX_PACKAGENAME_DEFAULT:               // 0 packageName
200             len = sprintf_s((char *)buffer, bufferLen, "%s", bundleInfo->bundleName);
201             break;
202         case SANDBOX_PACKAGENAME_CLONE:                 // 1 +clone-bundleIndex+packageName
203             len = sprintf_s((char *)buffer, bufferLen, "+clone-%u+%s", bundleInfo->bundleIndex, bundleInfo->bundleName);
204             break;
205         case SANDBOX_PACKAGENAME_EXTENSION: {           // 2 +extension-<extensionType>+packageName
206             APPSPAWN_CHECK(extension != NULL, return -1, "Invalid extension data");
207             len = sprintf_s((char *)buffer, bufferLen, "+extension-%s+%s", extension, bundleInfo->bundleName);
208             break;
209         }
210         case SANDBOX_PACKAGENAME_CLONE_AND_EXTENSION: { // 3 +clone-bundleIndex+extension-<extensionType>+packageName
211             APPSPAWN_CHECK(extension != NULL, return -1, "Invalid extension data");
212             len = sprintf_s((char *)buffer, bufferLen, "+clone-%u+extension-%s+%s",
213                 bundleInfo->bundleIndex, extension, bundleInfo->bundleName);
214             break;
215         }
216         case SANDBOX_PACKAGENAME_ATOMIC_SERVICE: {      // 4 +auid-<accountId>+packageName
217             char *accountId = (char *)GetAppSpawnMsgExtInfo(context->message, MSG_EXT_NAME_ACCOUNT_ID, NULL);
218             APPSPAWN_CHECK(accountId != NULL, return -1, "Invalid accountId data");
219             len = sprintf_s((char *)buffer, bufferLen, "+auid-%s+%s", accountId, bundleInfo->bundleName);
220             break;
221         }
222         default:
223             break;
224     }
225     APPSPAWN_CHECK(len > 0 && ((uint32_t)len < bufferLen),
226         return -1, "Failed to format path app: %{public}s flags %{public}u", context->bundleName, flags);
227     *realLen = (uint32_t)len;
228     return 0;
229 }
230 
GetVariableName(char * varData,uint32_t len,const char * varStart,uint32_t * varLen)231 static int GetVariableName(char *varData, uint32_t len, const char *varStart, uint32_t *varLen)
232 {
233     uint32_t i = 0;
234     uint32_t sourceLen = strlen(varStart);
235     for (; i < sourceLen; i++) {
236         if (i > len) {
237             return -1;
238         }
239         varData[i] = *(varStart + i);
240         if (varData[i] == '>') {
241             break;
242         }
243     }
244     varData[i + 1] = '\0';
245     *varLen = i + 1;
246     return 0;
247 }
248 
ReplaceVariable(const SandboxContext * context,const char * varStart,SandboxBuffer * sandboxBuffer,uint32_t * varLen,const VarExtraData * extraData)249 static int ReplaceVariable(const SandboxContext *context,
250     const char *varStart, SandboxBuffer *sandboxBuffer, uint32_t *varLen, const VarExtraData *extraData)
251 {
252     char varName[128] = {0};  // 128 max len for var
253     int ret = GetVariableName(varName, sizeof(varName), varStart, varLen);
254     APPSPAWN_CHECK(ret == 0, return -1, "Failed to get variable name");
255 
256     uint32_t valueLen = 0;
257     AppSandboxVarNode *node = GetAppSandboxVarNode(varName);
258     if (node != NULL) {
259         ret = node->replaceVar(context, sandboxBuffer->buffer + sandboxBuffer->current,
260             sandboxBuffer->bufferLen - sandboxBuffer->current - 1, &valueLen, extraData);
261         APPSPAWN_CHECK(ret == 0 && valueLen < (sandboxBuffer->bufferLen - sandboxBuffer->current),
262             return -1, "Failed to fill real data");
263         sandboxBuffer->current += valueLen;
264         return 0;
265     }
266     // "<param:persist.nweb.sandbox.src_path>"
267     if (strncmp(varName, "<param:", sizeof("<param:") - 1) == 0) {  // retry param:
268         varName[*varLen - 1] = '\0';                                // erase last >
269         return ReplaceVariableByParameter(varName, sandboxBuffer);
270     }
271     if (strncmp(varName, "<lib>", sizeof("<lib>") - 1) == 0) {  // retry lib
272         ret = memcpy_s(sandboxBuffer->buffer + sandboxBuffer->current,
273             sandboxBuffer->bufferLen - sandboxBuffer->current, APPSPAWN_LIB_NAME, strlen(APPSPAWN_LIB_NAME));
274         APPSPAWN_CHECK(ret == 0, return -1, "Failed to copy real data");
275         sandboxBuffer->current += strlen(APPSPAWN_LIB_NAME);
276         return 0;
277     }
278     // no match revered origin data
279     APPSPAWN_LOGE("ReplaceVariable var '%{public}s' no match variable", varName);
280     ret = memcpy_s(sandboxBuffer->buffer + sandboxBuffer->current,
281         sandboxBuffer->bufferLen - sandboxBuffer->current, varName, *varLen);
282     APPSPAWN_CHECK(ret == 0, return -1, "Failed to copy real data");
283     sandboxBuffer->current += *varLen;
284     return 0;
285 }
286 
HandleVariableReplace(const SandboxContext * context,SandboxBuffer * sandboxBuffer,const char * source,const VarExtraData * extraData)287 static int HandleVariableReplace(const SandboxContext *context,
288     SandboxBuffer *sandboxBuffer, const char *source, const VarExtraData *extraData)
289 {
290     size_t sourceLen = strlen(source);
291     for (size_t i = 0; i < sourceLen; i++) {
292         if ((sandboxBuffer->current + 1) >= sandboxBuffer->bufferLen) {
293             return -1;
294         }
295         if (*(source + i) != '<') {  // copy source
296             *(sandboxBuffer->buffer + sandboxBuffer->current) = *(source + i);
297             sandboxBuffer->current++;
298             continue;
299         }
300         uint32_t varLen = 0;
301         int ret = ReplaceVariable(context, source + i, sandboxBuffer, &varLen, extraData);
302         APPSPAWN_CHECK(ret == 0, return ret, "Failed to fill real data");
303         i += (varLen - 1);
304     }
305     return 0;
306 }
307 
GetSandboxRealVar(const SandboxContext * context,uint32_t bufferType,const char * source,const char * prefix,const VarExtraData * extraData)308 const char *GetSandboxRealVar(const SandboxContext *context, uint32_t bufferType, const char *source,
309                               const char *prefix, const VarExtraData *extraData)
310 {
311     APPSPAWN_CHECK_ONLY_EXPER(context != NULL, return NULL);
312     APPSPAWN_CHECK(bufferType < ARRAY_LENGTH(context->buffer), return NULL, "Invalid index for buffer");
313     SandboxBuffer *sandboxBuffer = &((SandboxContext *)context)->buffer[bufferType];
314     APPSPAWN_CHECK_ONLY_EXPER(sandboxBuffer != NULL && sandboxBuffer->buffer != NULL, return NULL);
315     const char *tmp = source;
316     int ret = 0;
317     if (!IsPathEmpty(prefix)) {  // copy prefix data
318         ret = HandleVariableReplace(context, sandboxBuffer, prefix, extraData);
319         APPSPAWN_CHECK(ret == 0, return NULL, "Failed to replace source %{public}s ", prefix);
320 
321         if (tmp != NULL && sandboxBuffer->buffer[sandboxBuffer->current - 1] == '/' && *tmp == '/') {
322             tmp = source + 1;
323         }
324     }
325     if (!IsPathEmpty(tmp)) {  // copy source data
326         ret = HandleVariableReplace(context, sandboxBuffer, tmp, extraData);
327         APPSPAWN_CHECK(ret == 0, return NULL, "Failed to replace source %{public}s ", source);
328     }
329     sandboxBuffer->buffer[sandboxBuffer->current] = '\0';
330     // restore buffer
331     sandboxBuffer->current = 0;
332 
333     // For the depNode scenario, if there are variables in the deps path, a secondary replacement is required
334     if (extraData != NULL && extraData->sandboxTag == SANDBOX_TAG_NAME_GROUP && extraData->data.depNode != NULL) {
335         if (strstr(sandboxBuffer->buffer, "<") != NULL) {
336             SandboxBuffer *tmpBuffer = &((SandboxContext *)context)->buffer[BUFFER_FOR_TMP];
337             ret = HandleVariableReplace(context, tmpBuffer, sandboxBuffer->buffer, extraData);
338             APPSPAWN_CHECK(ret == 0, return NULL, "Failed to replace source %{public}s ", sandboxBuffer->buffer);
339             tmpBuffer->buffer[tmpBuffer->current] = '\0';
340             ret = strcpy_s(sandboxBuffer->buffer, sandboxBuffer->bufferLen, tmpBuffer->buffer);
341             APPSPAWN_CHECK(ret == 0, return NULL, "Failed to copy source %{public}s ", sandboxBuffer->buffer);
342         }
343     }
344     return sandboxBuffer->buffer;
345 }
346 
AddVariableReplaceHandler(const char * name,ReplaceVarHandler handler)347 int AddVariableReplaceHandler(const char *name, ReplaceVarHandler handler)
348 {
349     APPSPAWN_CHECK(name != NULL && handler != NULL, return APPSPAWN_ARG_INVALID, "Invalid arg ");
350     if (GetAppSandboxVarNode(name) != NULL) {
351         return APPSPAWN_NODE_EXIST;
352     }
353 
354     size_t len = APPSPAWN_ALIGN(strlen(name) + 1);
355     AppSandboxVarNode *node = (AppSandboxVarNode *)malloc(sizeof(AppSandboxVarNode) + len);
356     APPSPAWN_CHECK(node != NULL, return APPSPAWN_SYSTEM_ERROR, "Failed to create sandbox");
357     OH_ListInit(&node->node);
358     node->replaceVar = handler;
359     int ret = strcpy_s(node->name, len, name);
360     APPSPAWN_CHECK(ret == 0, free(node);
361                              node = NULL;
362                              return -1, "Failed to copy name %{public}s", name);
363     OH_ListAddTail(&g_sandboxVarList, &node->node);
364     return 0;
365 }
366 
AddDefaultVariable(void)367 void AddDefaultVariable(void)
368 {
369     AddVariableReplaceHandler(PARAMETER_PACKAGE_NAME, VarPackageNameReplace);
370     AddVariableReplaceHandler(PARAMETER_USER_ID, VarCurrentUseIdReplace);
371     AddVariableReplaceHandler(PARAMETER_HOST_USER_ID, VarCurrentHostUserIdReplace);
372     AddVariableReplaceHandler(PARAMETER_PACKAGE_INDEX, VarPackageNameIndexReplace);
373     AddVariableReplaceHandler(PARAMETER_ARK_WEB_PACKAGE_INDEX, VarArkWebPackageNameReplace);
374     /*
375         deps-path路径变量的含义:
376         1)首次挂载时,表示mount-paths-deps->sandbox-path  【STAGE_GLOBAL或者应用孵化时的挂载】
377         使用 MOUNT_PATH_OP_REPLACE_BY_SANDBOX 标记
378         2)二次挂载时,表示mount-paths-deps->src-path;
379             如果mount-paths-deps->src-path为空,则使用mount-paths-deps->sandbox-path
380         使用 MOUNT_PATH_OP_ONLY_SANDBOX + MOUNT_PATH_OP_REPLACE_BY_SRC,只使用源目录,不添加root-dir
381         【RemountByName时,如el2解锁或nweb更新时】
382     */
383     AddVariableReplaceHandler("<deps-sandbox-path>", ReplaceVariableForDepSandboxPath);
384     AddVariableReplaceHandler("<deps-src-path>", ReplaceVariableForDepSrcPath);
385     AddVariableReplaceHandler("<deps-path>", ReplaceVariableForDepPath);
386     AddVariableReplaceHandler("<variablePackageName>", ReplaceVariableForpackageName);
387 }
388 
ClearVariable(void)389 void ClearVariable(void)
390 {
391     OH_ListRemoveAll(&g_sandboxVarList, NULL);
392 }
393