1 /*
2 * Copyright (C) 2021 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 "preferences_xml_utils.h"
17
18 #include <sys/stat.h>
19
20 #include <cerrno>
21 #include <cstring>
22
23 #include "libxml/parser.h"
24 #include "log_print.h"
25 #include "preferences_file_lock.h"
26 #include "preferences_file_operation.h"
27 #include "preferences_impl.h"
28 namespace OHOS {
29 namespace NativePreferences {
30 static bool ParseNodeElement(const xmlNode *node, Element &element);
31 static bool ParsePrimitiveNodeElement(const xmlNode *node, Element &element);
32 static bool ParseStringNodeElement(const xmlNode *node, Element &element);
33 static bool ParseArrayNodeElement(const xmlNode *node, Element &element);
34 static xmlNode *CreateElementNode(Element &element);
35 static xmlNode *CreatePrimitiveNode(Element &element);
36 static xmlNode *CreateStringNode(Element &element);
37 static xmlNode *CreateArrayNode(Element &element);
38
IsFileExist(const std::string & inputPath)39 static bool IsFileExist(const std::string &inputPath)
40 {
41 if (inputPath.length() > PATH_MAX) {
42 return false;
43 }
44 struct stat buffer;
45 return (stat(inputPath.c_str(), &buffer) == 0);
46 }
47
RemoveBackupFile(const std::string & fileName)48 static void RemoveBackupFile(const std::string &fileName)
49 {
50 std::string backupFileName = PreferencesImpl::MakeFilePath(fileName, STR_BACKUP);
51 if (IsFileExist(backupFileName) && std::remove(backupFileName.c_str())) {
52 LOG_WARN("failed to delete backup file %{public}d.", errno);
53 }
54 }
55
RenameFromBackupFile(const std::string & fileName)56 static bool RenameFromBackupFile(const std::string &fileName)
57 {
58 std::string backupFileName = PreferencesImpl::MakeFilePath(fileName, STR_BACKUP);
59 if (!IsFileExist(backupFileName)) {
60 LOG_DEBUG("the backup file does not exist.");
61 return false;
62 }
63 if (std::rename(backupFileName.c_str(), fileName.c_str())) {
64 LOG_ERROR("failed to restore backup errno %{public}d.", errno);
65 return false;
66 }
67 return true;
68 }
69
RenameFile(const std::string & fileName,const std::string & fileType)70 static bool RenameFile(const std::string &fileName, const std::string &fileType)
71 {
72 std::string name = PreferencesImpl::MakeFilePath(fileName, fileType);
73 if (std::rename(fileName.c_str(), name.c_str())) {
74 LOG_ERROR("failed to rename file to %{public}s file %{public}d.", fileType.c_str(), errno);
75 return false;
76 }
77 return true;
78 }
79
RenameToBackupFile(const std::string & fileName)80 static bool RenameToBackupFile(const std::string &fileName)
81 {
82 return RenameFile(fileName, STR_BACKUP);
83 }
84
RenameToBrokenFile(const std::string & fileName)85 static bool RenameToBrokenFile(const std::string &fileName)
86 {
87 return RenameFile(fileName, STR_BROKEN);
88 }
89
ReadFile(const std::string & fileName)90 static xmlDoc *ReadFile(const std::string &fileName)
91 {
92 return xmlReadFile(fileName.c_str(), "UTF-8", XML_PARSE_NOBLANKS);
93 }
94
XmlReadFile(const std::string & fileName,const std::string & dataGroupId)95 static xmlDoc *XmlReadFile(const std::string &fileName, const std::string &dataGroupId)
96 {
97 xmlDoc *doc = nullptr;
98 PreferencesFileLock fileLock(PreferencesImpl::MakeFilePath(fileName, STR_LOCK), dataGroupId);
99 if (IsFileExist(fileName)) {
100 doc = ReadFile(fileName);
101 if (doc != nullptr) {
102 return doc;
103 }
104 xmlErrorPtr xmlErr = xmlGetLastError();
105 std::string errMessage = (xmlErr != nullptr) ? xmlErr->message : "null";
106 LOG_ERROR("failed to read XML format file, error is %{public}s.", errMessage.c_str());
107 if (!RenameToBrokenFile(fileName)) {
108 return doc;
109 }
110 }
111 if (RenameFromBackupFile(fileName)) {
112 return ReadFile(fileName);
113 }
114 return nullptr;
115 }
116
117 /* static */
ReadSettingXml(const std::string & fileName,const std::string & dataGroupId,std::vector<Element> & settings)118 bool PreferencesXmlUtils::ReadSettingXml(
119 const std::string &fileName, const std::string &dataGroupId, std::vector<Element> &settings)
120 {
121 LOG_RECORD_FILE_NAME("Read setting xml start.");
122 if (fileName.size() == 0) {
123 LOG_ERROR("The length of the file name is 0.");
124 return false;
125 }
126 auto doc = std::shared_ptr<xmlDoc>(XmlReadFile(fileName, dataGroupId), [](xmlDoc *doc) { xmlFreeDoc(doc); });
127 if (doc == nullptr) {
128 return false;
129 }
130
131 xmlNode *root = xmlDocGetRootElement(doc.get());
132 if (!root || xmlStrcmp(root->name, reinterpret_cast<const xmlChar *>("preferences"))) {
133 LOG_ERROR("Failed to obtain the XML root element.");
134 return false;
135 }
136
137 bool success = true;
138 const xmlNode *cur = nullptr;
139 for (cur = root->children; cur != nullptr; cur = cur->next) {
140 Element element;
141
142 if (ParseNodeElement(cur, element)) {
143 settings.push_back(element);
144 } else {
145 success = false;
146 LOG_ERROR("The error occurred during getting xml child elements.");
147 break;
148 }
149 }
150
151 /* free the document */
152 LOG_RECORD_FILE_NAME("Read setting xml end.");
153 return success;
154 }
155
156 /* static */
ParseNodeElement(const xmlNode * node,Element & element)157 bool ParseNodeElement(const xmlNode *node, Element &element)
158 {
159 if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("int"))
160 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("long"))
161 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("bool"))
162 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("float"))
163 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("double"))) {
164 return ParsePrimitiveNodeElement(node, element);
165 }
166
167 if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("string"))
168 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("uint8Array"))) {
169 return ParseStringNodeElement(node, element);
170 }
171
172 if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("boolArray"))
173 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("stringArray"))
174 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("doubleArray"))
175 || !xmlStrcmp(node->name, reinterpret_cast<const xmlChar *>("set"))) {
176 return ParseArrayNodeElement(node, element);
177 }
178
179 LOG_ERROR("An unsupported element type was encountered in parsing = %{public}s.", node->name);
180 return false;
181 }
182
183 /* static */
ParsePrimitiveNodeElement(const xmlNode * node,Element & element)184 bool ParsePrimitiveNodeElement(const xmlNode *node, Element &element)
185 {
186 xmlChar *key = xmlGetProp(node, reinterpret_cast<const xmlChar *>("key"));
187 xmlChar *value = xmlGetProp(node, reinterpret_cast<const xmlChar *>("value"));
188
189 bool success = false;
190 if (value != nullptr) {
191 element.tag_ = std::string(reinterpret_cast<const char *>(node->name));
192 if (key != nullptr) {
193 element.key_ = std::string(reinterpret_cast<char *>(key));
194 }
195 element.value_ = std::string(reinterpret_cast<char *>(value));
196 success = true;
197 } else {
198 LOG_ERROR("Failed to obtain a valid key or value when parsing %{public}s.", node->name);
199 }
200
201 if (key != nullptr) {
202 xmlFree(key);
203 }
204 if (value != nullptr) {
205 xmlFree(value);
206 }
207 return success;
208 }
209
210 /* static */
ParseStringNodeElement(const xmlNode * node,Element & element)211 bool ParseStringNodeElement(const xmlNode *node, Element &element)
212 {
213 xmlChar *key = xmlGetProp(node, (const xmlChar *)"key");
214 xmlChar *text = xmlNodeGetContent(node);
215
216 bool success = false;
217 if (text != nullptr) {
218 element.tag_ = std::string(reinterpret_cast<const char *>(node->name));
219 if (key != nullptr) {
220 element.key_ = std::string(reinterpret_cast<char *>(key));
221 }
222 element.value_ = std::string(reinterpret_cast<char *>(text));
223 success = true;
224 } else {
225 LOG_ERROR("Failed to obtain a valid key or value when parsing a String element.");
226 }
227
228 if (key != nullptr) {
229 xmlFree(key);
230 }
231 if (text != nullptr) {
232 xmlFree(text);
233 }
234 return success;
235 }
236
237 /* static */
ParseArrayNodeElement(const xmlNode * node,Element & element)238 bool ParseArrayNodeElement(const xmlNode *node, Element &element)
239 {
240 xmlChar *key = xmlGetProp(node, (const xmlChar *)"key");
241 const xmlNode *children = node->children;
242
243 bool success = false;
244 if (key != nullptr) {
245 element.tag_ = std::string(reinterpret_cast<const char *>(node->name));
246 element.key_ = std::string(reinterpret_cast<char *>(key));
247
248 const xmlNode *cur = nullptr;
249 bool finishTravelChild = true;
250 for (cur = children; cur != nullptr; cur = cur->next) {
251 Element child;
252 if (ParseNodeElement(cur, child)) {
253 element.children_.push_back(child);
254 } else {
255 finishTravelChild = false;
256 LOG_ERROR("Failed to parse the Array element and could not be completed successfully.");
257 break;
258 }
259 }
260 success = finishTravelChild;
261 } else {
262 LOG_ERROR("Failed to obtain a valid key or value when parsing a Array element.");
263 }
264
265 if (key != nullptr) {
266 xmlFree(key);
267 }
268 return success;
269 }
270
SaveFormatFileEnc(const std::string & fileName,xmlDoc * doc)271 static bool SaveFormatFileEnc(const std::string &fileName, xmlDoc *doc)
272 {
273 return xmlSaveFormatFileEnc(fileName.c_str(), doc, "UTF-8", 1) > 0;
274 }
275
XmlSaveFormatFileEnc(const std::string & fileName,const std::string & dataGroupId,xmlDoc * doc)276 bool XmlSaveFormatFileEnc(const std::string &fileName, const std::string &dataGroupId, xmlDoc *doc)
277 {
278 PreferencesFileLock fileLock(PreferencesImpl::MakeFilePath(fileName, STR_LOCK), dataGroupId);
279 if (IsFileExist(fileName) && !RenameToBackupFile(fileName)) {
280 return false;
281 }
282
283 if (!SaveFormatFileEnc(fileName, doc)) {
284 xmlErrorPtr xmlErr = xmlGetLastError();
285 std::string errMessage = (xmlErr != nullptr) ? xmlErr->message : "null";
286 LOG_ERROR("failed to save XML format file, error is %{public}s.", errMessage.c_str());
287 if (IsFileExist(fileName)) {
288 RenameToBrokenFile(fileName);
289 }
290 RenameFromBackupFile(fileName);
291 return false;
292 }
293
294 RemoveBackupFile(fileName);
295 PreferencesXmlUtils::LimitXmlPermission(fileName);
296 // make sure the file is written to disk.
297 if (!Fsync(fileName)) {
298 LOG_WARN("failed to write the file to the disk.");
299 }
300 LOG_DEBUG("successfully saved the XML format file");
301 return true;
302 }
303
304 /* static */
WriteSettingXml(const std::string & fileName,const std::string & dataGroupId,const std::vector<Element> & settings)305 bool PreferencesXmlUtils::WriteSettingXml(
306 const std::string &fileName, const std::string &dataGroupId, const std::vector<Element> &settings)
307 {
308 LOG_RECORD_FILE_NAME("Write setting xml start.");
309 if (fileName.size() == 0) {
310 LOG_ERROR("The length of the file name is 0.");
311 return false;
312 }
313
314 // define doc and root Node
315 auto doc = std::shared_ptr<xmlDoc>(xmlNewDoc(BAD_CAST "1.0"), [](xmlDoc *doc) { xmlFreeDoc(doc); });
316 if (doc == nullptr) {
317 LOG_ERROR("Failed to initialize the xmlDoc.");
318 return false;
319 }
320 xmlNode *rootNode = xmlNewNode(NULL, BAD_CAST "preferences");
321 if (rootNode == nullptr) {
322 LOG_ERROR("The xmlDoc failed to initialize the root node.");
323 return false;
324 }
325 xmlNewProp(rootNode, BAD_CAST "version", BAD_CAST "1.0");
326
327 // set root node
328 xmlDocSetRootElement(doc.get(), rootNode);
329
330 // set children node
331 for (Element element : settings) {
332 xmlNode *node = CreateElementNode(element);
333 if (node == nullptr) {
334 LOG_ERROR("The xmlDoc failed to initialize the element node.");
335 return false;
336 }
337 if (xmlAddChild(rootNode, node) == nullptr) {
338 /* free node in case of error */
339 LOG_ERROR("The xmlDoc failed to add the child node.");
340 xmlFreeNode(node);
341 return false;
342 }
343 }
344
345 /* 1: formatting spaces are added. */
346 bool result = XmlSaveFormatFileEnc(fileName, dataGroupId, doc.get());
347 LOG_RECORD_FILE_NAME("Write setting xml end.");
348 return result;
349 }
350
351 /* static */
CreateElementNode(Element & element)352 xmlNode *CreateElementNode(Element &element)
353 {
354 if ((element.tag_.compare("int") == 0) || (element.tag_.compare("long") == 0)
355 || (element.tag_.compare("float") == 0) || (element.tag_.compare("bool") == 0)
356 || (element.tag_.compare("double") == 0)) {
357 return CreatePrimitiveNode(element);
358 }
359
360 if (element.tag_.compare("string") == 0 || element.tag_.compare("uint8Array") == 0) {
361 return CreateStringNode(element);
362 }
363
364 if ((element.tag_.compare("doubleArray") == 0) || (element.tag_.compare("stringArray") == 0)
365 || (element.tag_.compare("boolArray") == 0)) {
366 return CreateArrayNode(element);
367 }
368
369 LOG_ERROR("An unsupported element type was encountered in parsing = %{public}s.", element.tag_.c_str());
370 return nullptr;
371 }
372
373 /* static */
CreatePrimitiveNode(Element & element)374 xmlNode *CreatePrimitiveNode(Element &element)
375 {
376 xmlNode *node = xmlNewNode(NULL, BAD_CAST element.tag_.c_str());
377 if (node == nullptr) {
378 LOG_ERROR("The xmlDoc failed to initialize the primitive element node.");
379 return nullptr;
380 }
381 if (!element.key_.empty()) {
382 const char *key = element.key_.c_str();
383 xmlNewProp(node, BAD_CAST "key", BAD_CAST key);
384 }
385
386 const char *value = element.value_.c_str();
387 xmlNewProp(node, BAD_CAST "value", BAD_CAST value);
388 return node;
389 }
390
CreateStringNode(Element & element)391 xmlNode *CreateStringNode(Element &element)
392 {
393 xmlNode *node = xmlNewNode(NULL, BAD_CAST element.tag_.c_str());
394 if (node == nullptr) {
395 LOG_ERROR("The xmlDoc failed to initialize the string element node.");
396 return nullptr;
397 }
398
399 if (!element.key_.empty()) {
400 const char *key = element.key_.c_str();
401 xmlNewProp(node, BAD_CAST "key", BAD_CAST key);
402 }
403
404 const char *value = element.value_.c_str();
405 xmlNodePtr text = xmlNewText(BAD_CAST value);
406 if (xmlAddChild(node, text) == nullptr) {
407 xmlFreeNode(text);
408 }
409 return node;
410 }
411
CreateArrayNode(Element & element)412 xmlNode *CreateArrayNode(Element &element)
413 {
414 xmlNode *node = xmlNewNode(NULL, BAD_CAST element.tag_.c_str());
415 if (node == nullptr) {
416 LOG_ERROR("The xmlDoc failed to initialize the array element node.");
417 return nullptr;
418 }
419
420 const char *key = element.key_.c_str();
421 xmlNewProp(node, BAD_CAST "key", BAD_CAST key);
422
423 if (element.children_.empty()) {
424 return node;
425 }
426 Element flag = element.children_[0];
427 if ((flag.tag_.compare("bool") == 0) || (flag.tag_.compare("double") == 0)) {
428 for (Element &child : element.children_) {
429 xmlNode *childNode = CreatePrimitiveNode(child);
430 if (childNode == nullptr) {
431 continue;
432 }
433 if (xmlAddChild(node, childNode) == nullptr) {
434 xmlFreeNode(childNode);
435 }
436 }
437 } else if (flag.tag_.compare("string") == 0) {
438 for (Element child : element.children_) {
439 xmlNode *childNode = CreateStringNode(child);
440 if (childNode == nullptr) {
441 continue;
442 }
443 if (xmlAddChild(node, childNode) == nullptr) {
444 xmlFreeNode(childNode);
445 }
446 }
447 }
448 return node;
449 }
450
LimitXmlPermission(const std::string & fileName)451 void PreferencesXmlUtils::LimitXmlPermission(const std::string &fileName)
452 {
453 /* clear execute permission of owner, clear execute permission of group, clear all permission of group. */
454 struct stat fileStat = { 0 };
455 if (stat(fileName.c_str(), &fileStat) != 0) {
456 LOG_ERROR("Failed to obtain stat of file, errno:%{public}d.", errno);
457 return;
458 }
459 if ((fileStat.st_mode & (S_IXUSR | S_IXGRP | S_IRWXO)) != 0) {
460 int result = chmod(fileName.c_str(), fileStat.st_mode & (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
461 if (result != 0) {
462 LOG_ERROR("Failed to chmod file, errno:%{public}d.", errno);
463 }
464 }
465 }
466 } // End of namespace NativePreferences
467 } // End of namespace OHOS