• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 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 "cert_manager.h"
17 
18 #include <unistd.h>
19 
20 #include "cert_manager_auth_mgr.h"
21 #include "cert_manager_file.h"
22 #include "cert_manager_file_operator.h"
23 #include "cert_manager_key_operation.h"
24 #include "cert_manager_mem.h"
25 #include "cert_manager_permission_check.h"
26 #include "cert_manager_status.h"
27 #include "cert_manager_storage.h"
28 #include "cert_manager_uri.h"
29 #include "cm_log.h"
30 #include "cm_type.h"
31 #include "cm_x509.h"
32 
33 #include "securec.h"
34 
35 #include "hks_api.h"
36 
37 #define MAX_PATH_LEN                        256
38 
39 #ifdef __cplusplus
40 extern "C" {
41 #endif
42 
43 static bool g_hksInitialized = false;
44 
CertManagerInitialize(void)45 int32_t CertManagerInitialize(void)
46 {
47     if (!g_hksInitialized) {
48         ASSERT_CM_CALL(HksInitialize());
49         g_hksInitialized = true;
50     }
51 
52     if (CmMakeDir(CERT_DIR) == CMR_ERROR_MAKE_DIR_FAIL) {
53         CM_LOG_E("Failed to create folder\n");
54         return CMR_ERROR_WRITE_FILE_FAIL;
55     }
56 
57     ASSERT_FUNC(CertManagerStatusInit());
58 
59     return CMR_OK;
60 }
61 
GetFilePath(const struct CmContext * context,uint32_t store,char * pathPtr,char * suffix,uint32_t * suffixLen)62 static int32_t GetFilePath(const struct CmContext *context, uint32_t store, char *pathPtr,
63     char *suffix, uint32_t *suffixLen)
64 {
65     int32_t ret, retVal;
66     if (suffix == NULL || suffixLen == NULL) {
67         CM_LOG_E("NULL pointer failure");
68         return CMR_ERROR_NULL_POINTER;
69     }
70 
71     switch (store) {
72         if (context == NULL) {
73             CM_LOG_E("Null pointer failture");
74             return CMR_ERROR_NULL_POINTER;
75         }
76         case CM_CREDENTIAL_STORE:
77         case CM_USER_TRUSTED_STORE:
78         case CM_PRI_CREDENTIAL_STORE:
79             if (store == CM_CREDENTIAL_STORE) {
80                 ret = sprintf_s(pathPtr, MAX_PATH_LEN, "%s%u", CREDNTIAL_STORE, context->userId);
81             } else if (store == CM_PRI_CREDENTIAL_STORE) {
82                 ret = sprintf_s(pathPtr, MAX_PATH_LEN, "%s%u", APP_CA_STORE, context->userId);
83             } else {
84                 ret = sprintf_s(pathPtr, MAX_PATH_LEN, "%s%u", USER_CA_STORE, context->userId);
85             }
86 
87             retVal = sprintf_s(suffix, MAX_SUFFIX_LEN, "%u", context->uid);
88             if (ret < 0 || retVal < 0) {
89                 CM_LOG_E("Construct file Path failed ret:%d, retVal:%d", ret, retVal);
90                 return CMR_ERROR;
91             }
92             break;
93         case CM_SYSTEM_TRUSTED_STORE:
94             ret = sprintf_s(pathPtr, MAX_PATH_LEN, "%s", SYSTEM_CA_STORE);
95             if (ret < 0) {
96                 return CMR_ERROR;
97             }
98             break;
99 
100         default:
101             return CMR_ERROR_NOT_SUPPORTED;
102     }
103     *suffixLen = (uint32_t)strlen(suffix);
104     return CMR_OK;
105 }
106 
CmGetFilePath(const struct CmContext * context,uint32_t store,struct CmMutableBlob * pathBlob)107 static int32_t CmGetFilePath(const struct CmContext *context, uint32_t store, struct CmMutableBlob *pathBlob)
108 {
109     char pathPtr[MAX_PATH_LEN] = {0};
110     uint32_t suffixLen = 0;
111     char suffixBuf[MAX_SUFFIX_LEN] = {0};
112 
113     if ((pathBlob == NULL) || (pathBlob->data == NULL)) {
114         CM_LOG_E("Null pointer failure");
115         return CMR_ERROR_NULL_POINTER;
116     }
117     int32_t ret = GetFilePath(context, store, pathPtr, suffixBuf, &suffixLen);
118     if (ret != CMR_OK) {
119         CM_LOG_E("Get file path faild");
120         return CMR_ERROR;
121     }
122 
123     /* Create folder if it does not exist */
124     if (CmMakeDir(pathPtr) == CMR_ERROR_MAKE_DIR_FAIL) {
125         CM_LOG_E("Failed to create path folder");
126         return CMR_ERROR_WRITE_FILE_FAIL;
127     }
128 
129     if (pathBlob->size - 1 < strlen(pathPtr) + suffixLen) {
130         CM_LOG_E("Failed to copy path");
131         return CMR_ERROR_BUFFER_TOO_SMALL;
132     }
133 
134     char *path = (char *)pathBlob->data;
135     if (suffixLen == 0) {
136         if (sprintf_s(path, MAX_PATH_LEN, "%s", pathPtr) < 0) {
137             return CM_FAILURE;
138         }
139     } else {
140         if (sprintf_s(path, MAX_PATH_LEN, "%s/%s", pathPtr, suffixBuf) < 0) {
141             return CM_FAILURE;
142         }
143     }
144 
145     pathBlob->size = strlen(path) + 1;
146     if (CmMakeDir(path) == CMR_ERROR_MAKE_DIR_FAIL) {
147         CM_LOG_E("Failed to create folder");
148         return CMR_ERROR_WRITE_FILE_FAIL;
149     }
150     return CMR_OK;
151 }
152 
FindObjectCert(const struct CmBlob * certUri,const struct CmMutableBlob * fNames,uint32_t certCount)153 static int32_t FindObjectCert(const struct CmBlob *certUri, const struct CmMutableBlob *fNames, uint32_t certCount)
154 {
155     for (uint32_t i = 0; i < certCount; i++) {
156         if (fNames[i].data == NULL) {
157             CM_LOG_E("Corrupted file name at index: %u", i);
158             return CMR_ERROR_STORAGE;
159         }
160         /* Check if url is matching with the cert filename */
161         if ((certUri->size <= fNames[i].size) && (memcmp(certUri->data, fNames[i].data, certUri->size) == 0)) {
162             return CM_SUCCESS;
163         }
164     }
165     return CMR_ERROR_NOT_FOUND;
166 }
167 
CertManagerFindCertFileNameByUri(const struct CmContext * context,const struct CmBlob * certUri,uint32_t store,struct CmMutableBlob * path)168 int32_t CertManagerFindCertFileNameByUri(const struct CmContext *context, const struct CmBlob *certUri,
169     uint32_t store, struct CmMutableBlob *path)
170 {
171     ASSERT_ARGS(context && certUri && certUri->data);
172 
173     int32_t ret = CmGetFilePath(context, store, path);
174     if (ret != CM_SUCCESS) {
175         CM_LOG_E("Failed obtain path for store %x\n", store);
176         return ret;
177     }
178 
179     struct CmMutableBlob fileNames = { 0, NULL };
180     ret = CertManagerGetFilenames(&fileNames, (char *)path->data);
181     if (ret != CM_SUCCESS) {
182         CM_LOG_E("Failed obtain filenames from path");
183         return CMR_ERROR_STORAGE;
184     }
185 
186     struct CmMutableBlob *fNames = (struct CmMutableBlob *)fileNames.data;
187     ret = FindObjectCert(certUri, fNames, fileNames.size);
188     FreeFileNames(fNames, fileNames.size);
189     if (ret != CM_SUCCESS) {
190         CM_LOG_E("No cert matched, err: %d", ret);
191     }
192     return ret;
193 }
194 
CmRemoveAppCert(const struct CmContext * context,const struct CmBlob * keyUri,const uint32_t store)195 int32_t CmRemoveAppCert(const struct CmContext *context, const struct CmBlob *keyUri,
196     const uint32_t store)
197 {
198     ASSERT_ARGS(context && keyUri && keyUri->data && keyUri->size);
199     int32_t ret;
200     if (store == CM_CREDENTIAL_STORE) {
201         ret = CmAuthDeleteAuthInfo(context, keyUri);
202         if (ret != CM_SUCCESS) {
203             CM_LOG_E("delete auth info failed when remove app certificate."); /* ignore ret code, only record log */
204         }
205     }
206 
207     char pathBuf[CERT_MAX_PATH_LEN] = {0};
208     struct CmMutableBlob path = { sizeof(pathBuf), (uint8_t*) pathBuf };
209 
210     ret = CmGetFilePath(context, store, &path);
211     if (ret != CMR_OK) {
212         CM_LOG_E("Failed obtain path for store %u", store);
213         return ret;
214     }
215 
216     ret = CertManagerFileRemove(pathBuf, (char *)keyUri->data);
217     if (ret != CMR_OK) {
218         CM_LOG_E("CertManagerFileRemove failed ret: %d", ret);
219         return ret;
220     }
221     ret = CmKeyOpDeleteKey(keyUri);
222     if (ret != CM_SUCCESS) { /* ignore the return of deleteKey */
223         CM_LOG_I("CertManagerKeyRemove failed, ret: %d", ret);
224     }
225 
226     return CMR_OK;
227 }
228 
ClearAuthInfo(const struct CmContext * context,const struct CmBlob * keyUri,const uint32_t store)229 static void ClearAuthInfo(const struct CmContext *context, const struct CmBlob *keyUri, const uint32_t store)
230 {
231     if (store != CM_CREDENTIAL_STORE) {
232         return;
233     }
234 
235     int32_t ret = CmAuthDeleteAuthInfo(context, keyUri);
236     if (ret != CM_SUCCESS) {
237         CM_LOG_E("delete auth info failed."); /* ignore ret code, only record log */
238     }
239 }
240 
CmAppCertGetFilePath(const struct CmContext * context,const uint32_t store,struct CmBlob * path)241 static int32_t CmAppCertGetFilePath(const struct CmContext *context, const uint32_t store, struct CmBlob *path)
242 {
243     int32_t ret = CM_FAILURE;
244 
245     switch (store) {
246         case CM_CREDENTIAL_STORE :
247             ret = sprintf_s((char*)path->data, MAX_PATH_LEN, "%s%u/%u", CREDNTIAL_STORE, context->userId, context->uid);
248             break;
249         case CM_PRI_CREDENTIAL_STORE :
250             ret = sprintf_s((char*)path->data, MAX_PATH_LEN, "%s%u", APP_CA_STORE, context->userId);
251             break;
252         default:
253             break;
254     }
255     if (ret < 0) {
256         return CM_FAILURE;
257     }
258     return CM_SUCCESS;
259 }
260 
CmFreeFileNames(struct CmBlob * fileNames,const uint32_t fileSize)261 void CmFreeFileNames(struct CmBlob *fileNames, const uint32_t fileSize)
262 {
263     if (fileNames == NULL) {
264         CM_LOG_E("CmFreeFileNames fileNames is null");
265         return;
266     }
267 
268     for (uint32_t i = 0; i < fileSize; i++) {
269         if (fileNames[i].data != NULL) {
270             CMFree(fileNames[i].data);
271             fileNames[i].size = 0;
272         }
273     }
274 }
275 
CmGetUri(const char * filePath,struct CmBlob * uriBlob)276 int32_t CmGetUri(const char *filePath, struct CmBlob *uriBlob)
277 {
278     if ((filePath == NULL) || (uriBlob == NULL) || (uriBlob->data == NULL)) {
279         CM_LOG_E("CmGetUri param is null");
280         return CM_FAILURE;
281     }
282 
283     uint32_t filePathLen = strlen(filePath);
284     if ((filePathLen == 0) || (filePathLen > CM_MAX_FILE_NAME_LEN)) {
285         return CMR_ERROR_INVALID_ARGUMENT;
286     }
287 
288     int32_t i = (int32_t)(filePathLen - 1);
289     for (; i >= 0; i--) {
290         if (filePath[i] == '/') {
291             break;
292         }
293     }
294 
295     int32_t index = i + 1; /* index range: 0 to filePathLen */
296     uint32_t uriLen = filePathLen - (uint32_t)index + 1; /* include '\0' at end, range: 1 to filePathLen + 1 */
297     if (memcpy_s(uriBlob->data, uriBlob->size, &filePath[index], uriLen) != EOK) {
298         return CMR_ERROR_BUFFER_TOO_SMALL;
299     }
300     uriBlob->size = uriLen;
301 
302     return CM_SUCCESS;
303 }
304 
CmRemoveSpecifiedAppCert(const struct CmContext * context,const uint32_t store)305 static int32_t CmRemoveSpecifiedAppCert(const struct CmContext *context, const uint32_t store)
306 {
307     uint32_t fileCount = 0;
308     int32_t retCode, ret = CM_SUCCESS;
309     char pathBuf[CERT_MAX_PATH_LEN] = {0};
310     char uriBuf[MAX_LEN_URI] = {0};
311     struct CmBlob fileNames[MAX_COUNT_CERTIFICATE];
312     struct CmBlob path = { sizeof(pathBuf), (uint8_t*)pathBuf };
313     struct CmBlob uriBlob = { sizeof(uriBuf), (uint8_t*)uriBuf };
314     uint32_t len = MAX_COUNT_CERTIFICATE * sizeof(struct CmBlob);
315     (void)memset_s(fileNames, len, 0, len);
316 
317     do {
318         if (CmAppCertGetFilePath(context, store, &path) != CM_SUCCESS) {
319             ret = CM_FAILURE;
320             CM_LOG_E("Get file path for store:%u faild", store);
321             break;
322         }
323 
324         if (CmUserIdLayerGetFileCountAndNames(pathBuf, fileNames, MAX_COUNT_CERTIFICATE, &fileCount) != CM_SUCCESS) {
325             ret = CM_FAILURE;
326             CM_LOG_E("Get file count and names from path faild");
327             break;
328         }
329 
330         for (uint32_t i = 0; i < fileCount; i++) {
331             if (CertManagerFileRemove(NULL, (char *)fileNames[i].data) != CM_SUCCESS) {
332                 CM_LOG_E("App cert %u remove faild", i);
333                 continue;
334             }
335 
336             uriBlob.size = sizeof(uriBuf);
337             (void)memset_s(uriBuf, uriBlob.size, 0, uriBlob.size);
338             if (CmGetUri((char *)fileNames[i].data, &uriBlob) != CM_SUCCESS) {
339                 CM_LOG_E("Get uri failed");
340                 continue;
341             }
342 
343             retCode = CmKeyOpDeleteKey(&uriBlob);
344             if (retCode != CM_SUCCESS) { /* ignore the return of deleteKey */
345                 CM_LOG_I("App key %u remove failed ret: %d", i, retCode);
346             }
347             ClearAuthInfo(context, &uriBlob, store);
348         }
349     } while (0);
350 
351     CmFreeFileNames(fileNames, MAX_COUNT_CERTIFICATE);
352     return ret;
353 }
354 
CmRemoveAllAppCert(const struct CmContext * context)355 int32_t CmRemoveAllAppCert(const struct CmContext *context)
356 {
357     if (!CmHasPrivilegedPermission() || !CmHasCommonPermission()) {
358         CM_LOG_E("permission check failed");
359         return CMR_ERROR_PERMISSION_DENIED;
360     }
361     if (!CmIsSystemApp()) {
362         CM_LOG_E("remove app cert: caller is not system app");
363         return CMR_ERROR_NOT_SYSTEMP_APP;
364     }
365 
366     /* Only public and private credential removed can be returned */
367     /* remove pubic credential app cert */
368     int32_t ret = CmRemoveSpecifiedAppCert(context, CM_CREDENTIAL_STORE);
369     if (ret != CM_SUCCESS) {
370         CM_LOG_E("remove pubic credential app cert faild");
371     }
372 
373     /* remove private credential app cert */
374     ret = CmRemoveSpecifiedAppCert(context, CM_PRI_CREDENTIAL_STORE);
375     if (ret != CM_SUCCESS) {
376         CM_LOG_E("remove private credential app cert faild");
377     }
378 
379     return ret;
380 }
381 
CmServiceGetAppCertList(const struct CmContext * context,uint32_t store,struct CmBlob * fileNames,const uint32_t fileSize,uint32_t * fileCount)382 int32_t CmServiceGetAppCertList(const struct CmContext *context, uint32_t store, struct CmBlob *fileNames,
383     const uint32_t fileSize, uint32_t *fileCount)
384 {
385     char pathBuf[CERT_MAX_PATH_LEN] = {0};
386     struct CmBlob path = { sizeof(pathBuf), (uint8_t*)pathBuf };
387 
388     int32_t ret = CmAppCertGetFilePath(context, store, &path);
389     if (ret != CM_SUCCESS) {
390         CM_LOG_E("Get file path for store:%u faild", store);
391         return CM_FAILURE;
392     }
393 
394     CM_LOG_I("Get app cert list path");
395 
396     if (store == CM_CREDENTIAL_STORE) {
397         ret = CmUidLayerGetFileCountAndNames(pathBuf, fileNames, fileSize, fileCount);
398     } else {
399         ret = CmUserIdLayerGetFileCountAndNames(pathBuf, fileNames, fileSize, fileCount);
400     }
401     if (ret != CM_SUCCESS) {
402         CM_LOG_E("Get file count and names from path faild ret:%d", ret);
403         return ret;
404     }
405 
406     CM_LOG_I("Get app cert list fileCount:%u", *fileCount);
407 
408     return CM_SUCCESS;
409 }
410 
CherkCertCountBeyondMax(const char * path,const char * fileName)411 static int32_t CherkCertCountBeyondMax(const char *path, const char *fileName)
412 {
413     int32_t ret = CM_FAILURE;
414 
415     do {
416         int32_t tempCount = GetCertCount(path);
417         if (tempCount < MAX_COUNT_CERTIFICATE) {
418             ret = CM_SUCCESS;
419             break;
420         }
421 
422         char fullFileName[CM_MAX_FILE_NAME_LEN] = {0};
423         if (snprintf_s(fullFileName, CM_MAX_FILE_NAME_LEN, CM_MAX_FILE_NAME_LEN - 1, "%s/%s", path, fileName) < 0) {
424             CM_LOG_E("mkdir full name failed");
425             ret = CM_FAILURE;
426             break;
427         }
428 
429         if (access(fullFileName, F_OK) == 0) {
430             ret = CM_SUCCESS;
431             break;
432         }
433     } while (0);
434     return ret;
435 }
436 
ConstructCertUri(const struct CmContext * context,const struct CmBlob * certAlias,struct CmBlob * certUri)437 static int32_t ConstructCertUri(const struct CmContext *context, const struct CmBlob *certAlias,
438     struct CmBlob *certUri)
439 {
440     struct CmBlob commonUri = { 0, NULL };
441     int32_t ret;
442     do {
443         ret = CmConstructCommonUri(context, CM_URI_TYPE_CERTIFICATE, certAlias, &commonUri);
444         if (ret != CM_SUCCESS) {
445             CM_LOG_E("construct cert uri get common uri failed");
446             break;
447         }
448 
449         if (certUri->size < commonUri.size) {
450             CM_LOG_E("out cert uri size[%u] too small", certUri->size);
451             ret = CMR_ERROR_BUFFER_TOO_SMALL;
452             break;
453         }
454 
455         if (memcpy_s(certUri->data, certUri->size, commonUri.data, commonUri.size) != EOK) {
456             CM_LOG_E("copy cert uri failed");
457             ret = CMR_ERROR_INVALID_OPERATION;
458             break;
459         }
460 
461         certUri->size = commonUri.size;
462     } while (0);
463 
464     CM_FREE_PTR(commonUri.data);
465     return ret;
466 }
467 
CmWriteUserCert(const struct CmContext * context,struct CmMutableBlob * pathBlob,const struct CmBlob * userCert,const struct CmBlob * certAlias,struct CmBlob * certUri)468 int32_t CmWriteUserCert(const struct CmContext *context, struct CmMutableBlob *pathBlob,
469     const struct CmBlob *userCert, const struct CmBlob *certAlias, struct CmBlob *certUri)
470 {
471     if (certAlias->size > MAX_LEN_CERT_ALIAS) {
472         CM_LOG_E("alias size is too large");
473         return CMR_ERROR_INVALID_ARGUMENT;
474     }
475 
476     int32_t ret;
477     do {
478         ret = ConstructCertUri(context, certAlias, certUri);
479         if (ret != CM_SUCCESS) {
480             CM_LOG_E("get cert uri failed");
481             break;
482         }
483 
484         if (certUri->size > MAX_LEN_URI) {
485             CM_LOG_E("uri size is too large");
486             ret = CMR_ERROR_INVALID_ARGUMENT;
487             break;
488         }
489 
490         ret = CherkCertCountBeyondMax((char*)pathBlob->data, (char *)certUri->data);
491         if (ret != CM_SUCCESS) {
492             CM_LOG_E("cert count beyond maxcount, can't install");
493             ret = CMR_ERROR_INVALID_ARGUMENT;
494             break;
495         }
496 
497         if (CmFileWrite((char*)pathBlob->data, (char *)certUri->data, 0, userCert->data, userCert->size) != CMR_OK) {
498             CM_LOG_E("Failed to write certificate");
499             ret = CMR_ERROR_WRITE_FILE_FAIL;
500             break;
501         }
502     } while (0);
503     return ret;
504 }
505 
CmRemoveUserCert(struct CmMutableBlob * pathBlob,const struct CmBlob * certUri)506 int32_t CmRemoveUserCert(struct CmMutableBlob *pathBlob, const struct CmBlob *certUri)
507 {
508     return CertManagerFileRemove((char *)pathBlob->data, (char *)certUri->data);
509 }
510 
RemoveAllUserCert(const struct CmContext * context,uint32_t store,const char * path)511 static int32_t RemoveAllUserCert(const struct CmContext *context, uint32_t store, const char* path)
512 {
513     ASSERT_ARGS(path);
514     struct CmMutableBlob fileNames = { 0, NULL };
515     struct CmMutableBlob pathBlob = { strlen(path) + 1, (uint8_t *)path }; /* include '\0' at end. */
516 
517     int32_t ret = CertManagerGetFilenames(&fileNames, path);
518     if (ret != CM_SUCCESS) {
519         CM_LOG_E("Failed obtain filenames from path");
520         return ret;
521     }
522 
523     struct CmMutableBlob *fNames = (struct CmMutableBlob *)fileNames.data;
524     for (uint32_t i = 0; i < fileNames.size; i++) {
525         ret = CertManagerFileRemove(path, (char *)fNames[i].data);
526         if (ret != CMR_OK) {
527             CM_LOG_E("User Cert %u remove failed, ret: %d", i, ret);
528             continue;
529         }
530         ret = CmSetStatusEnable(context, &pathBlob, (struct CmBlob *)(&fNames[i]), store);
531         if (ret != CM_SUCCESS) {
532             CM_LOG_E("Update StatusFile %u fail, ret = %d", i, ret);
533             continue;
534         }
535     }
536 
537     FreeFileNames(fNames, fileNames.size);
538     return ret;
539 }
540 
RemoveAllUidDir(const char * path)541 static int32_t RemoveAllUidDir(const char* path)
542 {
543     return CM_ERROR(CmDirRemove(path));
544 }
545 
CmRemoveAllUserCert(const struct CmContext * context,uint32_t store,const struct CmMutableBlob * pathList)546 int32_t CmRemoveAllUserCert(const struct CmContext *context, uint32_t store, const struct CmMutableBlob *pathList)
547 {
548     ASSERT_ARGS(pathList && pathList->data && pathList->size);
549     int32_t ret = CM_SUCCESS;
550     struct CmMutableBlob *path = (struct CmMutableBlob *)pathList->data;
551 
552     for (uint32_t i = 0; i < pathList->size; i++) {
553         ret = RemoveAllUserCert(context, store, (char *)path[i].data);
554         if (ret != CM_SUCCESS) {
555             CM_LOG_E("Failed remove usercert at %u_th dir", i);
556             continue;
557         }
558         ret = RemoveAllUidDir((char *)path[i].data);
559         if (ret != CM_SUCCESS) {
560             CM_LOG_E("Remove UidPath fail, ret = %d", ret);
561             continue;
562         }
563     }
564     return ret;
565 }
566 
567 #ifdef __cplusplus
568 }
569 #endif