plptools
Loading...
Searching...
No Matches
fuse.c
Go to the documentation of this file.
1/*
2 plpfuse: Expose EPOC's file system via FUSE
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
4 Copyright (C) 2007-2024 Reuben Thomas <rrt@sc3d.org>
5
6 This program can be distributed under the terms of the GNU GPL.
7 See the file COPYING.
8*/
9
10#include "config.h"
11
12#include <stdarg.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <unistd.h>
17#include <fcntl.h>
18#include <errno.h>
19#include <sys/time.h>
20#include <syslog.h>
21#ifdef HAVE_ATTR_XATTR_H
22#include <attr/xattr.h>
23#elif defined(HAVE_SYS_XATTR_H)
24#include <sys/xattr.h>
25#endif
26
27#ifndef ENOATTR
28#define ENOATTR ENODATA
29#endif
30#ifdef HAVE_ATTR_ATTRIBUTES_H
31#include <attr/attributes.h>
32#endif
33
34#include "plpfuse.h"
35#include "rfsv_api.h"
36
37/* Name of our extended attribute */
38#define XATTR_NAME "user.epoc"
39
40/* Maximum length of a generated psion xattr string */
41#define XATTR_MAXLEN 3
42
43#ifndef ENOMEDIUM
44#define ENOMEDIUM ENODEV
45#endif
46
48
49void
50debuglog(const char *fmt, ...)
51{
52 va_list ap;
53 char *buf;
54
55 if (!debug)
56 return;
57 va_start(ap, fmt);
58 if (vasprintf(&buf, fmt, ap) == -1)
59 syslog(LOG_DEBUG, "vasprintf error in debuglog");
60 else {
61 syslog(LOG_DEBUG, "%s", buf);
62 free(buf);
63 }
64 va_end(ap);
65}
66
67static void
68xattr2pattr(long *psisattr, long *psidattr, const char *oxattr, const char *nxattr)
69{
70 if ((strchr(oxattr, 'a') == NULL) != (strchr(nxattr, 'a') == NULL)) { /* a = archive */
71 if (strchr(nxattr, 'a'))
72 *psisattr |= PSI_A_ARCHIVE;
73 else
74 *psidattr |= PSI_A_ARCHIVE;
75 }
76 if ((strchr(oxattr, 'h') == NULL) != (strchr(nxattr, 'h') == NULL)) { /* h = hidden */
77 if (strchr(nxattr, 'h'))
78 *psisattr |= PSI_A_HIDDEN;
79 else
80 *psidattr |= PSI_A_HIDDEN;
81 }
82 if ((strchr(oxattr, 's') == NULL) != (strchr(nxattr, 's') == NULL)) { /* s = system */
83 if (strchr(nxattr, 's'))
84 *psisattr |= PSI_A_SYSTEM;
85 else
86 *psidattr |= PSI_A_SYSTEM;
87 }
88}
89
90static void
91attr2pattr(long oattr, long nattr, char *oxattr, char *nxattr, long *psisattr, long *psidattr)
92{
93 *psisattr = *psidattr = 0;
94 if ((oattr & 0400) != (nattr & 0400)) {
95 if (nattr & 0400) /* readable */
96 *psisattr |= PSI_A_READ;
97 else
98 *psidattr |= PSI_A_READ;
99 }
100 if ((oattr & 0200) != (nattr & 0200)) {
101 if (nattr & 0200) /* Not writable -> readonly */
102 *psidattr |= PSI_A_RDONLY;
103 else
104 *psisattr |= PSI_A_RDONLY;
105 }
106 xattr2pattr(psisattr, psidattr, oxattr, nxattr);
107}
108
109static void
110pattr2xattr(long psiattr, char *xattr)
111{
112 *xattr = '\0';
113
114 if (psiattr & PSI_A_HIDDEN)
115 strcat(xattr, "h");
116 if (psiattr & PSI_A_SYSTEM)
117 strcat(xattr, "s");
118 if (psiattr & PSI_A_ARCHIVE)
119 strcat(xattr, "a");
120}
121
122static void
123pattr2attr(long psiattr, long size, long ftime, struct stat *st, char *xattr)
124{
125 struct fuse_context *ct = fuse_get_context();
126
127 memset(st, 0, sizeof(*st));
128 st->st_uid = ct->uid;
129 st->st_gid = ct->gid;
130
131 if (psiattr & PSI_A_DIR) {
132 st->st_mode = 0700 | S_IFDIR;
133 st->st_blocks = 1;
134 st->st_size = BLOCKSIZE;
135 st->st_nlink = 2; /* Call getlinks for more accurate count */
136 } else {
137 st->st_blocks = (size + BLOCKSIZE - 1) / BLOCKSIZE;
138 st->st_size = size;
139 st->st_nlink = 1;
140 st->st_mode = S_IFREG;
141
142 if (psiattr & PSI_A_READ)
143 st->st_mode |= 0400; /* File readable */
144 if (!(psiattr & PSI_A_RDONLY))
145 st->st_mode |= 0200; /* File writeable */
146 }
147 st->st_mtime = st->st_ctime = st->st_atime = ftime;
148 pattr2xattr(psiattr, xattr);
149}
150
152
153static int
155{
156 device *dp, *np;
157 int link_count = 2; /* set the root link count */
158
159 for (dp = devices; dp; dp = np) {
160 np = dp->next;
161 free(dp->name);
162 free(dp);
163 }
164 devices = NULL;
165 if (rfsv_drivelist(&link_count, &devices))
166 return 1;
167 return 0;
168}
169
170static char *
171dirname(const char *dir)
172{
173 static char *namebuf = NULL;
174 if (namebuf)
175 free(namebuf);
176 if (asprintf(&namebuf, "%s\\", dir) == -1)
177 return NULL;
178 return namebuf;
179}
180
181static const char *
182filname(const char *dir)
183{
184 char *p;
185 if ((p = (char *) rindex(dir, '\\')))
186 return p + 1;
187 else
188 return dir;
189}
190
191static int
192dircount(const char *path, long *count)
193{
194 dentry *e = NULL;
195 long ret = 0;
196
197 *count = 0;
198 debuglog("dircount: %s", path);
199 debuglog("RFSV dir %s", path);
200 if ((ret = rfsv_dir(dirname(path), &e)) != 0)
201 return ret;
202 while (e) {
203 struct stat st;
204 dentry *o = e;
205 char xattr[XATTR_MAXLEN + 1];
206 pattr2attr(e->attr, e->size, e->time, &st, xattr);
207 free(e->name);
208 e = e->next;
209 free(o);
210 if (st.st_nlink > 1)
211 (*count)++;
212 }
213
214 debuglog("count %d", *count);
215 return ret;
216}
217
218static int getlinks(const char *path, struct stat *st)
219{
220 long dcount;
221 int ret = dircount(path, &dcount);
222 if (ret == 0)
223 st->st_nlink = dcount + 2;
224 return ret;
225}
226
227static int plp_getattr(const char *path, struct stat *st)
228{
229 char xattr[XATTR_MAXLEN + 1];
230 int ret = 0;
231
232 debuglog("plp_getattr `%s'", ++path);
233
234 if (strcmp(path, "") == 0) {
235 pattr2attr(PSI_A_DIR, 0, 0, st, xattr);
236 if (!query_devices()) {
237 device *dp;
238
239 for (dp = devices; dp; dp = dp->next)
240 st->st_nlink++;
241 debuglog("root has %d links", st->st_nlink);
242 } else
243 return rfsv_isalive() ? -ENOENT : -ENOMEDIUM;
244 } else {
245 long pattr, psize, ptime;
246
247 if (strlen(path) == 2 && path[1] == ':') {
248 debuglog("getattr: device");
249 if (!query_devices()) {
250 device *dp;
251
252 for (dp = devices; dp; dp = dp->next) {
253 debuglog("cmp '%c', '%c'", dp->letter, path[0]);
254 if (dp->letter == path[0])
255 break;
256 }
257 debuglog("device: %s", dp ? "exists" : "does not exist");
258 pattr2attr(PSI_A_DIR, 0, 0, st, xattr);
259 return getlinks(path, st);
260 } else
261 return rfsv_isalive() ? -ENOENT : -ENOMEDIUM;
262 }
263
264 debuglog("getattr: fileordir");
265 if ((ret = rfsv_getattr(path, &pattr, &psize, &ptime)) == 0) {
266 pattr2attr(pattr, psize, ptime, st, xattr);
267 debuglog(" attrs Psion: %x %d %d, UNIX modes: %o, xattrs: %s", pattr, psize, ptime, st->st_mode, xattr);
268 if (st->st_nlink > 1)
269 ret = getlinks(path, st);
270 }
271 }
272
273 debuglog("getattr: returned %d", ret);
274 return ret;
275}
276
277static int plp_access(const char *path, int mask)
278{
279 (void)mask;
280 debuglog("plp_access `%s'", ++path);
281 return 0;
282}
283
284static int plp_readlink(const char *path, char *buf, size_t size)
285{
286 (void)buf;
287 (void)size;
288 debuglog("plp_readlink `%s'", ++path);
289 return -EINVAL;
290}
291
292
293static int plp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
294 off_t offset, struct fuse_file_info *fi)
295{
296 device *dp;
297 dentry *e = NULL;
298 char xattr[XATTR_MAXLEN + 1];
299
300 debuglog("plp_readdir `%s'", ++path);
301
302 (void)offset;
303 (void)fi;
304
305 if (strcmp(path, "") == 0) {
306 debuglog("readdir root");
307 if (query_devices() == 0) {
308 for (dp = devices; dp; dp = dp->next) {
309 struct stat st;
310 unsigned char name[3];
311
312 name[0] = dp->letter;
313 name[1] = ':';
314 name[2] = '\0';
315 pattr2attr(dp->attrib, 1, 0, &st, xattr);
316 if (filler(buf, (char *)name, &st, 0))
317 break;
318 }
319 }
320 } else {
321 int ret;
322 debuglog("RFSV dir `%s'", dirname(path));
323 if ((ret = rfsv_dir(dirname(path), &e)) != 0)
324 return ret;
325
326 debuglog("scanning contents");
327 while (e) {
328 dentry *o;
329 struct stat st;
330 const char *name = filname(e->name);
331
332 pattr2attr(e->attr, e->size, e->time, &st, xattr);
333 debuglog(" %s %o %d %d", name, st.st_mode, st.st_size, st.st_mtime);
334 if (filler(buf, name, &st, 0))
335 break;
336 free(e->name);
337 o = e;
338 e = e->next;
339 free(o);
340 }
341 }
342
343 debuglog("readdir OK");
344 return 0;
345}
346
347static int plp_mknod(const char *path, mode_t mode, dev_t dev)
348{
349 int ret = -EINVAL;
350
351 debuglog("plp_mknod `%s' %o", ++path, mode);
352
353 if (S_ISREG(mode) && dev == 0) {
354 uint32_t phandle;
355 if ((ret = rfsv_fcreate(0x200, path, &phandle)) == 0)
356 rfsv_fclose(phandle);
357 }
358
359 return ret;
360}
361
362static int plp_mkdir(const char *path, mode_t mode)
363{
364 debuglog("plp_mkdir `%s' %o", ++path, mode);
365 return rfsv_mkdir(path);
366}
367
368static int plp_unlink(const char *path)
369{
370 debuglog("plp_unlink `%s'", ++path);
371 return rfsv_remove(path);
372}
373
374static int plp_rmdir(const char *path)
375{
376 debuglog("plp_rmdir `%s'", ++path);
377 return rfsv_rmdir(path);
378}
379
380static int plp_symlink(const char *from, const char *to)
381{
382 debuglog("plp_symlink `%s' -> `'%s'", ++from, ++to);
383 return -EPERM;
384}
385
386static int plp_rename(const char *from, const char *to)
387{
388 debuglog("plp_rename `%s' -> `%s'", ++from, ++to);
389 rfsv_remove(to);
390 return rfsv_rename(from, to);
391}
392
393static int plp_link(const char *from, const char *to)
394{
395 debuglog("plp_link `%s' -> `%s'", ++from, ++to);
396 return -EPERM;
397}
398
399static int plp_chmod(const char *path, mode_t mode)
400{
401 int ret;
402 long psisattr, psidattr, pattr, psize, ptime;
403 struct stat st;
404 char xattr[XATTR_MAXLEN + 1];
405
406 debuglog("plp_chmod `%s'", ++path);
407
408 if ((ret = rfsv_getattr(path, &pattr, &psize, &ptime)) == 0) {
409 pattr2attr(pattr, psize, ptime, &st, xattr);
410 attr2pattr(st.st_mode, mode, "", "", &psisattr, &psidattr);
411 debuglog(" UNIX old, new: %o, %o; Psion set, clear: %x, %x", st.st_mode, mode, psisattr, psidattr);
412 if ((ret = rfsv_setattr(path, psisattr, psidattr)) == 0)
413 debuglog("chmod succeeded");
414 }
415
416 return ret;
417}
418
419static int plp_getxattr(const char *path, const char *name, char *value, size_t size
420#ifdef __APPLE__
421 , _GL_UNUSED uint32_t position
422#endif
423 )
424{
425 debuglog("plp_getxattr `%s' %s", ++path, name);
426 if (strcmp(name, XATTR_NAME) == 0) {
427 if (size >= XATTR_MAXLEN) {
428 long pattr, psize, ptime;
429 int ret;
430 if ((ret = rfsv_getattr(path, &pattr, &psize, &ptime)) == 0) {
431 pattr2xattr(pattr, value);
432 debuglog("getxattr succeeded: %s", value);
433 return strlen(value);
434 } else
435 return ret;
436 } else {
437 debuglog("only gave %d bytes, need %d", size, XATTR_MAXLEN);
438 return XATTR_MAXLEN;
439 }
440 }
441 return 0;
442}
443
444static int plp_setxattr(const char *path, const char *name, const char *value, size_t size, int flags
445#ifdef __APPLE__
446 , _GL_UNUSED uint32_t position
447#endif
448 )
449{
450 debuglog("plp_setxattr `%s'", ++path);
451 if (strcmp(name, XATTR_NAME) == 0) {
452 int ret;
453 long psisattr, psidattr;
454 char oxattr[XATTR_MAXLEN + 1], nxattr[XATTR_MAXLEN + 1];
455
456#ifdef XATTR_CREATE
457 if (flags & XATTR_CREATE)
458 return -EEXIST;
459#endif
460
461 strncpy(nxattr, value, size < XATTR_MAXLEN ? size : XATTR_MAXLEN);
462 nxattr[XATTR_MAXLEN] = '\0';
463 /* Need to undo earlier increment of path when calling plp_getxattr */
464 plp_getxattr(path - 1, name, oxattr, XATTR_MAXLEN
465#ifdef __APPLE__
466 , 0
467#endif
468 );
469 psisattr = psidattr = 0;
470 xattr2pattr(&psisattr, &psidattr, oxattr, value);
471 debuglog("attrs set %x delete %x; %s, %s", psisattr, psidattr, oxattr, value);
472 if ((ret = rfsv_setattr(path, psisattr, psidattr)) != 0)
473 return ret;
474
475 debuglog("setxattr succeeded");
476 return 0;
477 } else {
478#ifdef XATTR_REPLACE
479 if (flags & XATTR_REPLACE)
480 return -ENOATTR;
481 else
482#endif
483 return -ENOTSUP;
484 }
485}
486
487static int plp_listxattr(const char *path, char *list, size_t size)
488{
489 debuglog("plp_listxattr `%s'", ++path);
490 if (size > sizeof(XATTR_NAME))
491 strcpy(list, XATTR_NAME);
492 return sizeof(XATTR_NAME);
493}
494
495static int plp_removexattr(const char *path, const char *name)
496{
497 debuglog("plp_removexattr `%s'", ++path);
498 (void)name;
499 return -ENOTSUP;
500}
501
502static int plp_chown(const char *path, uid_t uid, gid_t gid)
503{
504 (void)uid;
505 (void)gid;
506 debuglog("plp_chown `%s'", ++path);
507 return -EPERM;
508}
509
510static int plp_truncate(const char *path, off_t size)
511{
512 debuglog("plp_truncate `%s'", ++path);
513 return rfsv_setsize(path, size);
514}
515
516static int plp_utimens(const char *path, const struct timespec ts[2])
517{
518 debuglog("plp_utimens `%s'", ++path);
519 return rfsv_setmtime(path, ts[1].tv_sec);
520}
521
522static int plp_open(const char *path, struct fuse_file_info *fi)
523{
524 debuglog("plp_open `%s'", ++path);
525 (void)fi;
526 return 0;
527}
528
529static int plp_read(const char *path, char *buf, size_t size, off_t offset,
530 struct fuse_file_info *fi)
531{
532 long read;
533
534 (void)fi;
535 debuglog("plp_read `%s' offset %lld size %ld", ++path, offset, size);
536 read = rfsv_read(buf, (long)offset, size, path);
537 debuglog("read returned %ld", read);
538 return read;
539}
540
541static int plp_write(const char *path, const char *buf, size_t size,
542 off_t offset, struct fuse_file_info *fi)
543{
544 long written;
545
546 (void)fi;
547 debuglog("plp_write `%s' offset %lld size %ld", ++path, offset, size);
548 written = rfsv_write(buf, offset, size, path);
549 debuglog("write returned %ld", written);
550 return written;
551}
552
553static int plp_statfs(const char *path, struct statvfs *stbuf)
554{
555 device *dp;
556
557 (void)path;
558 debuglog("plp_statfs");
559
560 stbuf->f_bsize = BLOCKSIZE;
561 stbuf->f_frsize = BLOCKSIZE;
562 if (query_devices() == 0) {
563 for (dp = devices; dp; dp = dp->next) {
564 stbuf->f_blocks += (dp->total + BLOCKSIZE - 1) / BLOCKSIZE;
565 stbuf->f_bfree += (dp->free + BLOCKSIZE - 1) / BLOCKSIZE;
566 }
567 }
568 stbuf->f_bavail = stbuf->f_bfree;
569
570 /* Don't have numbers for these */
571 stbuf->f_files = 0;
572 stbuf->f_ffree = stbuf->f_favail = 0;
573
574 stbuf->f_fsid = FID;
575 stbuf->f_flag = 0; /* don't have mount flags */
576 stbuf->f_namemax = 255; /* KDMaxFileNameLen% */
577
578 return 0;
579}
580
581struct fuse_operations plp_oper = {
582 .getattr = plp_getattr,
583 .access = plp_access,
584 .readlink = plp_readlink,
585 .readdir = plp_readdir,
586 .mknod = plp_mknod,
587 .mkdir = plp_mkdir,
588 .symlink = plp_symlink,
589 .unlink = plp_unlink,
590 .rmdir = plp_rmdir,
591 .rename = plp_rename,
592 .link = plp_link,
593 .chmod = plp_chmod,
594 .setxattr = plp_setxattr,
595 .getxattr = plp_getxattr,
596 .listxattr = plp_listxattr,
597 .removexattr = plp_removexattr,
598 .chown = plp_chown,
599 .truncate = plp_truncate,
600 .utimens = plp_utimens,
601 .open = plp_open,
602 .read = plp_read,
603 .write = plp_write,
604 .statfs = plp_statfs,
605};
static int plp_unlink(const char *path)
Definition: fuse.c:368
static int getlinks(const char *path, struct stat *st)
Definition: fuse.c:218
#define XATTR_MAXLEN
Definition: fuse.c:41
static device * devices
Definition: fuse.c:151
static int plp_link(const char *from, const char *to)
Definition: fuse.c:393
static int plp_utimens(const char *path, const struct timespec ts[2])
Definition: fuse.c:516
void debuglog(const char *fmt,...)
Definition: fuse.c:50
static int plp_removexattr(const char *path, const char *name)
Definition: fuse.c:495
static int plp_mknod(const char *path, mode_t mode, dev_t dev)
Definition: fuse.c:347
static int plp_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
Definition: fuse.c:529
static int plp_open(const char *path, struct fuse_file_info *fi)
Definition: fuse.c:522
#define ENOMEDIUM
Definition: fuse.c:44
static int plp_readlink(const char *path, char *buf, size_t size)
Definition: fuse.c:284
static char * dirname(const char *dir)
Definition: fuse.c:171
#define XATTR_NAME
Definition: fuse.c:38
#define ENOATTR
Definition: fuse.c:28
static int plp_listxattr(const char *path, char *list, size_t size)
Definition: fuse.c:487
static void attr2pattr(long oattr, long nattr, char *oxattr, char *nxattr, long *psisattr, long *psidattr)
Definition: fuse.c:91
static int plp_rename(const char *from, const char *to)
Definition: fuse.c:386
static int plp_mkdir(const char *path, mode_t mode)
Definition: fuse.c:362
static int plp_access(const char *path, int mask)
Definition: fuse.c:277
static int query_devices(void)
Definition: fuse.c:154
static void pattr2attr(long psiattr, long size, long ftime, struct stat *st, char *xattr)
Definition: fuse.c:123
static int plp_rmdir(const char *path)
Definition: fuse.c:374
static void xattr2pattr(long *psisattr, long *psidattr, const char *oxattr, const char *nxattr)
Definition: fuse.c:68
static int plp_setxattr(const char *path, const char *name, const char *value, size_t size, int flags)
Definition: fuse.c:444
static int plp_getattr(const char *path, struct stat *st)
Definition: fuse.c:227
struct fuse_operations plp_oper
Definition: fuse.c:581
static int plp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
Definition: fuse.c:293
static int plp_statfs(const char *path, struct statvfs *stbuf)
Definition: fuse.c:553
static int plp_symlink(const char *from, const char *to)
Definition: fuse.c:380
static int dircount(const char *path, long *count)
Definition: fuse.c:192
static int plp_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
Definition: fuse.c:541
static int plp_chown(const char *path, uid_t uid, gid_t gid)
Definition: fuse.c:502
static int plp_chmod(const char *path, mode_t mode)
Definition: fuse.c:399
int debug
Definition: fuse.c:47
static int plp_truncate(const char *path, off_t size)
Definition: fuse.c:510
static int plp_getxattr(const char *path, const char *name, char *value, size_t size)
Definition: fuse.c:419
static const char * filname(const char *dir)
Definition: fuse.c:182
static void pattr2xattr(long psiattr, char *xattr)
Definition: fuse.c:110
int rfsv_rmdir(const char *name)
Definition: main.cc:180
int rfsv_dir(const char *file, dentry **e)
Definition: main.cc:150
int rfsv_drivelist(int *cnt, device **dlist)
Definition: main.cc:304
int rfsv_mkdir(const char *file)
Definition: main.cc:186
int rfsv_getattr(const char *name, long *attr, long *size, long *time)
Definition: main.cc:285
int rfsv_remove(const char *file)
Definition: main.cc:192
int rfsv_setmtime(const char *name, long time)
Definition: main.cc:259
int rfsv_write(const char *buf, long offset, long len, const char *name)
Definition: main.cc:244
int rfsv_fcreate(long attr, const char *file, uint32_t *handle)
Definition: main.cc:204
int rfsv_setattr(const char *name, long sattr, long dattr)
Definition: main.cc:279
int rfsv_fclose(long handle)
Definition: main.cc:198
int rfsv_rename(const char *oldname, const char *newname)
Definition: main.cc:298
int rfsv_isalive(void)
Definition: main.cc:142
int rfsv_setsize(const char *name, long size)
Definition: main.cc:265
int rfsv_read(char *buf, long offset, long len, const char *name)
Definition: main.cc:229
#define FID
Definition: plpfuse.h:61
#define BLOCKSIZE
Definition: plpfuse.h:60
#define PSI_A_HIDDEN
Definition: rfsv_api.h:50
#define PSI_A_SYSTEM
Definition: rfsv_api.h:51
#define PSI_A_DIR
Definition: rfsv_api.h:52
#define PSI_A_ARCHIVE
Definition: rfsv_api.h:53
#define PSI_A_RDONLY
Definition: rfsv_api.h:49
#define PSI_A_READ
Definition: rfsv_api.h:58
long time
Definition: plpfuse.h:49
long size
Definition: plpfuse.h:51
struct p_dentry * next
Definition: plpfuse.h:53
char * name
Definition: plpfuse.h:48
long attr
Definition: plpfuse.h:50
Description of a Psion-Device.
Definition: plpfuse.h:34
char * name
Definition: plpfuse.h:35
long attrib
Definition: plpfuse.h:37
long total
Definition: plpfuse.h:38
char letter
Definition: plpfuse.h:36
struct p_device * next
Definition: plpfuse.h:40
long free
Definition: plpfuse.h:39