1 // Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
2 //
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 #include <stdbool.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <sys/fcntl.h>
21 #include <sys/dirent.h>
22 #include "esp_vfs.h"
23 #include "unity.h"
24 #include "esp_log.h"
25 
26 /* Dummy VFS implementation to check if VFS is called or not with expected path
27  */
28 typedef struct {
29     const char* match_path;
30     bool called;
31 } dummy_vfs_t;
32 
dummy_open(void * ctx,const char * path,int flags,int mode)33 static int dummy_open(void* ctx, const char * path, int flags, int mode)
34 {
35     dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
36     dummy->called = true;
37     if (strcmp(dummy->match_path, path) == 0) {
38         return 1;
39     }
40     errno = ENOENT;
41     return -1;
42 }
43 
dummy_close(void * ctx,int fd)44 static int dummy_close(void* ctx, int fd)
45 {
46     dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
47     dummy->called = true;
48     if (fd == 1) {
49         return 0;
50     }
51     errno = EBADF;
52     return -1;
53 }
54 
dummy_opendir(void * ctx,const char * path)55 static DIR* dummy_opendir(void* ctx, const char* path)
56 {
57     dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
58     dummy->called = true;
59     if (strcmp(dummy->match_path, path) == 0) {
60         DIR* result = calloc(1, sizeof(DIR));
61         TEST_ASSERT_NOT_NULL(result);
62         return result;
63     }
64     errno = ENOENT;
65     return NULL;
66 }
67 
dummy_closedir(void * ctx,DIR * pdir)68 static int dummy_closedir(void* ctx, DIR* pdir)
69 {
70     dummy_vfs_t* dummy = (dummy_vfs_t*) ctx;
71     dummy->called = true;
72     free(pdir);
73     return 0;
74 }
75 
76 /* Initializer for this dummy VFS implementation
77  */
78 
79 #define DUMMY_VFS() { \
80         .flags = ESP_VFS_FLAG_CONTEXT_PTR, \
81         .open_p = dummy_open, \
82         .close_p = dummy_close, \
83         .opendir_p = dummy_opendir, \
84         .closedir_p = dummy_closedir \
85     }
86 
87 /* Helper functions to test VFS behavior
88  */
89 
test_open(dummy_vfs_t * instance,const char * path,bool should_be_called,bool should_be_opened,int line)90 static void test_open(dummy_vfs_t* instance, const char* path,
91         bool should_be_called, bool should_be_opened, int line)
92 {
93     const int flags = O_CREAT | O_TRUNC | O_RDWR;
94     instance->called = false;
95     int fd = esp_vfs_open(__getreent(), path, flags, 0);
96     UNITY_TEST_ASSERT_EQUAL_INT(should_be_called, instance->called, line,
97             "should_be_called check failed");
98     if (should_be_called) {
99         if (should_be_opened) {
100             UNITY_TEST_ASSERT(fd >= 0, line, "should be opened");
101         } else {
102             UNITY_TEST_ASSERT(fd < 0, line, "should not be opened");
103         }
104     }
105     esp_vfs_close(__getreent(), fd);
106 }
107 
test_opendir(dummy_vfs_t * instance,const char * path,bool should_be_called,bool should_be_opened,int line)108 static void test_opendir(dummy_vfs_t* instance, const char* path,
109         bool should_be_called, bool should_be_opened, int line)
110 {
111     instance->called = false;
112     DIR* dir = opendir(path);
113     UNITY_TEST_ASSERT_EQUAL_INT(should_be_called, instance->called, line,
114             "should_be_called check failed");
115     if (should_be_called) {
116         if (should_be_opened) {
117             UNITY_TEST_ASSERT(dir != NULL, line, "should be opened");
118         } else {
119             UNITY_TEST_ASSERT(dir == 0, line, "should not be opened");
120         }
121     }
122     if (dir) {
123         closedir(dir);
124     }
125 }
126 
127 /* Helper macros which forward line number to assertion macros inside test_open
128  * and test_opendir
129  */
130 
131 #define test_opened(instance, path) test_open(instance, path, true, true, __LINE__)
132 #define test_not_opened(instance, path) test_open(instance, path, true, false, __LINE__)
133 #define test_not_called(instance, path) test_open(instance, path, false, false, __LINE__)
134 
135 #define test_dir_opened(instance, path) test_opendir(instance, path, true, true, __LINE__)
136 #define test_dir_not_opened(instance, path) test_opendir(instance, path, true, false, __LINE__)
137 #define test_dir_not_called(instance, path) test_opendir(instance, path, false, false, __LINE__)
138 
139 
140 
141 TEST_CASE("vfs parses paths correctly", "[vfs]")
142 {
143     dummy_vfs_t inst_foo = {
144             .match_path = "",
145             .called = false
146     };
147     esp_vfs_t desc_foo = DUMMY_VFS();
148     TEST_ESP_OK( esp_vfs_register("/foo", &desc_foo, &inst_foo) );
149 
150     dummy_vfs_t inst_foo1 = {
151         .match_path = "",
152         .called = false
153     };
154     esp_vfs_t desc_foo1 = DUMMY_VFS();
155     TEST_ESP_OK( esp_vfs_register("/foo1", &desc_foo1, &inst_foo1) );
156 
157     inst_foo.match_path = "/file";
158     test_opened(&inst_foo, "/foo/file");
159     test_not_opened(&inst_foo, "/foo/file1");
160     test_not_called(&inst_foo, "/foo1/file");
161     test_not_called(&inst_foo, "/foo1");
162     test_not_opened(&inst_foo, "/foo");
163     inst_foo.match_path = "/junk";
164     test_dir_opened(&inst_foo, "/foo/junk");
165     inst_foo.match_path = "/";
166     test_dir_opened(&inst_foo, "/foo/");
167     test_dir_opened(&inst_foo, "/foo");
168     test_dir_not_called(&inst_foo1, "/foo");
169     test_dir_not_opened(&inst_foo, "/foo/1");
170     test_dir_not_called(&inst_foo, "/foo1");
171 
172     inst_foo1.match_path = "/file1";
173     test_not_called(&inst_foo1, "/foo/file1");
174     test_opened(&inst_foo1, "/foo1/file1");
175     test_not_opened(&inst_foo1, "/foo1/file");
176 
177     // Test nested VFS entries
178     dummy_vfs_t inst_foobar = {
179         .match_path = "",
180         .called = false
181     };
182     esp_vfs_t desc_foobar = DUMMY_VFS();
183     TEST_ESP_OK( esp_vfs_register("/foo/bar", &desc_foobar, &inst_foobar) );
184 
185     dummy_vfs_t inst_toplevel = {
186         .match_path = "",
187         .called = false
188     };
189     esp_vfs_t desc_toplevel = DUMMY_VFS();
190     TEST_ESP_OK( esp_vfs_register("", &desc_toplevel, &inst_toplevel) );
191 
192     inst_foo.match_path = "/bar/file";
193     inst_foobar.match_path = "/file";
194     test_not_called(&inst_foo, "/foo/bar/file");
195     test_opened(&inst_foobar, "/foo/bar/file");
196     test_dir_not_called(&inst_foo, "/foo/bar/file");
197     test_dir_opened(&inst_foobar, "/foo/bar/file");
198     inst_toplevel.match_path = "/tmp/foo";
199     test_opened(&inst_toplevel, "/tmp/foo");
200     inst_toplevel.match_path = "foo";
201     test_opened(&inst_toplevel, "foo");
202 
203     TEST_ESP_OK( esp_vfs_unregister("/foo") );
204     TEST_ESP_OK( esp_vfs_unregister("/foo1") );
205     TEST_ESP_OK( esp_vfs_unregister("/foo/bar") );
206     TEST_ESP_OK( esp_vfs_unregister("") );
207 }
208 
209 TEST_CASE("vfs unregisters correct nested mount point", "[vfs]")
210 {
211     dummy_vfs_t inst_foobar = {
212         .match_path = "/file",
213         .called = false
214     };
215     esp_vfs_t desc_foobar = DUMMY_VFS();
216     TEST_ESP_OK( esp_vfs_register("/foo/bar", &desc_foobar, &inst_foobar) );
217 
218     dummy_vfs_t inst_foo = {
219         .match_path = "/bar/file",
220         .called = false
221     };
222     esp_vfs_t desc_foo = DUMMY_VFS();
223     TEST_ESP_OK( esp_vfs_register("/foo", &desc_foo, &inst_foo) );
224 
225     /* basic operation */
226     test_opened(&inst_foobar, "/foo/bar/file");
227     test_not_called(&inst_foo, "/foo/bar/file");
228 
229     /* this should not match anything */
230     TEST_ESP_ERR(ESP_ERR_INVALID_STATE, esp_vfs_unregister("/foo/b"));
231 
232     /* unregister "/foo" and check that we haven't unregistered "/foo/bar" */
233     TEST_ESP_OK( esp_vfs_unregister("/foo") );
234     test_not_called(&inst_foo, "/foo/bar/file");
235     test_opened(&inst_foobar, "/foo/bar/file");
236 
237     /* repeat the above, with the reverse order of registration */
238     TEST_ESP_OK( esp_vfs_unregister("/foo/bar") );
239     TEST_ESP_OK( esp_vfs_register("/foo", &desc_foo, &inst_foo) );
240     TEST_ESP_OK( esp_vfs_register("/foo/bar", &desc_foobar, &inst_foobar) );
241     test_opened(&inst_foobar, "/foo/bar/file");
242     test_not_called(&inst_foo, "/foo/bar/file");
243     TEST_ESP_OK( esp_vfs_unregister("/foo") );
244     test_not_called(&inst_foo, "/foo/bar/file");
245     test_opened(&inst_foobar, "/foo/bar/file");
246     TEST_ESP_OK( esp_vfs_unregister("/foo/bar") );
247 }
248 
249 
test_vfs_register(const char * prefix,bool expect_success,int line)250 void test_vfs_register(const char* prefix, bool expect_success, int line)
251 {
252     dummy_vfs_t inst;
253     esp_vfs_t desc = DUMMY_VFS();
254     esp_err_t err = esp_vfs_register(prefix, &desc, &inst);
255     if (expect_success) {
256         UNITY_TEST_ASSERT_EQUAL_INT(ESP_OK, err, line, "esp_vfs_register should succeed");
257     } else {
258         UNITY_TEST_ASSERT_EQUAL_INT(ESP_ERR_INVALID_ARG,
259                 err, line, "esp_vfs_register should fail");
260     }
261     if (err == ESP_OK) {
262         TEST_ESP_OK( esp_vfs_unregister(prefix) );
263     }
264 }
265 
266 #define test_register_ok(prefix) test_vfs_register(prefix, true, __LINE__)
267 #define test_register_fail(prefix) test_vfs_register(prefix, false, __LINE__)
268 
269 TEST_CASE("vfs checks mount point path", "[vfs]")
270 {
271     test_register_ok("");
272     test_register_fail("/");
273     test_register_fail("a");
274     test_register_fail("aa");
275     test_register_fail("aaa");
276     test_register_ok("/a");
277     test_register_ok("/aa");
278     test_register_ok("/aaa/bbb");
279     test_register_fail("/aaa/");
280     test_register_fail("/aaa/bbb/");
281     test_register_ok("/23456789012345");
282     test_register_fail("/234567890123456");
283 }
284