1 /*
2   fuse iconv module: file name charset conversion
3   Copyright (C) 2007  Miklos Szeredi <miklos@szeredi.hu>
4 
5   This program can be distributed under the terms of the GNU LGPLv2.
6   See the file COPYING.LIB
7 */
8 
9 #include <config.h>
10 
11 #include <fuse.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stddef.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <iconv.h>
18 #include <pthread.h>
19 #include <locale.h>
20 #include <langinfo.h>
21 
22 struct iconv {
23 	struct fuse_fs *next;
24 	pthread_mutex_t lock;
25 	char *from_code;
26 	char *to_code;
27 	iconv_t tofs;
28 	iconv_t fromfs;
29 };
30 
31 struct iconv_dh {
32 	struct iconv *ic;
33 	void *prev_buf;
34 	fuse_fill_dir_t prev_filler;
35 };
36 
iconv_get(void)37 static struct iconv *iconv_get(void)
38 {
39 	return fuse_get_context()->private_data;
40 }
41 
iconv_convpath(struct iconv * ic,const char * path,char ** newpathp,int fromfs)42 static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
43 			  int fromfs)
44 {
45 	size_t pathlen;
46 	size_t newpathlen;
47 	char *newpath;
48 	size_t plen;
49 	char *p;
50 	size_t res;
51 	int err;
52 
53 	if (path == NULL) {
54 		*newpathp = NULL;
55 		return 0;
56 	}
57 
58 	pathlen = strlen(path);
59 	newpathlen = pathlen * 4;
60 	newpath = malloc(newpathlen + 1);
61 	if (!newpath)
62 		return -ENOMEM;
63 
64 	plen = newpathlen;
65 	p = newpath;
66 	pthread_mutex_lock(&ic->lock);
67 	do {
68 		res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
69 			    &pathlen, &p, &plen);
70 		if (res == (size_t) -1) {
71 			char *tmp;
72 			size_t inc;
73 
74 			err = -EILSEQ;
75 			if (errno != E2BIG)
76 				goto err;
77 
78 			inc = (pathlen + 1) * 4;
79 			newpathlen += inc;
80 			tmp = realloc(newpath, newpathlen + 1);
81 			err = -ENOMEM;
82 			if (!tmp)
83 				goto err;
84 
85 			p = tmp + (p - newpath);
86 			plen += inc;
87 			newpath = tmp;
88 		}
89 	} while (res == (size_t) -1);
90 	pthread_mutex_unlock(&ic->lock);
91 	*p = '\0';
92 	*newpathp = newpath;
93 	return 0;
94 
95 err:
96 	iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
97 	pthread_mutex_unlock(&ic->lock);
98 	free(newpath);
99 	return err;
100 }
101 
iconv_getattr(const char * path,struct stat * stbuf,struct fuse_file_info * fi)102 static int iconv_getattr(const char *path, struct stat *stbuf,
103 			 struct fuse_file_info *fi)
104 {
105 	struct iconv *ic = iconv_get();
106 	char *newpath;
107 	int err = iconv_convpath(ic, path, &newpath, 0);
108 	if (!err) {
109 		err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
110 		free(newpath);
111 	}
112 	return err;
113 }
114 
iconv_access(const char * path,int mask)115 static int iconv_access(const char *path, int mask)
116 {
117 	struct iconv *ic = iconv_get();
118 	char *newpath;
119 	int err = iconv_convpath(ic, path, &newpath, 0);
120 	if (!err) {
121 		err = fuse_fs_access(ic->next, newpath, mask);
122 		free(newpath);
123 	}
124 	return err;
125 }
126 
iconv_readlink(const char * path,char * buf,size_t size)127 static int iconv_readlink(const char *path, char *buf, size_t size)
128 {
129 	struct iconv *ic = iconv_get();
130 	char *newpath;
131 	int err = iconv_convpath(ic, path, &newpath, 0);
132 	if (!err) {
133 		err = fuse_fs_readlink(ic->next, newpath, buf, size);
134 		if (!err) {
135 			char *newlink;
136 			err = iconv_convpath(ic, buf, &newlink, 1);
137 			if (!err) {
138 				strncpy(buf, newlink, size - 1);
139 				buf[size - 1] = '\0';
140 				free(newlink);
141 			}
142 		}
143 		free(newpath);
144 	}
145 	return err;
146 }
147 
iconv_opendir(const char * path,struct fuse_file_info * fi)148 static int iconv_opendir(const char *path, struct fuse_file_info *fi)
149 {
150 	struct iconv *ic = iconv_get();
151 	char *newpath;
152 	int err = iconv_convpath(ic, path, &newpath, 0);
153 	if (!err) {
154 		err = fuse_fs_opendir(ic->next, newpath, fi);
155 		free(newpath);
156 	}
157 	return err;
158 }
159 
iconv_dir_fill(void * buf,const char * name,const struct stat * stbuf,off_t off,enum fuse_fill_dir_flags flags)160 static int iconv_dir_fill(void *buf, const char *name,
161 			  const struct stat *stbuf, off_t off,
162 			  enum fuse_fill_dir_flags flags)
163 {
164 	struct iconv_dh *dh = buf;
165 	char *newname;
166 	int res = 0;
167 	if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
168 		res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
169 		free(newname);
170 	}
171 	return res;
172 }
173 
iconv_readdir(const char * path,void * buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info * fi,enum fuse_readdir_flags flags)174 static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
175 			 off_t offset, struct fuse_file_info *fi,
176 			 enum fuse_readdir_flags flags)
177 {
178 	struct iconv *ic = iconv_get();
179 	char *newpath;
180 	int err = iconv_convpath(ic, path, &newpath, 0);
181 	if (!err) {
182 		struct iconv_dh dh;
183 		dh.ic = ic;
184 		dh.prev_buf = buf;
185 		dh.prev_filler = filler;
186 		err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
187 				      offset, fi, flags);
188 		free(newpath);
189 	}
190 	return err;
191 }
192 
iconv_releasedir(const char * path,struct fuse_file_info * fi)193 static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
194 {
195 	struct iconv *ic = iconv_get();
196 	char *newpath;
197 	int err = iconv_convpath(ic, path, &newpath, 0);
198 	if (!err) {
199 		err = fuse_fs_releasedir(ic->next, newpath, fi);
200 		free(newpath);
201 	}
202 	return err;
203 }
204 
iconv_mknod(const char * path,mode_t mode,dev_t rdev)205 static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
206 {
207 	struct iconv *ic = iconv_get();
208 	char *newpath;
209 	int err = iconv_convpath(ic, path, &newpath, 0);
210 	if (!err) {
211 		err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
212 		free(newpath);
213 	}
214 	return err;
215 }
216 
iconv_mkdir(const char * path,mode_t mode)217 static int iconv_mkdir(const char *path, mode_t mode)
218 {
219 	struct iconv *ic = iconv_get();
220 	char *newpath;
221 	int err = iconv_convpath(ic, path, &newpath, 0);
222 	if (!err) {
223 		err = fuse_fs_mkdir(ic->next, newpath, mode);
224 		free(newpath);
225 	}
226 	return err;
227 }
228 
iconv_unlink(const char * path)229 static int iconv_unlink(const char *path)
230 {
231 	struct iconv *ic = iconv_get();
232 	char *newpath;
233 	int err = iconv_convpath(ic, path, &newpath, 0);
234 	if (!err) {
235 		err = fuse_fs_unlink(ic->next, newpath);
236 		free(newpath);
237 	}
238 	return err;
239 }
240 
iconv_rmdir(const char * path)241 static int iconv_rmdir(const char *path)
242 {
243 	struct iconv *ic = iconv_get();
244 	char *newpath;
245 	int err = iconv_convpath(ic, path, &newpath, 0);
246 	if (!err) {
247 		err = fuse_fs_rmdir(ic->next, newpath);
248 		free(newpath);
249 	}
250 	return err;
251 }
252 
iconv_symlink(const char * from,const char * to)253 static int iconv_symlink(const char *from, const char *to)
254 {
255 	struct iconv *ic = iconv_get();
256 	char *newfrom;
257 	char *newto;
258 	int err = iconv_convpath(ic, from, &newfrom, 0);
259 	if (!err) {
260 		err = iconv_convpath(ic, to, &newto, 0);
261 		if (!err) {
262 			err = fuse_fs_symlink(ic->next, newfrom, newto);
263 			free(newto);
264 		}
265 		free(newfrom);
266 	}
267 	return err;
268 }
269 
iconv_rename(const char * from,const char * to,unsigned int flags)270 static int iconv_rename(const char *from, const char *to, unsigned int flags)
271 {
272 	struct iconv *ic = iconv_get();
273 	char *newfrom;
274 	char *newto;
275 	int err = iconv_convpath(ic, from, &newfrom, 0);
276 	if (!err) {
277 		err = iconv_convpath(ic, to, &newto, 0);
278 		if (!err) {
279 			err = fuse_fs_rename(ic->next, newfrom, newto, flags);
280 			free(newto);
281 		}
282 		free(newfrom);
283 	}
284 	return err;
285 }
286 
iconv_link(const char * from,const char * to)287 static int iconv_link(const char *from, const char *to)
288 {
289 	struct iconv *ic = iconv_get();
290 	char *newfrom;
291 	char *newto;
292 	int err = iconv_convpath(ic, from, &newfrom, 0);
293 	if (!err) {
294 		err = iconv_convpath(ic, to, &newto, 0);
295 		if (!err) {
296 			err = fuse_fs_link(ic->next, newfrom, newto);
297 			free(newto);
298 		}
299 		free(newfrom);
300 	}
301 	return err;
302 }
303 
iconv_chmod(const char * path,mode_t mode,struct fuse_file_info * fi)304 static int iconv_chmod(const char *path, mode_t mode,
305 		       struct fuse_file_info *fi)
306 {
307 	struct iconv *ic = iconv_get();
308 	char *newpath;
309 	int err = iconv_convpath(ic, path, &newpath, 0);
310 	if (!err) {
311 		err = fuse_fs_chmod(ic->next, newpath, mode, fi);
312 		free(newpath);
313 	}
314 	return err;
315 }
316 
iconv_chown(const char * path,uid_t uid,gid_t gid,struct fuse_file_info * fi)317 static int iconv_chown(const char *path, uid_t uid, gid_t gid,
318 		       struct fuse_file_info *fi)
319 {
320 	struct iconv *ic = iconv_get();
321 	char *newpath;
322 	int err = iconv_convpath(ic, path, &newpath, 0);
323 	if (!err) {
324 		err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
325 		free(newpath);
326 	}
327 	return err;
328 }
329 
iconv_truncate(const char * path,off_t size,struct fuse_file_info * fi)330 static int iconv_truncate(const char *path, off_t size,
331 			   struct fuse_file_info *fi)
332 {
333 	struct iconv *ic = iconv_get();
334 	char *newpath;
335 	int err = iconv_convpath(ic, path, &newpath, 0);
336 	if (!err) {
337 		err = fuse_fs_truncate(ic->next, newpath, size, fi);
338 		free(newpath);
339 	}
340 	return err;
341 }
342 
iconv_utimens(const char * path,const struct timespec ts[2],struct fuse_file_info * fi)343 static int iconv_utimens(const char *path, const struct timespec ts[2],
344 			 struct fuse_file_info *fi)
345 {
346 	struct iconv *ic = iconv_get();
347 	char *newpath;
348 	int err = iconv_convpath(ic, path, &newpath, 0);
349 	if (!err) {
350 		err = fuse_fs_utimens(ic->next, newpath, ts, fi);
351 		free(newpath);
352 	}
353 	return err;
354 }
355 
iconv_create(const char * path,mode_t mode,struct fuse_file_info * fi)356 static int iconv_create(const char *path, mode_t mode,
357 			struct fuse_file_info *fi)
358 {
359 	struct iconv *ic = iconv_get();
360 	char *newpath;
361 	int err = iconv_convpath(ic, path, &newpath, 0);
362 	if (!err) {
363 		err = fuse_fs_create(ic->next, newpath, mode, fi);
364 		free(newpath);
365 	}
366 	return err;
367 }
368 
iconv_open_file(const char * path,struct fuse_file_info * fi)369 static int iconv_open_file(const char *path, struct fuse_file_info *fi)
370 {
371 	struct iconv *ic = iconv_get();
372 	char *newpath;
373 	int err = iconv_convpath(ic, path, &newpath, 0);
374 	if (!err) {
375 		err = fuse_fs_open(ic->next, newpath, fi);
376 		free(newpath);
377 	}
378 	return err;
379 }
380 
iconv_read_buf(const char * path,struct fuse_bufvec ** bufp,size_t size,off_t offset,struct fuse_file_info * fi)381 static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
382 			  size_t size, off_t offset, struct fuse_file_info *fi)
383 {
384 	struct iconv *ic = iconv_get();
385 	char *newpath;
386 	int err = iconv_convpath(ic, path, &newpath, 0);
387 	if (!err) {
388 		err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
389 		free(newpath);
390 	}
391 	return err;
392 }
393 
iconv_write_buf(const char * path,struct fuse_bufvec * buf,off_t offset,struct fuse_file_info * fi)394 static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
395 			   off_t offset, struct fuse_file_info *fi)
396 {
397 	struct iconv *ic = iconv_get();
398 	char *newpath;
399 	int err = iconv_convpath(ic, path, &newpath, 0);
400 	if (!err) {
401 		err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
402 		free(newpath);
403 	}
404 	return err;
405 }
406 
iconv_statfs(const char * path,struct statvfs * stbuf)407 static int iconv_statfs(const char *path, struct statvfs *stbuf)
408 {
409 	struct iconv *ic = iconv_get();
410 	char *newpath;
411 	int err = iconv_convpath(ic, path, &newpath, 0);
412 	if (!err) {
413 		err = fuse_fs_statfs(ic->next, newpath, stbuf);
414 		free(newpath);
415 	}
416 	return err;
417 }
418 
iconv_flush(const char * path,struct fuse_file_info * fi)419 static int iconv_flush(const char *path, struct fuse_file_info *fi)
420 {
421 	struct iconv *ic = iconv_get();
422 	char *newpath;
423 	int err = iconv_convpath(ic, path, &newpath, 0);
424 	if (!err) {
425 		err = fuse_fs_flush(ic->next, newpath, fi);
426 		free(newpath);
427 	}
428 	return err;
429 }
430 
iconv_release(const char * path,struct fuse_file_info * fi)431 static int iconv_release(const char *path, struct fuse_file_info *fi)
432 {
433 	struct iconv *ic = iconv_get();
434 	char *newpath;
435 	int err = iconv_convpath(ic, path, &newpath, 0);
436 	if (!err) {
437 		err = fuse_fs_release(ic->next, newpath, fi);
438 		free(newpath);
439 	}
440 	return err;
441 }
442 
iconv_fsync(const char * path,int isdatasync,struct fuse_file_info * fi)443 static int iconv_fsync(const char *path, int isdatasync,
444 		       struct fuse_file_info *fi)
445 {
446 	struct iconv *ic = iconv_get();
447 	char *newpath;
448 	int err = iconv_convpath(ic, path, &newpath, 0);
449 	if (!err) {
450 		err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
451 		free(newpath);
452 	}
453 	return err;
454 }
455 
iconv_fsyncdir(const char * path,int isdatasync,struct fuse_file_info * fi)456 static int iconv_fsyncdir(const char *path, int isdatasync,
457 			  struct fuse_file_info *fi)
458 {
459 	struct iconv *ic = iconv_get();
460 	char *newpath;
461 	int err = iconv_convpath(ic, path, &newpath, 0);
462 	if (!err) {
463 		err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
464 		free(newpath);
465 	}
466 	return err;
467 }
468 
iconv_setxattr(const char * path,const char * name,const char * value,size_t size,int flags)469 static int iconv_setxattr(const char *path, const char *name,
470 			  const char *value, size_t size, int flags)
471 {
472 	struct iconv *ic = iconv_get();
473 	char *newpath;
474 	int err = iconv_convpath(ic, path, &newpath, 0);
475 	if (!err) {
476 		err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
477 				       flags);
478 		free(newpath);
479 	}
480 	return err;
481 }
482 
iconv_getxattr(const char * path,const char * name,char * value,size_t size)483 static int iconv_getxattr(const char *path, const char *name, char *value,
484 			  size_t size)
485 {
486 	struct iconv *ic = iconv_get();
487 	char *newpath;
488 	int err = iconv_convpath(ic, path, &newpath, 0);
489 	if (!err) {
490 		err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
491 		free(newpath);
492 	}
493 	return err;
494 }
495 
iconv_listxattr(const char * path,char * list,size_t size)496 static int iconv_listxattr(const char *path, char *list, size_t size)
497 {
498 	struct iconv *ic = iconv_get();
499 	char *newpath;
500 	int err = iconv_convpath(ic, path, &newpath, 0);
501 	if (!err) {
502 		err = fuse_fs_listxattr(ic->next, newpath, list, size);
503 		free(newpath);
504 	}
505 	return err;
506 }
507 
iconv_removexattr(const char * path,const char * name)508 static int iconv_removexattr(const char *path, const char *name)
509 {
510 	struct iconv *ic = iconv_get();
511 	char *newpath;
512 	int err = iconv_convpath(ic, path, &newpath, 0);
513 	if (!err) {
514 		err = fuse_fs_removexattr(ic->next, newpath, name);
515 		free(newpath);
516 	}
517 	return err;
518 }
519 
iconv_lock(const char * path,struct fuse_file_info * fi,int cmd,struct flock * lock)520 static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
521 		      struct flock *lock)
522 {
523 	struct iconv *ic = iconv_get();
524 	char *newpath;
525 	int err = iconv_convpath(ic, path, &newpath, 0);
526 	if (!err) {
527 		err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
528 		free(newpath);
529 	}
530 	return err;
531 }
532 
iconv_flock(const char * path,struct fuse_file_info * fi,int op)533 static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
534 {
535 	struct iconv *ic = iconv_get();
536 	char *newpath;
537 	int err = iconv_convpath(ic, path, &newpath, 0);
538 	if (!err) {
539 		err = fuse_fs_flock(ic->next, newpath, fi, op);
540 		free(newpath);
541 	}
542 	return err;
543 }
544 
iconv_bmap(const char * path,size_t blocksize,uint64_t * idx)545 static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
546 {
547 	struct iconv *ic = iconv_get();
548 	char *newpath;
549 	int err = iconv_convpath(ic, path, &newpath, 0);
550 	if (!err) {
551 		err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
552 		free(newpath);
553 	}
554 	return err;
555 }
556 
iconv_lseek(const char * path,off_t off,int whence,struct fuse_file_info * fi)557 static off_t iconv_lseek(const char *path, off_t off, int whence,
558 			 struct fuse_file_info *fi)
559 {
560 	struct iconv *ic = iconv_get();
561 	char *newpath;
562 	int res = iconv_convpath(ic, path, &newpath, 0);
563 	if (!res) {
564 		res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
565 		free(newpath);
566 	}
567 	return res;
568 }
569 
iconv_init(struct fuse_conn_info * conn,struct fuse_config * cfg)570 static void *iconv_init(struct fuse_conn_info *conn,
571 			struct fuse_config *cfg)
572 {
573 	struct iconv *ic = iconv_get();
574 	fuse_fs_init(ic->next, conn, cfg);
575 	/* Don't touch cfg->nullpath_ok, we can work with
576 	   either */
577 	return ic;
578 }
579 
iconv_destroy(void * data)580 static void iconv_destroy(void *data)
581 {
582 	struct iconv *ic = data;
583 	fuse_fs_destroy(ic->next);
584 	iconv_close(ic->tofs);
585 	iconv_close(ic->fromfs);
586 	pthread_mutex_destroy(&ic->lock);
587 	free(ic->from_code);
588 	free(ic->to_code);
589 	free(ic);
590 }
591 
592 static const struct fuse_operations iconv_oper = {
593 	.destroy	= iconv_destroy,
594 	.init		= iconv_init,
595 	.getattr	= iconv_getattr,
596 	.access		= iconv_access,
597 	.readlink	= iconv_readlink,
598 	.opendir	= iconv_opendir,
599 	.readdir	= iconv_readdir,
600 	.releasedir	= iconv_releasedir,
601 	.mknod		= iconv_mknod,
602 	.mkdir		= iconv_mkdir,
603 	.symlink	= iconv_symlink,
604 	.unlink		= iconv_unlink,
605 	.rmdir		= iconv_rmdir,
606 	.rename		= iconv_rename,
607 	.link		= iconv_link,
608 	.chmod		= iconv_chmod,
609 	.chown		= iconv_chown,
610 	.truncate	= iconv_truncate,
611 	.utimens	= iconv_utimens,
612 	.create		= iconv_create,
613 	.open		= iconv_open_file,
614 	.read_buf	= iconv_read_buf,
615 	.write_buf	= iconv_write_buf,
616 	.statfs		= iconv_statfs,
617 	.flush		= iconv_flush,
618 	.release	= iconv_release,
619 	.fsync		= iconv_fsync,
620 	.fsyncdir	= iconv_fsyncdir,
621 	.setxattr	= iconv_setxattr,
622 	.getxattr	= iconv_getxattr,
623 	.listxattr	= iconv_listxattr,
624 	.removexattr	= iconv_removexattr,
625 	.lock		= iconv_lock,
626 	.flock		= iconv_flock,
627 	.bmap		= iconv_bmap,
628 	.lseek		= iconv_lseek,
629 };
630 
631 static const struct fuse_opt iconv_opts[] = {
632 	FUSE_OPT_KEY("-h", 0),
633 	FUSE_OPT_KEY("--help", 0),
634 	{ "from_code=%s", offsetof(struct iconv, from_code), 0 },
635 	{ "to_code=%s", offsetof(struct iconv, to_code), 1 },
636 	FUSE_OPT_END
637 };
638 
iconv_help(void)639 static void iconv_help(void)
640 {
641 	char *old = strdup(setlocale(LC_CTYPE, ""));
642 	char *charmap = strdup(nl_langinfo(CODESET));
643 	setlocale(LC_CTYPE, old);
644 	free(old);
645 	printf(
646 "    -o from_code=CHARSET   original encoding of file names (default: UTF-8)\n"
647 "    -o to_code=CHARSET	    new encoding of the file names (default: %s)\n",
648 		charmap);
649 	free(charmap);
650 }
651 
iconv_opt_proc(void * data,const char * arg,int key,struct fuse_args * outargs)652 static int iconv_opt_proc(void *data, const char *arg, int key,
653 			  struct fuse_args *outargs)
654 {
655 	(void) data; (void) arg; (void) outargs;
656 
657 	if (!key) {
658 		iconv_help();
659 		return -1;
660 	}
661 
662 	return 1;
663 }
664 
iconv_new(struct fuse_args * args,struct fuse_fs * next[])665 static struct fuse_fs *iconv_new(struct fuse_args *args,
666 				 struct fuse_fs *next[])
667 {
668 	struct fuse_fs *fs;
669 	struct iconv *ic;
670 	char *old = NULL;
671 	const char *from;
672 	const char *to;
673 
674 	ic = calloc(1, sizeof(struct iconv));
675 	if (ic == NULL) {
676 		fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
677 		return NULL;
678 	}
679 
680 	if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
681 		goto out_free;
682 
683 	if (!next[0] || next[1]) {
684 		fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
685 		goto out_free;
686 	}
687 
688 	from = ic->from_code ? ic->from_code : "UTF-8";
689 	to = ic->to_code ? ic->to_code : "";
690 	/* FIXME: detect charset equivalence? */
691 	if (!to[0])
692 		old = strdup(setlocale(LC_CTYPE, ""));
693 	ic->tofs = iconv_open(from, to);
694 	if (ic->tofs == (iconv_t) -1) {
695 		fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
696 			to, from);
697 		goto out_free;
698 	}
699 	ic->fromfs = iconv_open(to, from);
700 	if (ic->tofs == (iconv_t) -1) {
701 		fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
702 			from, to);
703 		goto out_iconv_close_to;
704 	}
705 	if (old) {
706 		setlocale(LC_CTYPE, old);
707 		free(old);
708 	}
709 
710 	ic->next = next[0];
711 	fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
712 	if (!fs)
713 		goto out_iconv_close_from;
714 
715 	return fs;
716 
717 out_iconv_close_from:
718 	iconv_close(ic->fromfs);
719 out_iconv_close_to:
720 	iconv_close(ic->tofs);
721 out_free:
722 	free(ic->from_code);
723 	free(ic->to_code);
724 	free(ic);
725 	if (old) {
726 		setlocale(LC_CTYPE, old);
727 		free(old);
728 	}
729 	return NULL;
730 }
731 
732 FUSE_REGISTER_MODULE(iconv, iconv_new);
733