43 #include "event.h" |
43 #include "event.h" |
44 |
44 |
45 #define VFS_MALLOC(pool, size) pool ? pool_malloc(pool, size) : malloc(size) |
45 #define VFS_MALLOC(pool, size) pool ? pool_malloc(pool, size) : malloc(size) |
46 #define VFS_FREE(pool, ptr) pool ? pool_free(pool, ptr) : free(ptr) |
46 #define VFS_FREE(pool, ptr) pool ? pool_free(pool, ptr) : free(ptr) |
47 |
47 |
48 static UcxMap *vfs_map; |
48 static UcxMap *vfs_type_map; |
49 |
49 |
50 static VFS sys_vfs = { |
50 static VFS sys_vfs = { |
51 sys_vfs_open, |
51 sys_vfs_open, |
52 sys_vfs_stat, |
52 sys_vfs_stat, |
53 sys_vfs_fstat, |
53 sys_vfs_fstat, |
54 sys_vfs_opendir, |
54 sys_vfs_opendir, |
|
55 sys_vfs_fdopendir, |
55 sys_vfs_mkdir, |
56 sys_vfs_mkdir, |
56 sys_vfs_unlink, |
57 sys_vfs_unlink, |
57 VFS_CHECKS_ACL |
58 sys_vfs_rmdir, |
|
59 VFS_CHECKS_ACL, |
|
60 NULL |
58 }; |
61 }; |
59 |
62 |
60 static VFS_IO sys_file_io = { |
63 static VFS_IO sys_file_io = { |
61 sys_file_read, |
64 sys_file_read, |
62 sys_file_write, |
65 sys_file_write, |
64 sys_file_pwrite, |
67 sys_file_pwrite, |
65 sys_file_seek, |
68 sys_file_seek, |
66 sys_file_close, |
69 sys_file_close, |
67 //sys_file_aioread, |
70 //sys_file_aioread, |
68 //sys_file_aiowrite, |
71 //sys_file_aiowrite, |
69 NULL, |
72 NULL, // aioread |
70 NULL |
73 NULL, // aiowrite |
|
74 NULL // getetag |
71 }; |
75 }; |
72 |
76 |
73 static VFS_DIRIO sys_dir_io = { |
77 static VFS_DIRIO sys_dir_io = { |
74 sys_dir_read, |
78 sys_dir_read, |
75 sys_dir_close |
79 sys_dir_close |
76 }; |
80 }; |
77 |
81 |
78 int vfs_init() { |
82 int vfs_init(void) { |
79 vfs_map = ucx_map_new(16); |
83 vfs_type_map = ucx_map_new(16); |
80 if(!vfs_map) { |
84 if(!vfs_type_map) { |
81 return -1; |
85 return -1; |
82 } |
86 } |
83 return 0; |
87 return 0; |
84 } |
88 } |
85 |
89 |
86 void vfs_add(char *name, VFS *vfs) { |
90 int vfs_register_type(const char *name, vfs_init_func vfsInit, vfs_create_func vfsCreate) { |
87 WS_ASSERT(name); |
91 WS_ASSERT(name); |
88 |
92 |
89 if(!vfs_map) { |
93 if(!vfs_type_map) { |
90 vfs_init(); |
94 if(vfs_init()) { |
91 } |
95 return 1; |
92 ucx_map_cstr_put(vfs_map, name, vfs); |
96 } |
|
97 } |
|
98 |
|
99 VfsType *vfsType = malloc(sizeof(VfsType)); |
|
100 if(!vfsType) { |
|
101 return 1; |
|
102 } |
|
103 vfsType->init = vfsInit; |
|
104 vfsType->create = vfsCreate; |
|
105 |
|
106 return ucx_map_cstr_put(vfs_type_map, name, vfsType); |
|
107 } |
|
108 |
|
109 VfsType* vfs_get_type(scstr_t vfs_class) { |
|
110 return ucx_map_sstr_get(vfs_type_map, vfs_class); |
|
111 } |
|
112 |
|
113 void* vfs_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, VfsType *vfs_class, WSConfigNode *config, int *error) { |
|
114 *error = 0; |
|
115 if(vfs_class->init) { |
|
116 void *initData = vfs_class->init(cfg, pool, config); |
|
117 if(!initData) { |
|
118 *error = 1; |
|
119 } |
|
120 return initData; |
|
121 } else { |
|
122 return NULL; |
|
123 } |
|
124 } |
|
125 |
|
126 VFS* vfs_create(Session *sn, Request *rq, const char *vfs_class, pblock *pb, void *initData) { |
|
127 VfsType *vfsType = ucx_map_cstr_get(vfs_type_map, vfs_class); |
|
128 if(!vfsType) { |
|
129 log_ereport(LOG_MISCONFIG, "vfs_create: unkown VFS type %s", vfs_class); |
|
130 return NULL; |
|
131 } |
|
132 |
|
133 return vfsType->create(sn, rq, pb, initData); |
93 } |
134 } |
94 |
135 |
95 VFSContext* vfs_request_context(Session *sn, Request *rq) { |
136 VFSContext* vfs_request_context(Session *sn, Request *rq) { |
96 WS_ASSERT(sn); |
137 WS_ASSERT(sn); |
97 WS_ASSERT(rq); |
138 WS_ASSERT(rq); |
98 |
139 |
99 VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext)); |
140 VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext)); |
|
141 if(!ctx) { |
|
142 return NULL; |
|
143 } |
100 ctx->sn = sn; |
144 ctx->sn = sn; |
101 ctx->rq = rq; |
145 ctx->rq = rq; |
102 ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs; |
146 ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs; |
103 ctx->user = acllist_getuser(sn, rq, rq->acllist); |
147 ctx->user = acllist_getuser(sn, rq, rq->acllist); |
104 ctx->acllist = rq->acllist; |
148 ctx->acllist = rq->acllist; |
105 ctx->aclreqaccess = rq->aclreqaccess; |
149 ctx->aclreqaccess = rq->aclreqaccess; |
106 ctx->pool = sn->pool; |
150 ctx->pool = sn->pool; |
107 ctx->vfs_errno = 0; |
151 ctx->vfs_errno = 0; |
|
152 ctx->error_response_set = 0; |
108 return ctx; |
153 return ctx; |
109 } |
154 } |
110 |
155 |
111 SYS_FILE vfs_open(VFSContext *ctx, char *path, int oflags) { |
156 SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags) { |
112 WS_ASSERT(ctx); |
157 WS_ASSERT(ctx); |
113 WS_ASSERT(path); |
158 WS_ASSERT(path); |
114 |
159 |
115 uint32_t access_mask = ctx->aclreqaccess | acl_oflag2mask(oflags); |
160 uint32_t access_mask = ctx->aclreqaccess | acl_oflag2mask(oflags); |
116 |
161 |
123 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
168 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
124 return NULL; |
169 return NULL; |
125 } |
170 } |
126 } |
171 } |
127 SYS_FILE file = ctx->vfs->open(ctx, path, oflags); |
172 SYS_FILE file = ctx->vfs->open(ctx, path, oflags); |
128 ctx->aclreqaccess = m; // restore original access mask |
173 ctx->aclreqaccess = m; // restore original access mask |
|
174 if(!file && ctx) { |
|
175 sys_set_error_status(ctx); |
|
176 } |
129 return file; |
177 return file; |
130 } |
178 } |
131 |
179 |
132 SYS_FILE vfs_openRO(VFSContext *ctx, char *path) { |
180 SYS_FILE vfs_openRO(VFSContext *ctx, const char *path) { |
133 return vfs_open(ctx, path, O_RDONLY); |
181 return vfs_open(ctx, path, O_RDONLY); |
134 } |
182 } |
135 |
183 |
136 SYS_FILE vfs_openWO(VFSContext *ctx, char *path) { |
184 SYS_FILE vfs_openWO(VFSContext *ctx, const char *path) { |
137 return vfs_open(ctx, path, O_WRONLY | O_CREAT); |
185 return vfs_open(ctx, path, O_WRONLY | O_CREAT); |
138 } |
186 } |
139 |
187 |
140 SYS_FILE vfs_openRW(VFSContext *ctx, char *path) { |
188 SYS_FILE vfs_openRW(VFSContext *ctx, const char *path) { |
141 return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT); |
189 return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT); |
142 } |
190 } |
143 |
191 |
144 int vfs_stat(VFSContext *ctx, char *path, struct stat *buf) { |
192 int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { |
145 WS_ASSERT(ctx); |
193 WS_ASSERT(ctx); |
146 WS_ASSERT(path); |
194 WS_ASSERT(path); |
147 |
195 |
148 uint32_t access_mask = ctx->aclreqaccess | ACL_READ_ATTRIBUTES; |
196 uint32_t access_mask = ctx->aclreqaccess | ACL_READ_ATTRIBUTES; |
149 |
197 |
157 return -1; |
205 return -1; |
158 } |
206 } |
159 } |
207 } |
160 int ret = ctx->vfs->stat(ctx, path, buf); |
208 int ret = ctx->vfs->stat(ctx, path, buf); |
161 ctx->aclreqaccess = m; // restore original access mask |
209 ctx->aclreqaccess = m; // restore original access mask |
|
210 if(ret && ctx) { |
|
211 sys_set_error_status(ctx); |
|
212 } |
162 return ret; |
213 return ret; |
163 } |
214 } |
164 |
215 |
165 int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { |
216 int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { |
166 WS_ASSERT(ctx); |
217 WS_ASSERT(ctx); |
167 WS_ASSERT(fd); |
218 WS_ASSERT(fd); |
168 WS_ASSERT(buf); |
219 WS_ASSERT(buf); |
169 |
220 |
170 return ctx->vfs->fstat(ctx, fd, buf); |
221 int ret = ctx->vfs->fstat(ctx, fd, buf); |
|
222 if(ret && ctx) { |
|
223 sys_set_error_status(ctx); |
|
224 } |
|
225 return ret; |
|
226 } |
|
227 |
|
228 const char * vfs_getetag(SYS_FILE fd) { |
|
229 WS_ASSERT(fd); |
|
230 |
|
231 if(fd->io->opt_getetag) { |
|
232 return fd->io->opt_getetag(fd); |
|
233 } |
|
234 return NULL; |
171 } |
235 } |
172 |
236 |
173 void vfs_close(SYS_FILE fd) { |
237 void vfs_close(SYS_FILE fd) { |
174 WS_ASSERT(fd); |
238 WS_ASSERT(fd); |
175 |
239 |
197 return NULL; |
261 return NULL; |
198 } |
262 } |
199 } |
263 } |
200 VFS_DIR dir = ctx->vfs->opendir(ctx, path); |
264 VFS_DIR dir = ctx->vfs->opendir(ctx, path); |
201 ctx->aclreqaccess = m; // restore original access mask |
265 ctx->aclreqaccess = m; // restore original access mask |
|
266 if(!dir && ctx) { |
|
267 sys_set_error_status(ctx); |
|
268 } |
|
269 return dir; |
|
270 } |
|
271 |
|
272 VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { |
|
273 WS_ASSERT(ctx); |
|
274 WS_ASSERT(path); |
|
275 |
|
276 uint32_t access_mask = ctx->aclreqaccess | ACL_LIST; |
|
277 |
|
278 // ctx->aclreqaccess should be the complete access mask |
|
279 uint32_t m = ctx->aclreqaccess; // save original access mask |
|
280 ctx->aclreqaccess = access_mask; // set mask for vfs->open call |
|
281 if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) { |
|
282 // VFS does not evaluates the ACL itself, so we have to do it here |
|
283 SysACL sysacl; |
|
284 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
|
285 return NULL; |
|
286 } |
|
287 } |
|
288 VFS_DIR dir = ctx->vfs->fdopendir(ctx, fd); |
|
289 ctx->aclreqaccess = m; // restore original access mask |
|
290 if(!dir && ctx) { |
|
291 sys_set_error_status(ctx); |
|
292 } |
202 return dir; |
293 return dir; |
203 } |
294 } |
204 |
295 |
205 int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry) { |
296 int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry) { |
206 WS_ASSERT(dir); |
297 WS_ASSERT(dir); |
225 } else { |
316 } else { |
226 free(dir); |
317 free(dir); |
227 } |
318 } |
228 } |
319 } |
229 |
320 |
230 int vfs_mkdir(VFSContext *ctx, char *path) { |
321 int vfs_mkdir(VFSContext *ctx, const char *path) { |
231 WS_ASSERT(ctx); |
322 WS_ASSERT(ctx); |
232 WS_ASSERT(path); |
323 WS_ASSERT(path); |
233 |
324 |
234 return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE); |
325 return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE); |
235 } |
326 } |
236 |
327 |
237 int vfs_unlink(VFSContext *ctx, char *path) { |
328 int vfs_unlink(VFSContext *ctx, const char *path) { |
238 WS_ASSERT(ctx); |
329 WS_ASSERT(ctx); |
239 WS_ASSERT(path); |
330 WS_ASSERT(path); |
240 |
331 |
241 return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE); |
332 return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE); |
242 } |
333 } |
243 |
334 |
|
335 int vfs_rmdir(VFSContext *ctx, const char *path) { |
|
336 WS_ASSERT(ctx); |
|
337 WS_ASSERT(path); |
|
338 |
|
339 return vfs_path_op(ctx, path, ctx->vfs->rmdir, ACL_DELETE); |
|
340 } |
244 |
341 |
245 // private |
342 // private |
246 int vfs_path_op(VFSContext *ctx, char *path, vfs_op_f op, uint32_t access) { |
343 int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access) { |
247 uint32_t access_mask = ctx->aclreqaccess; |
344 uint32_t access_mask = ctx->aclreqaccess; |
248 access_mask |= access; |
345 access_mask |= access; |
249 |
346 |
250 // ctx->aclreqaccess should be the complete access mask |
347 // ctx->aclreqaccess should be the complete access mask |
251 uint32_t m = ctx->aclreqaccess; // save original access mask |
348 uint32_t m = ctx->aclreqaccess; // save original access mask |
257 return -1; |
354 return -1; |
258 } |
355 } |
259 } |
356 } |
260 int ret = op(ctx, path); |
357 int ret = op(ctx, path); |
261 ctx->aclreqaccess = m; // restore original access mask |
358 ctx->aclreqaccess = m; // restore original access mask |
|
359 if(ret && ctx) { |
|
360 sys_set_error_status(ctx); |
|
361 } |
262 return ret; |
362 return ret; |
263 } |
363 } |
264 |
364 |
265 /* system vfs implementation */ |
365 /* system vfs implementation */ |
266 |
366 |
267 SYS_FILE sys_vfs_open(VFSContext *ctx, char *path, int oflags) { |
367 SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags) { |
268 uint32_t access_mask = ctx->aclreqaccess; |
368 uint32_t access_mask = ctx->aclreqaccess; |
269 pool_handle_t *pool = ctx->pool; |
369 pool_handle_t *pool = ctx->pool; |
270 |
370 |
271 // check ACLs |
371 // check ACLs |
272 SysACL sysacl; |
372 SysACL sysacl; |
311 file->fd = fd; |
411 file->fd = fd; |
312 file->io = &sys_file_io; |
412 file->io = &sys_file_io; |
313 return file; |
413 return file; |
314 } |
414 } |
315 |
415 |
316 int sys_vfs_stat(VFSContext *ctx, char *path, struct stat *buf) { |
416 int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { |
317 uint32_t access_mask = ctx->aclreqaccess; |
417 uint32_t access_mask = ctx->aclreqaccess; |
318 |
418 |
319 // check ACLs |
419 // check ACLs |
320 SysACL sysacl; |
420 SysACL sysacl; |
321 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
421 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
420 dir->fd = dir_fd; |
523 dir->fd = dir_fd; |
421 dir->io = &sys_dir_io; |
524 dir->io = &sys_dir_io; |
422 return dir; |
525 return dir; |
423 } |
526 } |
424 |
527 |
425 int sys_vfs_mkdir(VFSContext *ctx, char *path) { |
528 VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { |
|
529 uint32_t access_mask = ctx->aclreqaccess; |
|
530 pool_handle_t *pool = ctx->pool; |
|
531 |
|
532 // check ACLs |
|
533 SysACL sysacl; |
|
534 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
|
535 return NULL; |
|
536 } |
|
537 |
|
538 if(sysacl.acl) { |
|
539 if(!fs_acl_check_fd(&sysacl, ctx->user, fd->fd, access_mask)) { |
|
540 acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user); |
|
541 return NULL; |
|
542 } |
|
543 } |
|
544 |
|
545 // open directory |
|
546 DIR *sys_dir = fdopendir(fd->fd); |
|
547 if(!sys_dir) { |
|
548 if(ctx) { |
|
549 ctx->vfs_errno = errno; |
|
550 sys_set_error_status(ctx); |
|
551 } |
|
552 return NULL; |
|
553 } |
|
554 |
|
555 SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir)); |
|
556 if(!dir_data) { |
|
557 closedir(sys_dir); |
|
558 return NULL; |
|
559 } |
|
560 long maxfilelen = fpathconf(fd->fd, _PC_NAME_MAX); |
|
561 size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1; |
|
562 dir_data->cur = VFS_MALLOC(pool, entry_len); |
|
563 if(!dir_data->cur) { |
|
564 closedir(sys_dir); |
|
565 VFS_FREE(pool, dir_data); |
|
566 return NULL; |
|
567 } |
|
568 dir_data->dir = sys_dir; |
|
569 |
|
570 VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir)); |
|
571 if(!dir) { |
|
572 closedir(sys_dir); |
|
573 VFS_FREE(pool, dir_data->cur); |
|
574 VFS_FREE(pool, dir_data); |
|
575 return NULL; |
|
576 } |
|
577 dir->ctx = ctx; |
|
578 dir->data = dir_data; |
|
579 dir->fd = fd->fd; |
|
580 dir->io = &sys_dir_io; |
|
581 return dir; |
|
582 } |
|
583 |
|
584 int sys_vfs_mkdir(VFSContext *ctx, const char *path) { |
426 return sys_path_op(ctx, path, sys_mkdir); |
585 return sys_path_op(ctx, path, sys_mkdir); |
427 } |
586 } |
428 |
587 |
429 int sys_vfs_unlink(VFSContext *ctx, char *path) { |
588 int sys_vfs_unlink(VFSContext *ctx, const char *path) { |
430 return sys_path_op(ctx, path, sys_unlink); |
589 return sys_path_op(ctx, path, sys_unlink); |
431 } |
590 } |
432 |
591 |
433 |
592 int sys_vfs_rmdir(VFSContext *ctx, const char *path) { |
434 int sys_path_op(VFSContext *ctx, char *path, sys_op_f op) { |
593 return sys_path_op(ctx, path, sys_rmdir); |
|
594 } |
|
595 |
|
596 |
|
597 int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op) { |
435 uint32_t access_mask = ctx->aclreqaccess; |
598 uint32_t access_mask = ctx->aclreqaccess; |
436 |
599 |
437 // check ACLs |
600 // check ACLs |
438 SysACL sysacl; |
601 SysACL sysacl; |
439 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
602 if(sys_acl_check(ctx, access_mask, &sysacl)) { |
482 |
645 |
483 return 0; |
646 return 0; |
484 } |
647 } |
485 |
648 |
486 void sys_set_error_status(VFSContext *ctx) { |
649 void sys_set_error_status(VFSContext *ctx) { |
487 if(ctx->sn && ctx->rq) { |
650 if(ctx->sn && ctx->rq && !ctx->error_response_set) { |
488 int status = util_errno2status(ctx->vfs_errno); |
651 int status = util_errno2status(ctx->vfs_errno); |
489 protocol_status(ctx->sn, ctx->rq, status, NULL); |
652 protocol_status(ctx->sn, ctx->rq, status, NULL); |
|
653 ctx->error_response_set = TRUE; |
490 } |
654 } |
491 } |
655 } |
492 |
656 |
493 ssize_t sys_file_read(SYS_FILE fd, void *buf, size_t nbyte) { |
657 ssize_t sys_file_read(SYS_FILE fd, void *buf, size_t nbyte) { |
494 return read(fd->fd, buf, nbyte); |
658 return read(fd->fd, buf, nbyte); |
537 return sys_dir_read(dir, entry, getstat); |
701 return sys_dir_read(dir, entry, getstat); |
538 } else { |
702 } else { |
539 entry->name = name; |
703 entry->name = name; |
540 if(getstat) { |
704 if(getstat) { |
541 // TODO: check ACLs again for new path |
705 // TODO: check ACLs again for new path |
|
706 entry->stat_errno = 0; |
542 if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) { |
707 if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) { |
543 entry->stat_errno = errno; |
708 entry->stat_errno = errno; |
544 } |
709 } |
545 entry->stat_extra = NULL; |
710 entry->stat_extra = NULL; |
546 } |
711 } |
565 free(dirdata); |
730 free(dirdata); |
566 free(dir); |
731 free(dir); |
567 } |
732 } |
568 } |
733 } |
569 |
734 |
570 int sys_mkdir(VFSContext *ctx, char *path, SysACL *sysacl) { |
735 int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl) { |
571 mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; |
736 mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; |
572 int ret = mkdir(path, mode); |
737 int ret = mkdir(path, mode); |
573 if(ret == 0) { |
738 if(ret == 0) { |
574 if(sysacl->user_uid != -1) { |
739 if(sysacl->user_uid != -1) { |
575 if(chown(path, sysacl->user_uid, sysacl->user_gid)) { |
740 if(chown(path, sysacl->user_uid, sysacl->user_gid)) { |
578 } |
743 } |
579 } |
744 } |
580 return ret; |
745 return ret; |
581 } |
746 } |
582 |
747 |
583 int sys_unlink(VFSContext *ctx, char *path, SysACL *sysacl) { |
748 int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl) { |
584 return unlink(path); |
749 return unlink(path); |
|
750 } |
|
751 |
|
752 int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl) { |
|
753 return rmdir(path); |
585 } |
754 } |
586 |
755 |
587 /* public file api */ |
756 /* public file api */ |
588 |
757 |
589 NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) { |
758 NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) { |