1 /******************************* -*- C++ -*- *******************************
2  *                                                                         *
3  *   file            : fusepod.cpp                                         *
4  *   date started    : 13 Feb 2006                                         *
5  *   author          : Keegan Carruthers-Smith                             *
6  *   email           : keegan.csmith@gmail.com                             *
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  ***************************************************************************/
14 
15 extern "C" {
16 #ifdef linux
17 /* For pread()/pwrite() */
18 #define _XOPEN_SOURCE 500
19 #endif
20 
21 #include <fuse.h>
22 #include <dirent.h>
23 #include <errno.h>
24 #include <unistd.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <time.h>
29 }
30 
31 #include <iostream>
32 #include <fstream>
33 #include <sstream>
34 #include <vector>
35 #include <cstdlib>
36 #include <cassert>
37 #include <cstdio>
38 #include <cstdlib>
39 #include <algorithm>
40 
41 #include "fusepod_ipod.h"
42 #include "fusepod_constants.h"
43 #include "fusepod_util.h"
44 
45 using namespace std;
46 
47 /* I know globals are regarded as bad, but I'm lazy */
48 static FUSEPod * fusepod;
49 static string fuse_mount_point;
50 static string ipod_mount_point;
51 static string sync_script;
52 static string add_files_script;
53 static char * add_songs;
54 static bool syncing;
55 static string syncing_file;
56 static string currently_syncing;
57 
58 
59 /** Returns true if the transfer directory is a prefix of path */
60 inline static bool transfer_in_dir (const char * path) {
61     return fusepod_starts_with (&(path[1]), dir_transfer.c_str ());
62 }
63 
64 static void transfer_remove (const char * path) {
65     Node * node = fusepod->get_node (path);
66 
67     if (!node)
68         return;
69 
70     assert (S_ISREG (node->value.mode));
71 
72     node->remove_from_parent ();
73 
74     string realpath = fusepod->get_transfer_path (path);
75 
76     free ((void*)node->value.text);
77     delete node;
78 
79     unlink (realpath.c_str());
80 }
81 
82 static void transfer_add_songs (Node * node, const string & path) {
83     cout <<"Adding files in directory" << path << endl;
84     for (Node::iterator it = node->begin(); it != node->end(); ++it) {
85         Node * n = *it;
86         string new_path = path + "/" + n->value.text;
87 
88         if (S_ISDIR(n->value.mode)) {
89             transfer_add_songs (n, new_path);
90         } else if (!(n->value.mode & S_IWUSR)) {
91             //Files that have finished copying are marked not writable
92             currently_syncing = fusepod->get_transfer_path (new_path.c_str());
93             cout << "Adding track " << currently_syncing << "... ";
94             if (fusepod->upload_song (currently_syncing, false))
95                 cout << "Successful" << endl;
96             else
97                 cout << "Failed" << endl;
98 
99             transfer_remove (new_path.c_str());
100         }
101     }
102 }
103 
104 static void transfer_remove_empty_dirs (Node * node, const string & path, bool can_delete=true) {
105     for (Node::iterator it = node->begin(); it != node->end(); ++it)
106         if (S_ISDIR((*it)->value.mode))
107             transfer_remove_empty_dirs (*it, path + "/" + (*it)->value.text);
108 
109     if (node->begin() != node->end() || !can_delete ||
110         rmdir(fusepod->get_transfer_path (path.c_str()).c_str())) // not empty || cannot delete || can't delete real dir
111         return;
112 
113     node->remove_from_parent ();
114 
115     free ((void*)node->value.text);
116     delete node;
117 }
118 
119 
120 /** Returns fusepod->get_statistics with syncing info */
121 static string fusepod_get_stats () {
122     if (syncing) /* Add syncing stats */
123         return fusepod->get_statistics () +
124             "Currently Syncing: " + currently_syncing + "\n";
125     else
126         return fusepod->get_statistics ();
127 }
128 
129 static int fusepod_getattr (const char *path, struct stat *stbuf) {
130     Node * tn = fusepod->get_node (path);
131     if (tn == 0)
132         return -ENOENT;
133 
134     if (filename_add == &(path[1])) {
135         if (stat (add_songs, stbuf) != 0)
136             return -errno;
137         return 0;
138     }
139 
140     /* Update size for statistics file */
141     if (filename_stats == &(path[1]))
142         tn->value.size = fusepod_get_stats ().length ();
143 
144     /* Update size for files in the ipod's transfer dir */
145     if (transfer_in_dir (path)) {
146         struct stat st;
147         stat (fusepod->get_transfer_path (path).c_str (), &st);
148         tn->value.size = st.st_size;
149     }
150 
151     memset (stbuf, 0, sizeof (struct stat));
152     stbuf->st_uid   = getuid ();
153     stbuf->st_gid   = getgid ();
154     stbuf->st_mode  = tn->value.mode;
155     stbuf->st_size  = 4096;
156     stbuf->st_nlink = 1;
157 
158     if (tn->value.mode == MODE_DIR)
159         stbuf->st_nlink = tn->value.size + 2; /* +2 for .. and . */
160     else
161         stbuf->st_size = tn->value.size;
162 
163     return 0;
164 }
165 
166 static int fusepod_access (const char *path, int mask) {
167     int res;
168 
169     struct stat st;
170     res = fusepod_getattr (path, &st);
171     if (res != 0)
172         return res;
173 
174     if ((st.st_mode | mask) != st.st_mode)
175         return -EROFS;
176 
177     return 0;
178 }
179 
180 static int fusepod_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
181     (void) offset;
182     (void) fi;
183 
184     Node * tn = fusepod->get_node (path);
185     if (tn == 0 || (tn->value.mode & S_IFREG))
186         return -ENOENT;
187 
188     for (Node::iterator i = tn->begin (); i != tn->end (); ++i)
189         filler (buf, (*i)->value.text, 0, 0);
190 
191     return 0;
192 }
193 
194 static int fusepod_open(const char *path, struct fuse_file_info *fi) {
195     Node * tn = fusepod->get_node (path);
196     if (tn == 0)
197         return -ENOENT;
198 
199     string realpath;
200 
201     if (filename_add == &(path[1])) //The special file containing songs to sync
202         realpath = add_songs;
203     else if (transfer_in_dir (path)) //File in transfer directory
204         realpath = fusepod->get_transfer_path (path);
205     else if (tn->value.track) //A song
206         realpath = fusepod->get_real_path (tn->value);
207     else
208         return 0;
209 
210     int res = open(realpath.c_str(), fi->flags);
211     if (res == -1)
212         return -errno;
213 
214     fi->fh = res;
215     close (res);
216 
217     return 0;
218 }
219 
220 static int fusepod_truncate (const char * path, off_t offset) {
221     string realpath;
222 
223     if (filename_add == &(path[1]))
224         realpath = add_songs;
225     else if (transfer_in_dir (path))
226         realpath = fusepod->get_transfer_path (path);
227     else
228         return -EACCES;
229 
230     if (truncate (realpath.c_str(), offset) != 0)
231         return -errno;
232 
233     return 0;
234 }
235 
236 static int fusepod_write (const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
237     string real_path;
238 
239     if (filename_add == &(path[1]))
240         real_path = add_songs;
241     else if (transfer_in_dir (path))
242         real_path = fusepod->get_transfer_path (path);
243     else
244         return -EACCES;
245 
246     int fd;
247     int res;
248 
249     (void) fi;
250 
251     fd = open (real_path.c_str (), O_WRONLY);
252     if (fd == -1)
253         return -errno;
254 
255     res = pwrite(fd, buf, size, offset);
256     if (res == -1)
257         res = -errno;
258 
259     close(fd);
260     return res;
261 }
262 
263 static int fusepod_read_string (const string & str, char *buf, size_t size, off_t offset) {
264     if (offset >= str.length ())
265         return -EINVAL;
266 
267     int bytes_read = min ((int) (str.length () - offset), (int) size);
268     memcpy (buf, &(str.c_str ()[offset]), bytes_read);
269     return bytes_read;
270 }
271 
272 static int fusepod_read (const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
273     int fd;
274     int res;
275 
276     Node * tn = fusepod->get_node (path);
277     if (tn == 0)
278         return -ENOENT;
279     else if (!(tn->value.mode & S_IFREG))
280         return -EACCES;
281 
282 
283     /* Checking if reading in memory files */
284     if (filename_sync == &(path[1]))
285         return fusepod_read_string (sync_script, buf, size, offset);
286 
287     else if (filename_add_files == &(path[1]))
288         return fusepod_read_string (add_files_script, buf, size, offset);
289 
290     else if (filename_stats == &(path[1]))
291         return fusepod_read_string (fusepod_get_stats (), buf, size, offset);
292 
293 
294     /* Otherwise check if file in really another file on the filesystem */
295     string realpath;
296     if (filename_add == &(path[1]))
297         realpath = add_songs;
298     else if (transfer_in_dir (path))
299         realpath = fusepod->get_transfer_path (path);
300     else
301         realpath = fusepod->get_real_path (tn->value).c_str ();
302 
303 
304     (void) fi;
305     fd = open(realpath.c_str(), O_RDONLY);
306     if (fd == -1)
307         return -errno;
308 
309     res = pread(fd, buf, size, offset);
310     if (res == -1)
311         res = -errno;
312 
313     close(fd);
314     return res;
315 }
316 
317 static int fusepod_mknod (const char * path, mode_t mode, dev_t dev) {
318     if (filename_sync_do == &(path [1]) && !syncing) {
319         /* Synchronize iPod */
320         syncing = true;
321         ifstream in (add_songs);
322         string path;
323 
324         /* Read songs from add_songs */
325         while (!in.eof ()) {
326             getline (in, path);
327             fusepod_strip_string (path);
328 
329             struct stat st;
330             stat (path.c_str (), &st);
331 
332             if (S_ISREG(st.st_mode)) {
333                 cout << "Adding track " << path << "... ";
334                 currently_syncing = path;
335                 if (fusepod->upload_song (path))
336                     cout << "Successful" << endl;
337                 else
338                     cout << "Failed" << endl;
339             }
340         }
341 
342         in.close ();
343 
344         /* Empty add_songs file */
345         truncate (add_songs, 0);
346 
347         /* Recursively add songs in transfer */
348         /*string transfer_path = "/" + dir_transfer;
349         Node * transfer_node = fusepod->get_node ( ("/" + dir_transfer).c_str() );
350 
351         transfer_add_songs (transfer_node, transfer_path);
352         transfer_remove_empty_dirs (transfer_node, transfer_path, false);*/
353 
354         currently_syncing = "iTunesDB";
355         cout << "Syncing database... ";
356         fusepod->flush ();
357         cout << "Successful" << endl;
358 
359         syncing = false;
360         currently_syncing = "";
361         return 0;
362     }
363 
364     if (!transfer_in_dir (path))
365         return -EACCES;
366 
367     /* Making a file in the transfer directory */
368 
369     if (!S_ISREG (mode))
370         return -EPERM;
371 
372     string ipod_path = fusepod->get_transfer_path (path);
373 
374     if (mknod (ipod_path.c_str (), S_IFREG | 0666, 0))
375         return -errno;
376 
377     struct stat st;
378     stat (ipod_path.c_str (), &st);
379 
380     Node * parent = fusepod->root;
381 
382     char * split_path = strdup (path);
383     vector<char*> components = fusepod_split_path (split_path, '/');
384 
385     for (size_t i = 0; i < components.size ()-1; i++)
386         parent = parent->find (components [i]);
387 
388     //NOTE: filename string will have to be freed when syncing
389     NodeValue nv (strdup (components[components.size () -1]), st.st_mode, 0, st.st_size);
390 
391     free (split_path);
392 
393     parent->addChild (nv);
394 
395     return 0;
396 }
397 
398 static int fusepod_mkdir (const char * path, mode_t mode) {
399     //TODO: Add support for adding playlists
400     Node * node = fusepod->get_node (path);
401 
402     if (node != 0)
403         return -EEXIST;
404 
405     if (!transfer_in_dir (path))
406         return -EACCES;
407 
408     string realpath = fusepod->get_transfer_path (path);
409 
410     if (mkdir (realpath.c_str(), S_IFDIR | 0777))
411         return -errno;
412 
413     string parent   (path, 0, string(path).rfind ('/'));
414     string filename (path, string(path).rfind('/')+1, strlen(path)-1);
415 
416     node = fusepod->get_node (parent.c_str());
417     node->addChild (NodeValue (strdup(filename.c_str()), S_IFDIR | 0777));
418 
419     return 0;
420 }
421 
422 static int fusepod_rmdir (const char * path) {
423     Node * node = fusepod->get_node (path);
424 
425     if (node == 0)
426         return -ENOENT;
427 
428     //User can only remove directories in playlist or transfer directories
429     if (node->parent && dir_playlists == node->parent->value.text) {
430 
431         fusepod->remove_playlist (string (node->value.text));
432 
433     }
434     else if (transfer_in_dir (path)) {
435 
436         if (!S_ISDIR(node->value.mode))
437             return -ENOTDIR;
438 
439         if (node->begin() != node->end())
440             return -ENOTEMPTY;
441 
442         if (rmdir (fusepod->get_transfer_path (path).c_str()))
443             return -errno;
444 
445         node->remove_from_parent ();
446 
447         free ((void*)node->value.text);
448         delete node;
449 
450     }
451     else
452         return -EACCES;
453 
454     return 0;
455 }
456 
457 #define XATTRS gint32 _playcount = (gint32) track->playcount; \
458     gint32 _rating = track->rating / 20; \
459     const void * xattrs [14][2] = { \
460         {"tag.title",            track->title}, \
461         {"tag.artist",           track->artist}, \
462         {"tag.album",            track->album}, \
463         {"tag.genre",            track->genre}, \
464         {"tag.comment",          track->comment}, \
465         {"tag.composer",         track->composer}, \
466         {"tag.description",      track->description}, \
467         {"tag.podcasturl",       track->podcasturl}, \
468         {"tag.podcastrss",       track->podcastrss}, \
469 \
470         {"tag.track",            &track->track_nr}, \
471         {"tag.length",           &track->tracklen}, \
472         {"tag.year",             &track->year}, \
473         {"tag.playcount",        &_playcount}, \
474         {"tag.rating",           &_rating} \
475     };
476 
477 #define XATTRS_LEN 14
478 #define XATTRS_NUM 9
479 
480 
481 static int fusepod_listxattr (const char * path, char * attrs, size_t size) {
482     Node * node = fusepod->get_node (path);
483     if (!node)
484         return -ENOENT;
485 
486     if (!node->value.track)
487         return 0;
488 
489     size_t count = 0, pos = 0;
490     Track * track = node->value.track;
491 
492     XATTRS
493 
494     for (int i = 0; i < XATTRS_LEN; i++) {
495         if (i < XATTRS_NUM && !xattrs [i][1])
496             continue;
497 
498         size_t len = strlen ((char*)xattrs [i][0]) + 1;
499 
500         if (size == 0) {
501             count += len;
502             continue;
503         }
504 
505         if (len > size + pos)
506             return -ERANGE;
507 
508         memcpy (attrs + pos, xattrs [i][0], len);
509         pos += len;
510     }
511 
512     return size == 0 ? count : pos;
513 }
514 
515 static int fusepod_getxattr (const char * path, const char * attr, char * buf, size_t size) {
516     Node * node = fusepod->get_node (path);
517     if (!node)
518         return -ENOENT;
519 
520     if (!node->value.track)
521         return 0;
522 
523     Track * track = node->value.track;
524 
525     XATTRS
526 
527     for (int i = 0; i < XATTRS_LEN; i++) {
528         if (strcmp ((char*)xattrs [i][0], attr) != 0)
529             continue;
530 
531         if (!xattrs [i][1])
532             return -EACCES; //-ENOATTR;
533 
534         char * val;
535 
536         if (i < XATTRS_NUM) {
537             val = (char*)xattrs[i][1];
538         }
539         else {
540             val = new char [11]; //Max length of gint32 string is 10
541             snprintf (val, 11, "%d", *(gint32*)(xattrs[i][1]));
542             val [10] = 0;
543         }
544 
545         size_t len = strlen (val) + 1;
546 
547         if (size == 0) {
548             if (i >= XATTRS_NUM) delete val;
549             return len;
550         }
551 
552         if (len > size) {
553             if (i >= XATTRS_NUM) delete val;
554             return -ERANGE;
555         }
556 
557         strcpy (buf, val);
558 
559         if (i >= XATTRS_NUM)
560             delete val;
561 
562         return len;
563     }
564 
565     return -EACCES; //-ENOATTR;
566 }
567 
568 static int fusepod_statfs (const char * path, struct statvfs * vfs) {
569     int ret = statvfs (fusepod->mount_point.c_str (), vfs);
570 
571     if (ret != 0)
572         return -ret;
573 
574     return ret;
575 }
576 
577 static int fusepod_unlink (const char * path) {
578     Node * node = fusepod->get_node (path);
579     if (node == 0)
580         return -ENOENT;
581 
582     if (S_ISDIR (node->value.mode))
583         return -EISDIR;
584 
585     if (transfer_in_dir (path)) {
586         transfer_remove (path);
587         return 0;
588     }
589 
590     if (!node->value.track)
591         return -EACCES;
592 
593     if (!fusepod->remove_song (path))
594         return -EACCES;
595 
596     return 0;
597 }
598 
599 static int fusepod_release (const char * path, struct fuse_file_info * info) {
600     if (!transfer_in_dir (path))
601         return 0;
602 
603     /* Makes files in the Transfer directory read-only */
604     Node * node = fusepod->get_node (path);
605 
606     if (node)
607         node->value.mode = S_IFREG | 0444;
608 
609     //TODO: Add the songs here
610     currently_syncing = fusepod->get_transfer_path (path);
611     cout << "Adding track " << currently_syncing << "... ";
612     Track * track = fusepod->upload_song (currently_syncing, false);
613     if (track)
614         cout << "Successful" << endl;
615     else
616         cout << "Failed" << endl;
617 
618     transfer_remove (path);
619 
620     if (track)
621         fusepod->add_track (track);
622 
623     return 0;
624 }
625 
626 static void write_default_config () {
627     cout << "FUSEPod configuration file not found. Writing it now" << endl;
628 
629     if (!getenv ("HOME"))
630         return;
631 
632     string home = getenv ("HOME");
633     home += "/.fusepod";
634 
635     ofstream config (home.c_str ());
636 
637     if (!config)
638         return;
639 
640     config << default_config_file;
641 
642     config.close ();
643 }
644 
645 static vector<string> get_string_desc () {
646     istream * config = 0;
647 
648     if (getenv ("HOME")) {
649         string home = getenv ("HOME");
650         home += "/.fusepod";
651 
652         struct stat st;
653         stat (home.c_str (), &st);
654 
655         if (!S_ISREG(st.st_mode))
656             write_default_config ();
657 
658         config = new ifstream (home.c_str ());
659     }
660 
661     if (config)
662         cout << "Reading configuration file " << endl;
663     else
664         config = new istringstream (default_config_file);
665 
666     vector<string> paths;
667     string line;
668 
669     while (!config->eof ()) {
670         getline (*config, line);
671         line = fusepod_strip_string (line);
672         if (line.length () > 0 && line [0] == '/')
673             paths.push_back (line);
674     }
675 
676     delete config;
677 
678     return paths;
679 }
680 
681 void * fusepod_init () {
682     syncing = false;
683     syncing_file = "";
684 
685     srand (time (0)); //Random is used in FUSEPod::generate_filename()
686 
687     vector<string> pd = get_string_desc ();
688 
689     cout << "Reading iPod at " << ipod_mount_point << endl;
690     fusepod = new FUSEPod (ipod_mount_point, pd);
691     cout << "Finished reading iPod" << endl;
692 
693     /* These are special extensions to the filesystem for adding songs to the iPod*/
694     add_songs = strdup ("/tmp/fusepodXXXXXX");
695     mkstemp (add_songs);
696 
697     /* Create an empty temp file for add_songs */
698     mknod (add_songs, S_IFREG | 0666, 0);
699 
700     NodeValue add_song (fusepod_get_string (filename_add.c_str ()), S_IFREG | 0666);
701     fusepod->root->addChild (add_song);
702 
703     NodeValue sync_ipod (fusepod_get_string (filename_sync.c_str ()), S_IFREG | 0555);
704 
705     /* Make sync script */
706     sync_script =  "#!/bin/sh\n";
707     sync_script += "#FUSEPod sync script\n";
708     sync_script += "echo Syncing iPod...\n";
709     sync_script += "if [ \"$1\" = '-watch' ]; then\n";
710     sync_script += "    stats='" + fuse_mount_point + "/" + filename_stats + "'\n";
711     sync_script += "    initial=$(grep 'Track Count' \"$stats\" | cut -b 14-)\n";
712     sync_script += "    count=$(grep -c '^.*$' '" + fuse_mount_point + "/" + filename_add + "')\n";
713     sync_script += "    touch " + fuse_mount_point + "/" + filename_sync_do + " >& /dev/null &\n";
714     sync_script += "    sleep 0.2\n";
715     sync_script += "    while [ 1 ]; do\n";
716     sync_script += "        current=$(grep 'Track Count' \"$stats\" | cut -b 14-)\n";
717     sync_script += "        file=$(grep 'Currently Syncing' \"$stats\")\n";
718     sync_script += "        if [ $? != 0 ]; then break; fi\n";
719     sync_script += "        clear && echo $file && echo Track $[$current-$initial] of \"$count\" && sleep 0.2\n";
720     sync_script += "    done\n";
721     sync_script += "elif [ $# = 0 ]; then touch " + fuse_mount_point + "/" + filename_sync_do + " >& /dev/null\n";
722     sync_script += "else echo USAGE: $0 '[ -watch ]'\n";
723     sync_script += "fi\n";
724     sync_script += "echo Finished syncing iPod\n";
725 
726     sync_ipod.size = sync_script.length ();
727     fusepod->root->addChild (sync_ipod);
728 
729     NodeValue add_files (fusepod_get_string (filename_add_files.c_str ()), S_IFREG | 0555);
730 
731     /* Make Recursive Directory Add script */
732     add_files_script =  "#!/bin/sh\n";
733     add_files_script += "#FUSEPod Recursive Directory Add\n";
734     add_files_script += "if [ $# = 0 ]; then echo \"USAGE: $0 [ file or directory ] ...\"; exit 1; fi\n";
735     add_files_script += "for file in \"$@\"; do\n";
736     add_files_script += "    echo $file | grep ^/ &> /dev/null\n";
737     add_files_script += "    if [ $? != 0 ]; then file=$PWD/$file; fi\n";
738     add_files_script += "    find \"$file\" | egrep -i '(wav|mp3|m4a)$' >> '" + fuse_mount_point + "/" + filename_add + "'\n";
739     add_files_script += "done\n";
740 
741     add_files.size = add_files_script.length ();
742     fusepod->root->addChild (add_files);
743 
744     /* Add statistics file */
745     NodeValue stats (fusepod_get_string (filename_stats.c_str ()), S_IFREG | 0444);
746     fusepod->root->addChild (stats);
747 
748     /* Remove existing transfer directory the lazy way :) */
749     string transfer_dir = fusepod->mount_point + "/" + dir_transfer_ipod;
750     system (("rm -rf '" + transfer_dir + "'").c_str ());
751 
752     /* Add transfer directory */
753     if (!mkdir (transfer_dir.c_str (), S_IFDIR | 0777)) {
754         NodeValue transfer (fusepod_get_string (dir_transfer.c_str ()), S_IFDIR | 0777);
755         fusepod->root->addChild (transfer);
756     }
757 
758     cout << "Starting FUSE layer" << endl;
759 
760     return 0;
761 }
762 
763 static void fusepod_destroy (void * v) {
764     cout << "Cleaning up" << endl;
765 
766     if (add_songs)
767         remove (add_songs);
768 
769     string mount_point = fusepod->mount_point;
770 
771     if (fusepod)
772     	delete fusepod;
773 
774     string transfer_dir = mount_point + "/" + dir_transfer_ipod;
775     system (("rm -rf '" + transfer_dir + "'").c_str ());
776 }
777 
778 static struct fuse_operations fusepod_oper;
779 
780 int main (int argc, char **argv) {
781     //TODO Apparantly error messages aren't clear enough
782     if (argc > 1) {
783         /* This finds out were the mount point is so that any app can use it.
784            Note that it may contain ../ and ./ in it */
785         fuse_mount_point = argv [argc-1];
786         if (fuse_mount_point [0] != '/') {
787             if (!getenv ("PWD")) {
788                 cerr << "ERROR: Please supply the PWD environment variable or an absolute mount point" << endl;
789                 exit (1);
790             }
791             fuse_mount_point = string (getenv ("PWD")) + "/" + fuse_mount_point;
792         }
793 
794         /* Fixes subtle bug of when FUSE freezes when it doesn't find the iPod directory.
795            Check for help option. If not found and ipod cannot be found, exit.*/
796         bool found = false;
797         for (int i = 1; i < argc && !found; i++)
798             if (strcmp (argv [i], "--help") == 0 || strcmp (argv [i], "-h") == 0)
799                 found = true;
800 
801         ipod_mount_point = FUSEPod::discover_ipod ();
802 
803         if (!found && ipod_mount_point == "") {
804             cerr << "ERROR: Cannot find the iPod mount point.\n";
805             cerr << "This may happen because the iPod is not mounted or you have not created an itunes database yet\n";
806             cerr << "Please specifiy the mount point through the enviroment variable IPOD_DIR\n";
807             cerr << "Eg: IPOD_DIR=\"/media/ipod\" fusepod /home/keegan/myipod\n";
808             cerr << flush;
809             exit (1);
810         }
811 
812         char * ipod_dir = getenv ("IPOD_DIR");
813         if (!ipod_dir) ipod_dir = getenv ("IPOD_MOUNTPOINT");
814 
815         if (!found && ipod_dir && ipod_mount_point == ipod_dir &&
816             access ( (string (ipod_dir) + ITUNESDB_PATH).c_str (), F_OK ) != 0) { // Checks for itunesdb
817             cerr << "ERROR: Cannot find the iTunesDB in the directory specified by the IPOD_DIR or IPOD_MOUNTPOINT environment variable.\n";
818             cerr << "Do you want to create the iTunesDB in the specified directory? (y/n): ";
819 
820             char ans;
821             cin >> ans;
822             if (ans != 'y') {
823                 cerr << "\nCannot run FUSEPod without iTunesDB.\nExiting...\n";
824                 exit (1);
825             }
826 
827             if (!FUSEPod::create_itunes_dirs (string (ipod_dir))) {
828                 cerr << "\nERROR: Cannot create iTunesDB directory structure.\n";
829                 cerr << "Exiting...\n";
830                 exit (1);
831             }
832         }
833     }
834 
835     fusepod_oper.getattr   = fusepod_getattr;
836     fusepod_oper.listxattr = fusepod_listxattr;
837     fusepod_oper.getxattr  = fusepod_getxattr;
838     fusepod_oper.access    = fusepod_access;
839     fusepod_oper.readdir   = fusepod_readdir;
840     fusepod_oper.open      = fusepod_open;
841     fusepod_oper.truncate  = fusepod_truncate;
842     fusepod_oper.write     = fusepod_write;
843     fusepod_oper.read      = fusepod_read;
844     fusepod_oper.unlink    = fusepod_unlink;
845     fusepod_oper.statfs    = fusepod_statfs;
846     fusepod_oper.mknod     = fusepod_mknod;
847     fusepod_oper.mkdir     = fusepod_mkdir;
848     fusepod_oper.rmdir     = fusepod_rmdir;
849     fusepod_oper.release   = fusepod_release;
850     fusepod_oper.init      = fusepod_init;
851     fusepod_oper.destroy   = fusepod_destroy;
852 
853     return fuse_main(argc, argv, &fusepod_oper);
854 }
855