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 #ifdef HKS_CONFIG_FILE
17 #include HKS_CONFIG_FILE
18 #else
19 #include "hks_config.h"
20 #endif
21
22 #include "hks_file_operator.h"
23
24 #include <dirent.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <sys/stat.h>
28 #include <time.h>
29 #include <unistd.h>
30
31 #include "hks_log.h"
32 #include "hks_mem.h"
33 #include "hks_template.h"
34 #include "securec.h"
35 #define REQUIRED_KEY_NOT_AVAILABLE 126
36
37 #define UNKNOWN_TYPE (-1)
38 #define DE_TYPE 0
39 #define CE_TYPE 1
40 #define ECE_TYPE 2
41
GetFileName(const char * path,const char * fileName,char * fullFileName,uint32_t fullFileNameLen)42 static int32_t GetFileName(const char *path, const char *fileName, char *fullFileName, uint32_t fullFileNameLen)
43 {
44 if (path != NULL) {
45 if (strncpy_s(fullFileName, fullFileNameLen, path, strlen(path)) != EOK) {
46 return HKS_ERROR_INTERNAL_ERROR;
47 }
48
49 if (path[strlen(path) - 1] != '/') {
50 if (strncat_s(fullFileName, fullFileNameLen, "/", strlen("/")) != EOK) {
51 return HKS_ERROR_INTERNAL_ERROR;
52 }
53 }
54
55 if (strncat_s(fullFileName, fullFileNameLen, fileName, strlen(fileName)) != EOK) {
56 return HKS_ERROR_INTERNAL_ERROR;
57 }
58 } else {
59 if (strncpy_s(fullFileName, fullFileNameLen, fileName, strlen(fileName)) != EOK) {
60 return HKS_ERROR_INTERNAL_ERROR;
61 }
62 }
63
64 return HKS_SUCCESS;
65 }
66
GetFullFileName(const char * path,const char * fileName,char ** fullFileName)67 static int32_t GetFullFileName(const char *path, const char *fileName, char **fullFileName)
68 {
69 uint32_t nameLen = HKS_MAX_FILE_NAME_LEN;
70 char *tmpFileName = (char *)HksMalloc(nameLen);
71 HKS_IF_NULL_RETURN(tmpFileName, HKS_ERROR_MALLOC_FAIL)
72 (void)memset_s(tmpFileName, nameLen, 0, nameLen);
73
74 int32_t ret = GetFileName(path, fileName, tmpFileName, nameLen);
75 if (ret != HKS_SUCCESS) {
76 HKS_LOG_E("get full fileName failed");
77 HKS_FREE(tmpFileName);
78 return ret;
79 }
80
81 *fullFileName = tmpFileName;
82 return HKS_SUCCESS;
83 }
84
IsValidPath(const char * path)85 static int32_t IsValidPath(const char *path)
86 {
87 if (path == NULL) {
88 HKS_LOG_E("path is NULL!");
89 return HKS_ERROR_NULL_POINTER;
90 }
91 if (strstr(path, "../") != NULL) {
92 HKS_LOG_E("dangerous filePath, ../ is not allowed to be included");
93 return HKS_ERROR_INVALID_ARGUMENT;
94 }
95
96 return HKS_SUCCESS;
97 }
98
GetFilePathType(const char * fileName)99 static int32_t GetFilePathType(const char *fileName)
100 {
101 if (strstr(fileName, "/data/service/el1") != NULL) {
102 return DE_TYPE;
103 }
104 #ifdef L2_STANDARD
105 if (strstr(fileName, HKS_CE_ROOT_PATH) != NULL) {
106 return CE_TYPE;
107 } else if (strstr(fileName, HKS_ECE_ROOT_PATH) != NULL) {
108 return ECE_TYPE;
109 }
110 #endif
111 return UNKNOWN_TYPE;
112 }
113
IsFileExist(const char * fileName)114 static int32_t IsFileExist(const char *fileName)
115 {
116 if (access(fileName, F_OK) != 0) {
117 if (errno != ENOENT) {
118 HKS_LOG_FILE_OP_ERRNO("Check IsFileExist fail!", GetFilePathType(fileName));
119 }
120 return HKS_ERROR_NOT_EXIST;
121 }
122
123 return HKS_SUCCESS;
124 }
125
FileRead(const char * fileName,uint32_t offset,struct HksBlob * blob,uint32_t * size)126 static int32_t FileRead(const char *fileName, uint32_t offset, struct HksBlob *blob, uint32_t *size)
127 {
128 (void)offset;
129 HKS_IF_NOT_SUCC_RETURN(IsFileExist(fileName), HKS_ERROR_NOT_EXIST)
130
131 if (strstr(fileName, "../") != NULL) {
132 HKS_LOG_E("invalid filePath, ../ is included in file path");
133 return HKS_ERROR_INVALID_ARGUMENT;
134 }
135
136 char filePath[PATH_MAX + 1] = {0};
137 if (realpath(fileName, filePath) == NULL) {
138 HKS_LOG_E("invalid filePath, realpath failed");
139 return HKS_ERROR_INVALID_ARGUMENT;
140 }
141
142 FILE *fp = fopen(filePath, "rb");
143 if (fp == NULL) {
144 HKS_LOG_FILE_OP_ERRNO("open file fail,", GetFilePathType(filePath));
145 if (errno == REQUIRED_KEY_NOT_AVAILABLE) {
146 HKS_LOG_E("Check Permission failed!");
147 return HKS_ERROR_NO_PERMISSION;
148 }
149
150 return HKS_ERROR_OPEN_FILE_FAIL;
151 }
152
153 // Print file time info
154 struct stat fileStat;
155 (void)memset_s(&fileStat, sizeof(fileStat), 0, sizeof(fileStat));
156 if (stat(fileName, &fileStat) != 0) {
157 HKS_LOG_FILE_OP_ERRNO("file stat fail,", GetFilePathType(fileName));
158 if (fclose(fp) < 0) {
159 HKS_LOG_E("failed to close file, errno = 0x%" LOG_PUBLIC "x", errno);
160 return HKS_ERROR_OPEN_FILE_FAIL;
161 }
162 return HKS_ERROR_OPEN_FILE_FAIL;
163 }
164 HKS_LOG_I("File ctime: %" LOG_PUBLIC "s, mtime: %" LOG_PUBLIC "s",
165 ctime(&fileStat.st_ctime), ctime(&fileStat.st_mtime));
166
167 uint32_t len = fread(blob->data, 1, blob->size, fp);
168 if (fclose(fp) < 0) {
169 HKS_LOG_E("failed to close file, errno = 0x%" LOG_PUBLIC "x", errno);
170 return HKS_ERROR_CLOSE_FILE_FAIL;
171 }
172 *size = len;
173 return HKS_SUCCESS;
174 }
175
FileSize(const char * fileName)176 static uint32_t FileSize(const char *fileName)
177 {
178 HKS_IF_NOT_SUCC_RETURN(IsFileExist(fileName), 0)
179
180 struct stat fileStat;
181 (void)memset_s(&fileStat, sizeof(fileStat), 0, sizeof(fileStat));
182 if (stat(fileName, &fileStat) != 0) {
183 HKS_LOG_FILE_OP_ERRNO("file stat fail,", GetFilePathType(fileName));
184 return 0;
185 }
186
187 return fileStat.st_size;
188 }
189
GetRealPath(const char * fileName,char * filePath)190 static int32_t GetRealPath(const char *fileName, char *filePath)
191 {
192 if (memcpy_s(filePath, PATH_MAX, fileName, strlen(fileName)) != EOK) {
193 return HKS_ERROR_INSUFFICIENT_MEMORY;
194 }
195
196 if (strstr(filePath, "../") != NULL) {
197 HKS_LOG_E("invalid filePath!");
198 return HKS_ERROR_INVALID_KEY_FILE;
199 }
200 return HKS_SUCCESS;
201 }
202
FileWrite(const char * fileName,uint32_t offset,const uint8_t * buf,uint32_t len)203 static int32_t FileWrite(const char *fileName, uint32_t offset, const uint8_t *buf, uint32_t len)
204 {
205 (void)offset;
206 char filePath[PATH_MAX + 1] = {0};
207 int32_t ret = GetRealPath(fileName, filePath);
208 HKS_IF_NOT_SUCC_LOGE(ret, "get real path faild")
209
210 (void)realpath(fileName, filePath);
211
212 /* caller function ensures that the folder exists */
213 FILE *fp = fopen(filePath, "wb+");
214 if (fp == NULL) {
215 HKS_LOG_FILE_OP_ERRNO("open file fail,", GetFilePathType(filePath));
216 if (errno == REQUIRED_KEY_NOT_AVAILABLE) {
217 HKS_LOG_E("Check Permission failed!");
218 return HKS_ERROR_NO_PERMISSION;
219 }
220 return HKS_ERROR_OPEN_FILE_FAIL;
221 }
222
223 if (chmod(filePath, S_IRUSR | S_IWUSR) < 0) {
224 HKS_LOG_FILE_OP_ERRNO("chmod file fail,", GetFilePathType(filePath));
225 fclose(fp);
226 return HKS_ERROR_OPEN_FILE_FAIL;
227 }
228
229 uint32_t size = fwrite(buf, 1, len, fp);
230 if (size != len) {
231 HKS_LOG_FILE_OP_ERRNO("write file size fail,", GetFilePathType(filePath));
232 fclose(fp);
233 return HKS_ERROR_WRITE_FILE_FAIL;
234 }
235
236 if (fflush(fp) < 0) {
237 HKS_LOG_FILE_OP_ERRNO("fflush file fail,", GetFilePathType(filePath));
238 fclose(fp);
239 return HKS_ERROR_WRITE_FILE_FAIL;
240 }
241
242 int fd = fileno(fp);
243 if (fd < 0) {
244 HKS_LOG_FILE_OP_ERRNO("fileno fail,", GetFilePathType(filePath));
245 fclose(fp);
246 return HKS_ERROR_WRITE_FILE_FAIL;
247 }
248
249 if (fsync(fd) < 0) {
250 HKS_LOG_FILE_OP_ERRNO("sync file fail,", GetFilePathType(filePath));
251 fclose(fp);
252 return HKS_ERROR_WRITE_FILE_FAIL;
253 }
254
255 if (fclose(fp) < 0) {
256 HKS_LOG_FILE_OP_ERRNO("failed to close file,", GetFilePathType(filePath));
257 return HKS_ERROR_CLOSE_FILE_FAIL;
258 }
259
260 return HKS_SUCCESS;
261 }
262
FileRemove(const char * fileName)263 static int32_t FileRemove(const char *fileName)
264 {
265 int32_t ret = IsFileExist(fileName);
266 HKS_IF_NOT_SUCC_RETURN(ret, HKS_SUCCESS) /* if file not exist, return ok */
267
268 struct stat tmp;
269 if (stat(fileName, &tmp) != 0) {
270 HKS_LOG_FILE_OP_ERRNO("file stat fail,", GetFilePathType(fileName));
271 return HKS_ERROR_INTERNAL_ERROR;
272 }
273
274 if (S_ISDIR(tmp.st_mode)) {
275 HKS_LOG_E("Is Dir!");
276 return HKS_ERROR_INVALID_ARGUMENT;
277 }
278
279 if ((unlink(fileName) != 0) && (errno != ENOENT)) {
280 HKS_LOG_FILE_OP_ERRNO("unlink fail,", GetFilePathType(fileName));
281 return HKS_ERROR_REMOVE_FILE_FAIL;
282 }
283
284 return HKS_SUCCESS;
285 }
286
HksFileRemove(const char * path,const char * fileName)287 int32_t HksFileRemove(const char *path, const char *fileName)
288 {
289 HKS_IF_NULL_RETURN(fileName, HKS_ERROR_INVALID_ARGUMENT)
290
291 char *fullFileName = NULL;
292 int32_t ret = GetFullFileName(path, fileName, &fullFileName);
293 HKS_IF_NOT_SUCC_RETURN(ret, ret)
294 if (IsValidPath(fullFileName) != HKS_SUCCESS) {
295 HKS_FREE(fullFileName);
296 return HKS_ERROR_INVALID_ARGUMENT;
297 }
298
299 ret = FileRemove(fullFileName);
300 HKS_FREE(fullFileName);
301 return ret;
302 }
303
HksIsFileExist(const char * path,const char * fileName)304 int32_t HksIsFileExist(const char *path, const char *fileName)
305 {
306 HKS_IF_NULL_RETURN(fileName, HKS_ERROR_NULL_POINTER)
307
308 char *fullFileName = NULL;
309 int32_t ret = GetFullFileName(path, fileName, &fullFileName);
310 HKS_IF_NOT_SUCC_RETURN(ret, ret)
311 if (IsValidPath(fullFileName) != HKS_SUCCESS) {
312 HKS_FREE(fullFileName);
313 return HKS_ERROR_INVALID_ARGUMENT;
314 }
315
316 ret = IsFileExist(fullFileName);
317 HKS_FREE(fullFileName);
318 return ret;
319 }
320
HksIsDirExist(const char * path)321 int32_t HksIsDirExist(const char *path)
322 {
323 HKS_IF_NULL_RETURN(path, HKS_ERROR_NULL_POINTER)
324 if (IsValidPath(path) != HKS_SUCCESS) {
325 return HKS_ERROR_INVALID_ARGUMENT;
326 }
327 return IsFileExist(path);
328 }
329
HksMakeDir(const char * path)330 int32_t HksMakeDir(const char *path)
331 {
332 if (IsValidPath(path) != HKS_SUCCESS) {
333 return HKS_ERROR_INVALID_ARGUMENT;
334 }
335 int result = mkdir(path, S_IRWXU);
336 if (result == 0) {
337 return HKS_SUCCESS;
338 } else {
339 switch (errno) {
340 case EEXIST:
341 return HKS_ERROR_ALREADY_EXISTS;
342 default:
343 HKS_LOG_FILE_OP_ERRNO("mkdir fail,", GetFilePathType(path));
344 return HKS_ERROR_MAKE_DIR_FAIL;
345 }
346 }
347 }
348
HksOpenDir(const char * path)349 void *HksOpenDir(const char *path)
350 {
351 if (IsValidPath(path) != HKS_SUCCESS) {
352 return NULL;
353 }
354 return (void *)opendir(path);
355 }
356
HksCloseDir(void * dirp)357 int32_t HksCloseDir(void *dirp)
358 {
359 return closedir((DIR *)dirp);
360 }
361
HksGetDirFile(void * dirp,struct HksFileDirentInfo * direntInfo)362 int32_t HksGetDirFile(void *dirp, struct HksFileDirentInfo *direntInfo)
363 {
364 DIR *dir = (DIR *)dirp;
365 struct dirent *dire = readdir(dir);
366
367 while (dire != NULL) {
368 if (dire->d_type != DT_REG) { /* only care about files. */
369 dire = readdir(dir);
370 continue;
371 }
372
373 uint32_t len = strlen(dire->d_name);
374 if (memcpy_s(direntInfo->fileName, sizeof(direntInfo->fileName) - 1, dire->d_name, len) != EOK) {
375 return HKS_ERROR_INSUFFICIENT_MEMORY;
376 }
377 direntInfo->fileName[len] = '\0';
378 return HKS_SUCCESS;
379 }
380
381 return HKS_ERROR_NOT_EXIST;
382 }
383
HksRemoveDir(const char * dirPath)384 int32_t HksRemoveDir(const char *dirPath)
385 {
386 if (IsValidPath(dirPath) != HKS_SUCCESS) {
387 return HKS_ERROR_INVALID_ARGUMENT;
388 }
389 struct stat fileStat;
390 int32_t ret = stat(dirPath, &fileStat);
391 if (ret != 0) {
392 HKS_LOG_FILE_OP_ERRNO("file stat failed", GetFilePathType(dirPath));
393 return HKS_FAILURE;
394 }
395
396 if (!S_ISDIR(fileStat.st_mode)) {
397 HKS_LOG_E("path is not dir");
398 return HKS_FAILURE;
399 }
400
401 DIR *dir = opendir(dirPath);
402 HKS_IF_NULL_LOGE_RETURN(dir, HKS_ERROR_OPEN_FILE_FAIL, "open dir failed")
403
404 struct dirent *dire = readdir(dir);
405 while (dire != NULL) {
406 if (dire->d_type == DT_REG) { /* only care about files. */
407 ret = HksFileRemove(dirPath, dire->d_name);
408 HKS_IF_NOT_SUCC_LOGE(ret, "remove file failed when remove dir files, ret = %" LOG_PUBLIC "d.", ret)
409 }
410 dire = readdir(dir);
411 }
412
413 closedir(dir);
414 return HKS_SUCCESS;
415 }
416
HksDeletDirPartTwo(const char * path)417 static int32_t HksDeletDirPartTwo(const char *path)
418 {
419 int32_t ret;
420 char deletePath[HKS_MAX_FILE_NAME_LEN] = {0};
421 DIR *dir = opendir(path);
422 HKS_IF_NULL_LOGE_RETURN(dir, HKS_ERROR_OPEN_FILE_FAIL, "open dir failed")
423 struct dirent *dire = readdir(dir);
424 while (dire != NULL) {
425 if (strncpy_s(deletePath, sizeof(deletePath), path, strlen(path)) != EOK) {
426 closedir(dir);
427 return HKS_ERROR_INTERNAL_ERROR;
428 }
429
430 if (deletePath[strlen(deletePath) - 1] != '/') {
431 if (strncat_s(deletePath, sizeof(deletePath), "/", strlen("/")) != EOK) {
432 closedir(dir);
433 return HKS_ERROR_INTERNAL_ERROR;
434 }
435 }
436
437 if (strncat_s(deletePath, sizeof(deletePath), dire->d_name, strlen(dire->d_name)) != EOK) {
438 closedir(dir);
439 return HKS_ERROR_INTERNAL_ERROR;
440 }
441
442 if ((strcmp("..", dire->d_name) != 0) && (strcmp(".", dire->d_name) != 0)) {
443 (void)remove(deletePath);
444 }
445 dire = readdir(dir);
446 }
447 closedir(dir);
448 ret = remove(path);
449 return ret;
450 }
451
HksDeletDirPartOne(const char * path)452 static int32_t HksDeletDirPartOne(const char *path)
453 {
454 int32_t ret;
455 char deletePath[HKS_MAX_FILE_NAME_LEN] = {0};
456 DIR *dir = opendir(path);
457 HKS_IF_NULL_LOGE_RETURN(dir, HKS_ERROR_OPEN_FILE_FAIL, "open dir failed")
458 struct dirent *dire = readdir(dir);
459 while (dire != NULL) {
460 if (strncpy_s(deletePath, sizeof(deletePath), path, strlen(path)) != EOK) {
461 closedir(dir);
462 return HKS_ERROR_INTERNAL_ERROR;
463 }
464
465 if (deletePath[strlen(deletePath) - 1] != '/') {
466 if (strncat_s(deletePath, sizeof(deletePath), "/", strlen("/")) != EOK) {
467 closedir(dir);
468 return HKS_ERROR_INTERNAL_ERROR;
469 }
470 }
471
472 if (strncat_s(deletePath, sizeof(deletePath), dire->d_name, strlen(dire->d_name)) != EOK) {
473 closedir(dir);
474 return HKS_ERROR_INTERNAL_ERROR;
475 }
476
477 if (dire->d_type == DT_DIR && (strcmp("..", dire->d_name) != 0) && (strcmp(".", dire->d_name) != 0)) {
478 HksDeletDirPartTwo(deletePath);
479 } else if (dire->d_type != DT_DIR) {
480 (void)remove(deletePath);
481 }
482 dire = readdir(dir);
483 }
484 closedir(dir);
485 ret = remove(path);
486 return ret;
487 }
488
HksDeleteDir(const char * path)489 int32_t HksDeleteDir(const char *path)
490 {
491 if (IsValidPath(path) != HKS_SUCCESS) {
492 return HKS_ERROR_INVALID_ARGUMENT;
493 }
494 int32_t ret;
495 char deletePath[HKS_MAX_FILE_NAME_LEN] = { 0 };
496
497 DIR *dir = opendir(path);
498 if (dir == NULL) {
499 HKS_LOG_FILE_OP_ERRNO("open dir failed", GetFilePathType(path));
500 return HKS_ERROR_OPEN_FILE_FAIL;
501 }
502 struct dirent *dire = readdir(dir);
503 while (dire != NULL) {
504 if (strncpy_s(deletePath, sizeof(deletePath), path, strlen(path)) != EOK) {
505 closedir(dir);
506 return HKS_ERROR_INTERNAL_ERROR;
507 }
508
509 if (deletePath[strlen(deletePath) - 1] != '/') {
510 if (strncat_s(deletePath, sizeof(deletePath), "/", strlen("/")) != EOK) {
511 closedir(dir);
512 return HKS_ERROR_INTERNAL_ERROR;
513 }
514 }
515
516 if (strncat_s(deletePath, sizeof(deletePath), dire->d_name, strlen(dire->d_name)) != EOK) {
517 closedir(dir);
518 return HKS_ERROR_INTERNAL_ERROR;
519 }
520
521 if (dire->d_type == DT_DIR && (strcmp("..", dire->d_name) != 0) && (strcmp(".", dire->d_name) != 0)) {
522 HksDeletDirPartOne(deletePath);
523 } else if (dire->d_type != DT_DIR) {
524 (void)remove(deletePath);
525 }
526 dire = readdir(dir);
527 }
528 closedir(dir);
529 ret = remove(path);
530 return ret;
531 }
532
HksFileRead(const char * path,const char * fileName,uint32_t offset,struct HksBlob * blob,uint32_t * size)533 int32_t HksFileRead(const char *path, const char *fileName, uint32_t offset, struct HksBlob *blob, uint32_t *size)
534 {
535 if ((fileName == NULL) || (blob == NULL) || (blob->data == NULL) || (blob->size == 0) || (size == NULL)) {
536 return HKS_ERROR_INVALID_ARGUMENT;
537 }
538
539 char *fullFileName = NULL;
540 int32_t ret = GetFullFileName(path, fileName, &fullFileName);
541 HKS_IF_NOT_SUCC_RETURN(ret, ret)
542 if (IsValidPath(fullFileName) != HKS_SUCCESS) {
543 HKS_FREE(fullFileName);
544 return HKS_ERROR_INVALID_ARGUMENT;
545 }
546
547 ret = FileRead(fullFileName, offset, blob, size);
548 HKS_FREE(fullFileName);
549 return ret;
550 }
551
HksFileWrite(const char * path,const char * fileName,uint32_t offset,const uint8_t * buf,uint32_t len)552 int32_t HksFileWrite(const char *path, const char *fileName, uint32_t offset, const uint8_t *buf, uint32_t len)
553 {
554 if ((fileName == NULL) || (buf == NULL) || (len == 0)) {
555 return HKS_ERROR_INVALID_ARGUMENT;
556 }
557
558 char *fullFileName = NULL;
559 int32_t ret = GetFullFileName(path, fileName, &fullFileName);
560 HKS_IF_NOT_SUCC_RETURN(ret, ret)
561 if (IsValidPath(fullFileName) != HKS_SUCCESS) {
562 HKS_FREE(fullFileName);
563 return HKS_ERROR_INVALID_ARGUMENT;
564 }
565
566 ret = FileWrite(fullFileName, offset, buf, len);
567 HKS_FREE(fullFileName);
568 return ret;
569 }
570
HksFileSize(const char * path,const char * fileName)571 uint32_t HksFileSize(const char *path, const char *fileName)
572 {
573 HKS_IF_NULL_RETURN(fileName, 0)
574
575 char *fullFileName = NULL;
576 int32_t ret = GetFullFileName(path, fileName, &fullFileName);
577 HKS_IF_NOT_SUCC_RETURN(ret, 0)
578 if (IsValidPath(fullFileName) != HKS_SUCCESS) {
579 HKS_FREE(fullFileName);
580 return 0;
581 }
582
583 uint32_t size = FileSize(fullFileName);
584 HKS_FREE(fullFileName);
585 return size;
586 }
587
HksGetFileName(const char * path,const char * fileName,char * fullFileName,uint32_t fullFileNameLen)588 int32_t HksGetFileName(const char *path, const char *fileName, char *fullFileName, uint32_t fullFileNameLen)
589 {
590 return GetFileName(path, fileName, fullFileName, fullFileNameLen);
591 }