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 
Get(const char * aKey,int & aIterator,char * aValue,int aValueLength)53 otError ConfigFile::Get(const char *aKey, int &aIterator, char *aValue, int aValueLength)
54 {
55     otError  error = OT_ERROR_NONE;
56     char     line[kLineMaxSize + 1];
57     FILE    *fp = nullptr;
58     char    *ret;
59     long int pos;
60 
61     VerifyOrExit((aKey != nullptr) && (aValue != nullptr), error = OT_ERROR_INVALID_ARGS);
62     VerifyOrExit((fp = fopen(mFilePath, "r")) != nullptr, error = OT_ERROR_NOT_FOUND);
63     VerifyOrDie(fseek(fp, aIterator, SEEK_SET) == 0, OT_EXIT_ERROR_ERRNO);
64 
65     while ((ret = fgets(line, sizeof(line), fp)) != nullptr)
66     {
67         char *str;
68         char *key;
69         char *value;
70 
71         // If the string exceeds the `sizeof(line) - 1`, the string will be truncated to `sizeof(line) - 1` bytes string
72         // by the function `fgets()`.
73         if (strlen(line) + 1 == sizeof(line))
74         {
75             // The line is too long.
76             continue;
77         }
78 
79         // Remove comments
80         strtok(line, kCommentDelimiter);
81 
82         if ((str = strstr(line, "=")) == nullptr)
83         {
84             continue;
85         }
86 
87         *str = '\0';
88         key  = line;
89 
90         Strip(key);
91 
92         if (strcmp(aKey, key) == 0)
93         {
94             value = str + 1;
95             Strip(value);
96             aValueLength = OT_MIN(static_cast<int>(strlen(value)), (aValueLength - 1));
97             memcpy(aValue, value, static_cast<size_t>(aValueLength));
98             aValue[aValueLength] = '\0';
99             break;
100         }
101     }
102 
103     VerifyOrExit(ret != nullptr, error = OT_ERROR_NOT_FOUND);
104     VerifyOrDie((pos = ftell(fp)) >= 0, OT_EXIT_ERROR_ERRNO);
105     aIterator = static_cast<int>(pos);
106 
107 exit:
108     if (fp != nullptr)
109     {
110         fclose(fp);
111     }
112 
113     return error;
114 }
115 
Add(const char * aKey,const char * aValue)116 otError ConfigFile::Add(const char *aKey, const char *aValue)
117 {
118     otError     error = OT_ERROR_NONE;
119     FILE       *fp    = nullptr;
120     char       *path  = nullptr;
121     char       *dir;
122     struct stat st;
123 
124     VerifyOrExit((aKey != nullptr) && (aValue != nullptr), error = OT_ERROR_INVALID_ARGS);
125     VerifyOrDie((path = strdup(mFilePath)) != nullptr, OT_EXIT_ERROR_ERRNO);
126     dir = dirname(path);
127 
128     if (stat(dir, &st) == -1)
129     {
130         VerifyOrDie(mkdir(dir, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == 0, OT_EXIT_ERROR_ERRNO);
131     }
132 
133     VerifyOrDie((fp = fopen(mFilePath, "at")) != NULL, OT_EXIT_ERROR_ERRNO);
134     VerifyOrDie(fprintf(fp, "%s=%s\n", aKey, aValue) > 0, OT_EXIT_ERROR_ERRNO);
135 
136 exit:
137     if (fp != nullptr)
138     {
139         fclose(fp);
140     }
141 
142     if (path != nullptr)
143     {
144         free(path);
145     }
146 
147     return error;
148 }
149 
Clear(const char * aKey)150 otError ConfigFile::Clear(const char *aKey)
151 {
152     otError error = OT_ERROR_NONE;
153     char    swapFile[kFileNameMaxSize];
154     char    line[kLineMaxSize];
155     FILE   *fp     = nullptr;
156     FILE   *fpSwap = nullptr;
157 
158     VerifyOrExit(aKey != nullptr, error = OT_ERROR_INVALID_ARGS);
159     VerifyOrDie((fp = fopen(mFilePath, "r")) != NULL, OT_EXIT_ERROR_ERRNO);
160     snprintf(swapFile, sizeof(swapFile), "%s%s", mFilePath, kSwapSuffix);
161     VerifyOrDie((fpSwap = fopen(swapFile, "w+")) != NULL, OT_EXIT_ERROR_ERRNO);
162 
163     while (fgets(line, sizeof(line), fp) != nullptr)
164     {
165         bool  containsKey;
166         char *str1;
167         char *str2;
168 
169         str1 = strstr(line, kCommentDelimiter);
170         str2 = strstr(line, aKey);
171 
172         // If only the comment contains the key string, ignore it.
173         containsKey = (str2 != nullptr) && (str1 == nullptr || str2 < str1);
174 
175         if (!containsKey)
176         {
177             fputs(line, fpSwap);
178         }
179     }
180 
181 exit:
182     if (fp != nullptr)
183     {
184         fclose(fp);
185     }
186 
187     if (fpSwap != nullptr)
188     {
189         fclose(fpSwap);
190     }
191 
192     if (error == OT_ERROR_NONE)
193     {
194         VerifyOrDie(rename(swapFile, mFilePath) == 0, OT_EXIT_ERROR_ERRNO);
195     }
196 
197     return error;
198 }
199 
Strip(char * aString)200 void ConfigFile::Strip(char *aString)
201 {
202     int count = 0;
203 
204     for (int i = 0; aString[i]; i++)
205     {
206         if (aString[i] != ' ' && aString[i] != '\r' && aString[i] != '\n')
207         {
208             aString[count++] = aString[i];
209         }
210     }
211 
212     aString[count] = '\0';
213 }
214 } // namespace Posix
215 } // namespace ot
216