access: dtv: simplify/regroup setup and drop delsys
[vlc.git] / modules / access / nfs.c
1 /*****************************************************************************
2  * nfs.c: NFS VLC access plug-in
3  *****************************************************************************
4  * Copyright © 2016  VLC authors, VideoLAN and VideoLabs
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <assert.h>
26 #include <errno.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #ifdef HAVE_POLL
33 # include <poll.h>
34 #endif
35
36 #include <vlc_common.h>
37 #include <vlc_access.h>
38 #include <vlc_dialog.h>
39 #include <vlc_input_item.h>
40 #include <vlc_plugin.h>
41 #include <vlc_url.h>
42 #include <vlc_interrupt.h>
43
44 #include <nfsc/libnfs.h>
45 #include <nfsc/libnfs-raw.h>
46 #include <nfsc/libnfs-raw-nfs.h>
47 #include <nfsc/libnfs-raw-mount.h>
48
49 #define AUTO_GUID_TEXT N_("Set NFS uid/guid automatically")
50 #define AUTO_GUID_LONGTEXT N_("If uid/gid are not specified in " \
51     "the url, this module will try to automatically set a uid/gid.")
52
53 static int Open(vlc_object_t *);
54 static void Close(vlc_object_t *);
55
56 vlc_module_begin()
57     set_shortname(N_("NFS"))
58     set_description(N_("NFS input"))
59     set_category(CAT_INPUT)
60     set_subcategory(SUBCAT_INPUT_ACCESS)
61     add_bool("nfs-auto-guid", true, AUTO_GUID_TEXT, AUTO_GUID_LONGTEXT, true)
62     set_capability("access", 2)
63     add_shortcut("nfs")
64     set_callbacks(Open, Close)
65 vlc_module_end()
66
67 struct access_sys_t
68 {
69     struct rpc_context *    p_mount; /* used to to get exports mount point */
70     struct nfs_context *    p_nfs;
71     struct nfs_url *        p_nfs_url;
72     struct nfs_stat_64      stat;
73     struct nfsfh *          p_nfsfh;
74     struct nfsdir *         p_nfsdir;
75     vlc_url_t               encoded_url;
76     char *                  psz_url_decoded;
77     char *                  psz_url_decoded_slash;
78     bool                    b_error;
79     bool                    b_auto_guid;
80
81     union {
82         struct
83         {
84             char **         ppsz_names;
85             int             i_count;
86             unsigned int    i_current;
87         } exports;
88         struct
89         {
90             uint8_t *p_buf;
91             size_t i_len;
92         } read;
93         struct
94         {
95             bool b_done;
96         } seek;
97     } res;
98 };
99
100 static bool
101 nfs_check_status(access_t *p_access, int i_status, const char *psz_error,
102                  const char *psz_func)
103 {
104     if (i_status < 0)
105     {
106         if (i_status != -EINTR)
107         {
108             msg_Err(p_access, "%s failed: %d, '%s'", psz_func, i_status,
109                     psz_error);
110             if (!p_access->p_sys->b_error)
111                 vlc_dialog_display_error(p_access,
112                                          _("NFS operation failed"), "%s",
113                                          psz_error);
114         }
115         else
116             msg_Warn(p_access, "%s interrupted", psz_func);
117         p_access->p_sys->b_error = true;
118         return true;
119     }
120     else
121         return false;
122 }
123 #define NFS_CHECK_STATUS(p_access, i_status, p_data) \
124     nfs_check_status(p_access, i_status, (const char *)p_data, __func__)
125
126 static int
127 vlc_rpc_mainloop(access_t *p_access, struct rpc_context *p_rpc_ctx,
128                  bool (*pf_until_cb)(access_t *))
129 {
130     access_sys_t *p_sys = p_access->p_sys;
131
132     while (!p_sys->b_error && !pf_until_cb(p_access))
133     {
134         struct pollfd p_fds[1];
135         int i_ret;
136         p_fds[0].fd = rpc_get_fd(p_rpc_ctx);
137         p_fds[0].events = rpc_which_events(p_rpc_ctx);
138
139         if ((i_ret = vlc_poll_i11e(p_fds, 1, -1)) < 0)
140         {
141             if (errno == EINTR)
142                 msg_Warn(p_access, "vlc_poll_i11e interrupted");
143             else
144                 msg_Err(p_access, "vlc_poll_i11e failed");
145             p_sys->b_error = true;
146         }
147         else if (i_ret > 0 && p_fds[0].revents
148              && rpc_service(p_rpc_ctx, p_fds[0].revents) < 0)
149         {
150             msg_Err(p_access, "nfs_service failed");
151             p_sys->b_error = true;
152         }
153     }
154     return p_sys->b_error ? -1 : 0;
155 }
156
157 static int
158 vlc_nfs_mainloop(access_t *p_access, bool (*pf_until_cb)(access_t *))
159 {
160     access_sys_t *p_sys = p_access->p_sys;
161     assert(p_sys->p_nfs != NULL);
162     return vlc_rpc_mainloop(p_access, nfs_get_rpc_context(p_sys->p_nfs),
163                             pf_until_cb);
164 }
165
166 static int
167 vlc_mount_mainloop(access_t *p_access, bool (*pf_until_cb)(access_t *))
168 {
169     access_sys_t *p_sys = p_access->p_sys;
170     assert(p_sys->p_mount != NULL);
171     return vlc_rpc_mainloop(p_access, p_sys->p_mount, pf_until_cb);
172 }
173
174 static void
175 nfs_read_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
176             void *p_private_data)
177 {
178     access_t *p_access = p_private_data;
179     access_sys_t *p_sys = p_access->p_sys;
180     assert(p_sys->p_nfs == p_nfs);
181     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
182         return;
183
184     if (i_status == 0)
185         p_access->info.b_eof = true;
186     else
187     {
188         p_sys->res.read.i_len = i_status;
189         memcpy(p_sys->res.read.p_buf, p_data, i_status);
190     }
191 }
192
193 static bool
194 nfs_read_finished_cb(access_t *p_access)
195 {
196     access_sys_t *p_sys = p_access->p_sys;
197     return p_sys->res.read.i_len > 0 || p_access->info.b_eof;
198 }
199
200 static ssize_t
201 FileRead(access_t *p_access, uint8_t *p_buf, size_t i_len)
202 {
203     access_sys_t *p_sys = p_access->p_sys;
204
205     p_sys->res.read.i_len = 0;
206     p_sys->res.read.p_buf = p_buf;
207     if (nfs_read_async(p_sys->p_nfs, p_sys->p_nfsfh, i_len, nfs_read_cb,
208                        p_access) < 0)
209     {
210         msg_Err(p_access, "nfs_read_async failed");
211         return -1;
212     }
213
214     if (vlc_nfs_mainloop(p_access, nfs_read_finished_cb) < 0)
215         return -1;
216
217     return p_sys->res.read.i_len;
218 }
219
220 static void
221 nfs_seek_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
222             void *p_private_data)
223 {
224     access_t *p_access = p_private_data;
225     access_sys_t *p_sys = p_access->p_sys;
226     assert(p_sys->p_nfs == p_nfs);
227     (void) p_data;
228     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
229         return;
230
231     p_sys->res.seek.b_done = true;
232 }
233
234 static bool
235 nfs_seek_finished_cb(access_t *p_access)
236 {
237     access_sys_t *p_sys = p_access->p_sys;
238     return p_sys->res.seek.b_done;
239 }
240
241 static int
242 FileSeek(access_t *p_access, uint64_t i_pos)
243 {
244     access_sys_t *p_sys = p_access->p_sys;
245     p_access->info.b_eof = false;
246
247     p_sys->res.seek.b_done = false;
248     if (nfs_lseek_async(p_sys->p_nfs, p_sys->p_nfsfh, i_pos, SEEK_SET,
249                         nfs_seek_cb, p_access) < 0)
250     {
251         msg_Err(p_access, "nfs_seek_async failed");
252         return VLC_EGENERIC;
253     }
254
255     if (vlc_nfs_mainloop(p_access, nfs_seek_finished_cb) < 0)
256         return VLC_EGENERIC;
257
258     return VLC_SUCCESS;
259 }
260
261 static int
262 FileControl(access_t *p_access, int i_query, va_list args)
263 {
264     access_sys_t *p_sys = p_access->p_sys;
265
266     switch (i_query)
267     {
268         case ACCESS_CAN_SEEK:
269             *va_arg(args, bool *) = true;
270             break;
271
272         case ACCESS_CAN_FASTSEEK:
273             *va_arg(args, bool *) = false;
274             break;
275
276         case ACCESS_CAN_PAUSE:
277         case ACCESS_CAN_CONTROL_PACE:
278             *va_arg(args, bool *) = true;
279             break;
280
281         case ACCESS_GET_SIZE:
282         {
283             *va_arg(args, uint64_t *) = p_sys->stat.nfs_size;
284             break;
285         }
286
287         case ACCESS_GET_PTS_DELAY:
288             *va_arg(args, int64_t *) = var_InheritInteger(p_access,
289                                                           "network-caching");
290             break;
291
292         case ACCESS_SET_PAUSE_STATE:
293             break;
294
295         default:
296             return VLC_EGENERIC;
297     }
298     return VLC_SUCCESS;
299 }
300
301 static char *
302 NfsGetUrl(vlc_url_t *p_url, const char *psz_file)
303 {
304     /* nfs://<psz_host><psz_path><psz_file>?<psz_option> */
305     char *psz_url;
306     if (asprintf(&psz_url, "nfs://%s%s%s%s%s%s", p_url->psz_host,
307                  p_url->psz_path != NULL ? p_url->psz_path : "",
308                  p_url->psz_path != NULL && p_url->psz_path[0] != '\0' &&
309                  p_url->psz_path[strlen(p_url->psz_path) - 1] != '/' ? "/" : "",
310                  psz_file,
311                  p_url->psz_option != NULL ? "?" : "",
312                  p_url->psz_option != NULL ? p_url->psz_option : "") == -1)
313         return NULL;
314     else
315         return psz_url;
316 }
317
318 static input_item_t *
319 DirRead(access_t *p_access)
320 {
321     access_sys_t *p_sys = p_access->p_sys;
322     struct nfsdirent *p_nfsdirent;
323     assert(p_sys->p_nfsdir);
324
325     p_nfsdirent = nfs_readdir(p_sys->p_nfs, p_sys->p_nfsdir);
326     if (p_nfsdirent == NULL)
327         return NULL;
328     else
329     {
330         input_item_t *p_item;
331         char *psz_name_encoded = vlc_uri_encode(p_nfsdirent->name);
332         if (psz_name_encoded == NULL)
333             return NULL;
334         char *psz_url = NfsGetUrl(&p_sys->encoded_url, psz_name_encoded);
335         free(psz_name_encoded);
336         if (psz_url == NULL)
337             return NULL;
338
339         int i_type;
340         switch (p_nfsdirent->type)
341         {
342         case NF3REG:
343             i_type = ITEM_TYPE_FILE;
344             break;
345         case NF3DIR:
346             i_type = ITEM_TYPE_DIRECTORY;
347             break;
348         default:
349             i_type = ITEM_TYPE_UNKNOWN;
350         }
351         p_item = input_item_NewExt(psz_url, p_nfsdirent->name, -1, i_type,
352                                    ITEM_NET);
353         free(psz_url);
354         return p_item;
355     }
356 }
357
358 static input_item_t *
359 MountRead(access_t *p_access)
360 {
361     access_sys_t *p_sys = p_access->p_sys;
362     assert(p_sys->p_mount != NULL && p_sys->res.exports.i_count >= 0);
363
364     unsigned int i_count = p_sys->res.exports.i_count;
365     unsigned int i_current = p_sys->res.exports.i_current;
366     if (i_current >= i_count)
367         return NULL;
368
369     char *psz_name = p_sys->res.exports.ppsz_names[i_current];
370     p_sys->res.exports.i_current++;
371
372     char *psz_url = NfsGetUrl(&p_sys->encoded_url, psz_name);
373     if (psz_url == NULL)
374         return NULL;
375
376     input_item_t *p_item = input_item_NewDirectory(psz_url, psz_name, ITEM_NET);
377     free(psz_url);
378     return p_item;
379 }
380
381 static int
382 DirControl(access_t *p_access, int i_query, va_list args)
383 {
384     switch (i_query)
385     {
386     case ACCESS_IS_DIRECTORY:
387         *va_arg( args, bool * ) = false; /* is not sorted */
388         *va_arg( args, bool * ) = true; /* might loop */
389         break;
390     default:
391         return access_vaDirectoryControlHelper(p_access, i_query, args);
392     }
393
394     return VLC_SUCCESS;
395 }
396
397 static void
398 nfs_opendir_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
399                void *p_private_data)
400 {
401     access_t *p_access = p_private_data;
402     access_sys_t *p_sys = p_access->p_sys;
403     assert(p_sys->p_nfs == p_nfs);
404     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
405         return;
406
407     p_sys->p_nfsdir = p_data;
408 }
409
410 static void
411 nfs_open_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
412             void *p_private_data)
413 {
414     access_t *p_access = p_private_data;
415     access_sys_t *p_sys = p_access->p_sys;
416     assert(p_sys->p_nfs == p_nfs);
417     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
418         return;
419
420     p_sys->p_nfsfh = p_data;
421 }
422
423 static void
424 nfs_stat64_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
425               void *p_private_data)
426 {
427     access_t *p_access = p_private_data;
428     access_sys_t *p_sys = p_access->p_sys;
429     assert(p_sys->p_nfs == p_nfs);
430     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
431         return;
432
433     struct nfs_stat_64 *p_stat = p_data;
434     p_sys->stat = *p_stat;
435
436     if (p_sys->b_auto_guid)
437     {
438         nfs_set_uid(p_sys->p_nfs, p_sys->stat.nfs_uid);
439         nfs_set_gid(p_sys->p_nfs, p_sys->stat.nfs_gid);
440     }
441
442     if (S_ISDIR(p_sys->stat.nfs_mode))
443     {
444         msg_Dbg(p_access, "nfs_opendir: '%s'", p_sys->p_nfs_url->file);
445         if (nfs_opendir_async(p_sys->p_nfs, p_sys->p_nfs_url->file,
446                               nfs_opendir_cb, p_access) != 0)
447         {
448             msg_Err(p_access, "nfs_opendir_async failed");
449             p_sys->b_error = true;
450         }
451     }
452     else if (S_ISREG(p_sys->stat.nfs_mode))
453     {
454         msg_Dbg(p_access, "nfs_open: '%s'", p_sys->p_nfs_url->file);
455         if (nfs_open_async(p_sys->p_nfs, p_sys->p_nfs_url->file, O_RDONLY,
456                            nfs_open_cb, p_access) < 0)
457         {
458             msg_Err(p_access, "nfs_open_async failed");
459             p_sys->b_error = true;
460         }
461     }
462     else
463     {
464         msg_Err(p_access, "nfs_stat64_cb: file type not handled");
465         p_sys->b_error = true;
466     }
467 }
468
469 static void
470 nfs_mount_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
471              void *p_private_data)
472 {
473     access_t *p_access = p_private_data;
474     access_sys_t *p_sys = p_access->p_sys;
475     assert(p_sys->p_nfs == p_nfs);
476     (void) p_data;
477
478     /* If a directory url doesn't end with '/', there is no way to know which
479      * part of the url is the export point and which part is the path. An
480      * example with "nfs://myhost/mnt/data": we can't know if /mnt or /mnt/data
481      * is the export point. Therefore, in case of EACCES error, retry to mount
482      * the url by adding a '/' to the decoded path. */
483     if (i_status == -EACCES && p_sys->psz_url_decoded_slash == NULL)
484     {
485         vlc_url_t url;
486         vlc_UrlParse(&url, p_sys->psz_url_decoded);
487         if (url.psz_path == NULL || url.psz_path[0] == '\0'
488          || url.psz_path[strlen(url.psz_path) - 1] == '/'
489          || (p_sys->psz_url_decoded_slash = NfsGetUrl(&url, "/")) == NULL)
490         {
491             vlc_UrlClean(&url);
492             NFS_CHECK_STATUS(p_access, i_status, p_data);
493             return;
494         }
495         else
496         {
497             vlc_UrlClean(&url);
498             msg_Warn(p_access, "trying to mount '%s' again by adding a '/'",
499                      p_access->psz_url);
500             return;
501         }
502     }
503
504     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
505         return;
506
507     if (nfs_stat64_async(p_sys->p_nfs, p_sys->p_nfs_url->file, nfs_stat64_cb,
508                          p_access) < 0)
509     {
510         msg_Err(p_access, "nfs_stat64_async failed");
511         p_sys->b_error = true;
512     }
513 }
514
515 static bool
516 nfs_mount_open_finished_cb(access_t *p_access)
517 {
518     access_sys_t *p_sys = p_access->p_sys;
519     return p_sys->p_nfsfh != NULL || p_sys->p_nfsdir != NULL
520         || p_sys->psz_url_decoded_slash != NULL;
521 }
522
523 static bool
524 nfs_mount_open_slash_finished_cb(access_t *p_access)
525 {
526     access_sys_t *p_sys = p_access->p_sys;
527     return p_sys->p_nfsfh != NULL || p_sys->p_nfsdir != NULL;
528 }
529
530 static void
531 mount_export_cb(struct rpc_context *p_ctx, int i_status, void *p_data,
532                 void *p_private_data)
533 {
534     access_t *p_access = p_private_data;
535     access_sys_t *p_sys = p_access->p_sys;
536     assert(p_sys->p_mount == p_ctx);
537     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
538         return;
539
540     exports p_export = *(exports *)p_data;
541     p_sys->res.exports.i_count = 0;
542
543     /* Dup the export linked list into an array of const char * */
544     while (p_export != NULL)
545     {
546         p_sys->res.exports.i_count++;
547         p_export = p_export->ex_next;
548     }
549     if (p_sys->res.exports.i_count == 0)
550         return;
551
552     p_sys->res.exports.ppsz_names = calloc(p_sys->res.exports.i_count,
553                                            sizeof(char *));
554     if (p_sys->res.exports.ppsz_names == NULL)
555     {
556         p_sys->b_error = true;
557         return;
558     }
559
560     p_export = *(exports *)p_data;
561     unsigned int i_idx = 0;
562     while (p_export != NULL)
563     {
564         p_sys->res.exports.ppsz_names[i_idx] = strdup(p_export->ex_dir);
565         if (p_sys->res.exports.ppsz_names[i_idx] == NULL)
566         {
567             for (unsigned int i = 0; i < i_idx; ++i)
568                 free(p_sys->res.exports.ppsz_names[i]);
569             free(p_sys->res.exports.ppsz_names);
570             p_sys->res.exports.ppsz_names = NULL;
571             p_sys->res.exports.i_count = 0;
572             p_sys->b_error = true;
573             return;
574         }
575         i_idx++;
576         p_export = p_export->ex_next;
577     }
578 }
579
580 static bool
581 mount_getexports_finished_cb(access_t *p_access)
582 {
583     access_sys_t *p_sys = p_access->p_sys;
584     return p_sys->res.exports.i_count != -1;
585 }
586
587 static int
588 NfsInit(access_t *p_access, const char *psz_url_decoded)
589 {
590     access_sys_t *p_sys = p_access->p_sys;
591     p_sys->p_nfs = nfs_init_context();
592     if (p_sys->p_nfs == NULL)
593     {
594         msg_Err(p_access, "nfs_init_context failed");
595         return -1;
596     }
597
598     p_sys->p_nfs_url = nfs_parse_url_incomplete(p_sys->p_nfs, psz_url_decoded);
599     if (p_sys->p_nfs_url == NULL || p_sys->p_nfs_url->server == NULL)
600     {
601         msg_Err(p_access, "nfs_parse_url_incomplete failed: '%s'",
602                 nfs_get_error(p_sys->p_nfs));
603         return -1;
604     }
605     return 0;
606 }
607
608 static int
609 Open(vlc_object_t *p_obj)
610 {
611     access_t *p_access = (access_t *)p_obj;
612     access_sys_t *p_sys = calloc(1, sizeof (*p_sys));
613
614     if (unlikely(p_sys == NULL))
615         goto error;
616     p_access->p_sys = p_sys;
617     p_access->info.b_eof = false;
618
619     p_sys->b_auto_guid = var_InheritBool(p_obj, "nfs-auto-guid");
620
621     /* nfs_* functions need a decoded url */
622     p_sys->psz_url_decoded = vlc_uri_decode_duplicate(p_access->psz_url);
623     if (p_sys->psz_url_decoded == NULL)
624         goto error;
625
626     /* Parse the encoded URL */
627     vlc_UrlParse(&p_sys->encoded_url, p_access->psz_url);
628     if (p_sys->encoded_url.psz_option)
629     {
630         if (strstr(p_sys->encoded_url.psz_option, "uid")
631          || strstr(p_sys->encoded_url.psz_option, "gid"))
632             p_sys->b_auto_guid = false;
633     }
634
635     if (NfsInit(p_access, p_sys->psz_url_decoded) == -1)
636         goto error;
637
638     if (p_sys->p_nfs_url->path != NULL && p_sys->p_nfs_url->file != NULL)
639     {
640         /* The url has a valid path and file, mount the path and open/opendir
641          * the file */
642         msg_Dbg(p_access, "nfs_mount: server: '%s', path: '%s'",
643                 p_sys->p_nfs_url->server, p_sys->p_nfs_url->path);
644
645         if (nfs_mount_async(p_sys->p_nfs, p_sys->p_nfs_url->server,
646                             p_sys->p_nfs_url->path, nfs_mount_cb, p_access) < 0)
647         {
648             msg_Err(p_access, "nfs_mount_async failed");
649             goto error;
650         }
651
652         if (vlc_nfs_mainloop(p_access, nfs_mount_open_finished_cb) < 0)
653             goto error;
654
655         if (p_sys->psz_url_decoded_slash != NULL)
656         {
657             /* Retry to mount by adding a '/' to the path, see comment in
658              * nfs_mount_cb */
659             nfs_destroy_url(p_sys->p_nfs_url);
660             nfs_destroy_context(p_sys->p_nfs);
661             p_sys->p_nfs_url = NULL;
662             p_sys->p_nfs = NULL;
663
664             if (NfsInit(p_access, p_sys->psz_url_decoded_slash) == -1
665              || p_sys->p_nfs_url->path == NULL || p_sys->p_nfs_url->file == NULL)
666                 goto error;
667
668             if (nfs_mount_async(p_sys->p_nfs, p_sys->p_nfs_url->server,
669                                 p_sys->p_nfs_url->path, nfs_mount_cb, p_access) < 0)
670             {
671                 msg_Err(p_access, "nfs_mount_async failed");
672                 goto error;
673             }
674
675             if (vlc_nfs_mainloop(p_access, nfs_mount_open_slash_finished_cb) < 0)
676                 goto error;
677         }
678
679         if (p_sys->p_nfsfh != NULL)
680         {
681             p_access->pf_read = FileRead;
682             p_access->pf_seek = FileSeek;
683             p_access->pf_control = FileControl;
684         }
685         else if (p_sys->p_nfsdir != NULL)
686         {
687             p_access->pf_readdir = DirRead;
688             p_access->pf_seek = NULL;
689             p_access->pf_control = DirControl;
690         }
691         else
692             vlc_assert_unreachable();
693     }
694     else
695     {
696         /* url is just a server: fetch exports point */
697         nfs_destroy_context(p_sys->p_nfs);
698         p_sys->p_nfs = NULL;
699
700         p_sys->p_mount = rpc_init_context();
701         if (p_sys->p_mount == NULL)
702         {
703             msg_Err(p_access, "rpc_init_context failed");
704             goto error;
705         }
706
707         p_sys->res.exports.ppsz_names = NULL;
708         p_sys->res.exports.i_count = -1;
709         p_sys->res.exports.i_current = 0;
710
711         if (mount_getexports_async(p_sys->p_mount, p_sys->p_nfs_url->server,
712                                    mount_export_cb, p_access) < 0)
713         {
714             msg_Err(p_access, "mount_getexports_async failed");
715             goto error;
716         }
717
718         if (vlc_mount_mainloop(p_access, mount_getexports_finished_cb) < 0)
719             goto error;
720
721         p_access->pf_readdir = MountRead;
722         p_access->pf_seek = NULL;
723         p_access->pf_control = DirControl;
724     }
725
726     return VLC_SUCCESS;
727
728 error:
729     Close(p_obj);
730     return VLC_EGENERIC;
731 }
732
733 static void
734 Close(vlc_object_t *p_obj)
735 {
736     access_t *p_access = (access_t *)p_obj;
737     access_sys_t *p_sys = p_access->p_sys;
738
739     if (p_sys->p_nfsfh != NULL)
740         nfs_close(p_sys->p_nfs, p_sys->p_nfsfh);
741
742     if (p_sys->p_nfsdir != NULL)
743         nfs_closedir(p_sys->p_nfs, p_sys->p_nfsdir);
744
745     if (p_sys->p_nfs != NULL)
746         nfs_destroy_context(p_sys->p_nfs);
747
748     if (p_sys->p_mount != NULL)
749     {
750         for (int i = 0; i < p_sys->res.exports.i_count; ++i)
751             free(p_sys->res.exports.ppsz_names[i]);
752         free(p_sys->res.exports.ppsz_names);
753         rpc_destroy_context(p_sys->p_mount);
754     }
755
756     if (p_sys->p_nfs_url != NULL)
757         nfs_destroy_url(p_sys->p_nfs_url);
758
759     vlc_UrlClean(&p_sys->encoded_url);
760
761     free(p_sys->psz_url_decoded);
762     free(p_sys->psz_url_decoded_slash);
763     free(p_sys);
764 }