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