1 /*
2  *  Copyright (c) 2022, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "config_file.hpp"
30 
31 #include <libgen.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 
38 #include "utils.hpp"
39 #include <openthread/logging.h>
40 #include "common/code_utils.hpp"
41 #include "lib/platform/exit_code.h"
42 
43 namespace ot {
44 namespace Posix {
45 
ConfigFile(const char * aFilePath)46 ConfigFile::ConfigFile(const char *aFilePath)
47     : mFilePath(aFilePath)
48 {
49     assert(mFilePath != nullptr);
50     VerifyOrDie(strlen(mFilePath) + strlen(kSwapSuffix) < kFileNameMaxSize, OT_EXIT_FAILURE);
51 }
52 
HasKey(const char * aKey) const53 bool ConfigFile::HasKey(const char *aKey) const
54 {
55     int iterator = 0;
56 
57     return (Get(aKey, iterator, nullptr, 0) == OT_ERROR_NONE);
58 }
59 
DoesExist(void) const60 bool ConfigFile::DoesExist(void) const { return (access(mFilePath, 0) == 0); }
61 
Get(const char * aKey,int & aIterator,char * aValue,int aValueLength) const62 otError ConfigFile::Get(const char *aKey, int &aIterator, char *aValue, int aValueLength) const
63 {
64     otError  error = OT_ERROR_NONE;
65     char     line[kLineMaxSize + 1];
66     FILE    *fp = nullptr;
67     char    *ret;
68     char    *psave;
69     long int pos;
70 
71     VerifyOrExit(aKey != nullptr, error = OT_ERROR_INVALID_ARGS);
72     VerifyOrExit((fp = fopen(mFilePath, "r")) != nullptr, error = OT_ERROR_NOT_FOUND);
73     VerifyOrDie(fseek(fp, aIterator, SEEK_SET) == 0, OT_EXIT_ERROR_ERRNO);
74 
75     while ((ret = fgets(line, sizeof(line), fp)) != nullptr)
76     {
77         char *str;
78         char *key;
79         char *value;
80 
81         // If the string exceeds the `sizeof(line) - 1`, the string will be truncated to `sizeof(line) - 1` bytes string
82         // by the function `fgets()`.
83         if (strlen(line) + 1 == sizeof(line))
84         {
85             // The line is too long.
86             continue;
87         }
88 
89         // Remove comments
90         strtok_r(line, kCommentDelimiter, &psave);
91 
92         if ((str = strstr(line, "=")) == nullptr)
93         {
94             continue;
95         }
96 
97         *str = '\0';
98         key  = line;
99 
100         Strip(key);
101 
102         if (strcmp(aKey, key) == 0)
103         {
104             if (aValue != nullptr)
105             {
106                 value = str + 1;
107                 Strip(value);
108                 aValueLength = OT_MIN(static_cast<int>(strlen(value)), (aValueLength - 1));
109                 memcpy(aValue, value, static_cast<size_t>(aValueLength));
110                 aValue[aValueLength] = '\0';
111             }
112             break;
113         }
114     }
115 
116     VerifyOrExit(ret != nullptr, error = OT_ERROR_NOT_FOUND);
117     VerifyOrDie((pos = ftell(fp)) >= 0, OT_EXIT_ERROR_ERRNO);
118     aIterator = static_cast<int>(pos);
119 
120 exit:
121     if (fp != nullptr)
122     {
123         fclose(fp);
124     }
125 
126     return error;
127 }
128 
Add(const char * aKey,const char * aValue)129 otError ConfigFile::Add(const char *aKey, const char *aValue)
130 {
131     otError     error = OT_ERROR_NONE;
132     FILE       *fp    = nullptr;
133     char       *path  = nullptr;
134     char       *dir;
135     struct stat st;
136 
137     VerifyOrExit((aKey != nullptr) && (aValue != nullptr), error = OT_ERROR_INVALID_ARGS);
138     VerifyOrDie((path = strdup(mFilePath)) != nullptr, OT_EXIT_ERROR_ERRNO);
139     dir = dirname(path);
140 
141     if (stat(dir, &st) == -1)
142     {
143         VerifyOrDie(mkdir(dir, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == 0, OT_EXIT_ERROR_ERRNO);
144     }
145 
146     VerifyOrDie((fp = fopen(mFilePath, "at")) != NULL, OT_EXIT_ERROR_ERRNO);
147     VerifyOrDie(fprintf(fp, "%s=%s\n", aKey, aValue) > 0, OT_EXIT_ERROR_ERRNO);
148 
149 exit:
150     if (fp != nullptr)
151     {
152         fclose(fp);
153     }
154 
155     if (path != nullptr)
156     {
157         free(path);
158     }
159 
160     return error;
161 }
162 
Clear(const char * aKey)163 otError ConfigFile::Clear(const char *aKey)
164 {
165     otError error = OT_ERROR_NONE;
166     char    swapFile[kFileNameMaxSize];
167     char    line[kLineMaxSize];
168     FILE   *fp     = nullptr;
169     FILE   *fpSwap = nullptr;
170 
171     VerifyOrExit(aKey != nullptr, error = OT_ERROR_INVALID_ARGS);
172     VerifyOrDie((fp = fopen(mFilePath, "r")) != NULL, OT_EXIT_ERROR_ERRNO);
173     snprintf(swapFile, sizeof(swapFile), "%s%s", mFilePath, kSwapSuffix);
174     VerifyOrDie((fpSwap = fopen(swapFile, "w+")) != NULL, OT_EXIT_ERROR_ERRNO);
175 
176     while (fgets(line, sizeof(line), fp) != nullptr)
177     {
178         bool  containsKey;
179         char *str1;
180         char *str2;
181 
182         str1 = strstr(line, kCommentDelimiter);
183         str2 = strstr(line, aKey);
184 
185         // If only the comment contains the key string, ignore it.
186         containsKey = (str2 != nullptr) && (str1 == nullptr || str2 < str1);
187 
188         if (!containsKey)
189         {
190             fputs(line, fpSwap);
191         }
192     }
193 
194 exit:
195     if (fp != nullptr)
196     {
197         fclose(fp);
198     }
199 
200     if (fpSwap != nullptr)
201     {
202         fclose(fpSwap);
203     }
204 
205     if (error == OT_ERROR_NONE)
206     {
207         VerifyOrDie(rename(swapFile, mFilePath) == 0, OT_EXIT_ERROR_ERRNO);
208     }
209 
210     return error;
211 }
212 
Strip(char * aString) const213 void ConfigFile::Strip(char *aString) const
214 {
215     int count = 0;
216 
217     for (int i = 0; aString[i]; i++)
218     {
219         if (aString[i] != ' ' && aString[i] != '\r' && aString[i] != '\n')
220         {
221             aString[count++] = aString[i];
222         }
223     }
224 
225     aString[count] = '\0';
226 }
227 } // namespace Posix
228 } // namespace ot
229