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, VLC will 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_eof;
79     bool                    b_error;
80     bool                    b_auto_guid;
81 
82     union {
83         struct
84         {
85             char **         ppsz_names;
86             int             i_count;
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
nfs_check_status(stream_t * p_access,int i_status,const char * psz_error,const char * psz_func)101 nfs_check_status(stream_t *p_access, int i_status, const char *psz_error,
102                  const char *psz_func)
103 {
104     access_sys_t *sys = p_access->p_sys;
105 
106     if (i_status < 0)
107     {
108         if (i_status != -EINTR)
109         {
110             msg_Err(p_access, "%s failed: %d, '%s'", psz_func, i_status,
111                     psz_error);
112             if (!sys->b_error)
113                 vlc_dialog_display_error(p_access,
114                                          _("NFS operation failed"), "%s",
115                                          psz_error);
116         }
117         else
118             msg_Warn(p_access, "%s interrupted", psz_func);
119         sys->b_error = true;
120         return true;
121     }
122     else
123         return false;
124 }
125 #define NFS_CHECK_STATUS(p_access, i_status, p_data) \
126     nfs_check_status(p_access, i_status, (const char *)p_data, __func__)
127 
128 static int
vlc_rpc_mainloop(stream_t * p_access,struct rpc_context * p_rpc_ctx,bool (* pf_until_cb)(stream_t *))129 vlc_rpc_mainloop(stream_t *p_access, struct rpc_context *p_rpc_ctx,
130                  bool (*pf_until_cb)(stream_t *))
131 {
132     access_sys_t *p_sys = p_access->p_sys;
133 
134     while (!p_sys->b_error && !pf_until_cb(p_access))
135     {
136         struct pollfd p_fds[1];
137         int i_ret;
138         p_fds[0].fd = rpc_get_fd(p_rpc_ctx);
139         p_fds[0].events = rpc_which_events(p_rpc_ctx);
140 
141         if ((i_ret = vlc_poll_i11e(p_fds, 1, -1)) < 0)
142         {
143             if (errno == EINTR)
144                 msg_Warn(p_access, "vlc_poll_i11e interrupted");
145             else
146                 msg_Err(p_access, "vlc_poll_i11e failed");
147             p_sys->b_error = true;
148         }
149         else if (i_ret > 0 && p_fds[0].revents
150              && rpc_service(p_rpc_ctx, p_fds[0].revents) < 0)
151         {
152             msg_Err(p_access, "nfs_service failed");
153             p_sys->b_error = true;
154         }
155     }
156     return p_sys->b_error ? -1 : 0;
157 }
158 
159 static int
vlc_nfs_mainloop(stream_t * p_access,bool (* pf_until_cb)(stream_t *))160 vlc_nfs_mainloop(stream_t *p_access, bool (*pf_until_cb)(stream_t *))
161 {
162     access_sys_t *p_sys = p_access->p_sys;
163     assert(p_sys->p_nfs != NULL);
164     return vlc_rpc_mainloop(p_access, nfs_get_rpc_context(p_sys->p_nfs),
165                             pf_until_cb);
166 }
167 
168 static int
vlc_mount_mainloop(stream_t * p_access,bool (* pf_until_cb)(stream_t *))169 vlc_mount_mainloop(stream_t *p_access, bool (*pf_until_cb)(stream_t *))
170 {
171     access_sys_t *p_sys = p_access->p_sys;
172     assert(p_sys->p_mount != NULL);
173     return vlc_rpc_mainloop(p_access, p_sys->p_mount, pf_until_cb);
174 }
175 
176 static void
nfs_read_cb(int i_status,struct nfs_context * p_nfs,void * p_data,void * p_private_data)177 nfs_read_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
178             void *p_private_data)
179 {
180     VLC_UNUSED(p_nfs);
181     stream_t *p_access = p_private_data;
182     access_sys_t *p_sys = p_access->p_sys;
183     assert(p_sys->p_nfs == p_nfs);
184     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
185         return;
186 
187     if (i_status == 0)
188         p_sys->b_eof = true;
189     else
190     {
191         p_sys->res.read.i_len = i_status;
192         memcpy(p_sys->res.read.p_buf, p_data, i_status);
193     }
194 }
195 
196 static bool
nfs_read_finished_cb(stream_t * p_access)197 nfs_read_finished_cb(stream_t *p_access)
198 {
199     access_sys_t *p_sys = p_access->p_sys;
200     return p_sys->res.read.i_len > 0 || p_sys->b_eof;
201 }
202 
203 static ssize_t
FileRead(stream_t * p_access,void * p_buf,size_t i_len)204 FileRead(stream_t *p_access, void *p_buf, size_t i_len)
205 {
206     access_sys_t *p_sys = p_access->p_sys;
207 
208     if (p_sys->b_eof)
209         return 0;
210 
211     p_sys->res.read.i_len = 0;
212     p_sys->res.read.p_buf = p_buf;
213     if (nfs_read_async(p_sys->p_nfs, p_sys->p_nfsfh, i_len, nfs_read_cb,
214                        p_access) < 0)
215     {
216         msg_Err(p_access, "nfs_read_async failed");
217         return -1;
218     }
219 
220     if (vlc_nfs_mainloop(p_access, nfs_read_finished_cb) < 0)
221         return -1;
222 
223     return p_sys->res.read.i_len;
224 }
225 
226 static void
nfs_seek_cb(int i_status,struct nfs_context * p_nfs,void * p_data,void * p_private_data)227 nfs_seek_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
228             void *p_private_data)
229 {
230     VLC_UNUSED(p_nfs);
231     stream_t *p_access = p_private_data;
232     access_sys_t *p_sys = p_access->p_sys;
233     assert(p_sys->p_nfs == p_nfs);
234     (void) p_data;
235     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
236         return;
237 
238     p_sys->res.seek.b_done = true;
239 }
240 
241 static bool
nfs_seek_finished_cb(stream_t * p_access)242 nfs_seek_finished_cb(stream_t *p_access)
243 {
244     access_sys_t *p_sys = p_access->p_sys;
245     return p_sys->res.seek.b_done;
246 }
247 
248 static int
FileSeek(stream_t * p_access,uint64_t i_pos)249 FileSeek(stream_t *p_access, uint64_t i_pos)
250 {
251     access_sys_t *p_sys = p_access->p_sys;
252 
253     p_sys->res.seek.b_done = false;
254     if (nfs_lseek_async(p_sys->p_nfs, p_sys->p_nfsfh, i_pos, SEEK_SET,
255                         nfs_seek_cb, p_access) < 0)
256     {
257         msg_Err(p_access, "nfs_seek_async failed");
258         return VLC_EGENERIC;
259     }
260 
261     if (vlc_nfs_mainloop(p_access, nfs_seek_finished_cb) < 0)
262         return VLC_EGENERIC;
263 
264     p_sys->b_eof = false;
265 
266     return VLC_SUCCESS;
267 }
268 
269 static int
FileControl(stream_t * p_access,int i_query,va_list args)270 FileControl(stream_t *p_access, int i_query, va_list args)
271 {
272     access_sys_t *p_sys = p_access->p_sys;
273 
274     switch (i_query)
275     {
276         case STREAM_CAN_SEEK:
277             *va_arg(args, bool *) = true;
278             break;
279 
280         case STREAM_CAN_FASTSEEK:
281             *va_arg(args, bool *) = false;
282             break;
283 
284         case STREAM_CAN_PAUSE:
285         case STREAM_CAN_CONTROL_PACE:
286             *va_arg(args, bool *) = true;
287             break;
288 
289         case STREAM_GET_SIZE:
290         {
291             *va_arg(args, uint64_t *) = p_sys->stat.nfs_size;
292             break;
293         }
294 
295         case STREAM_GET_PTS_DELAY:
296             *va_arg(args, int64_t *) = var_InheritInteger(p_access,
297                                                           "network-caching");
298             break;
299 
300         case STREAM_SET_PAUSE_STATE:
301             break;
302 
303         default:
304             return VLC_EGENERIC;
305     }
306     return VLC_SUCCESS;
307 }
308 
309 static char *
NfsGetUrl(vlc_url_t * p_url,const char * psz_file)310 NfsGetUrl(vlc_url_t *p_url, const char *psz_file)
311 {
312     /* nfs://<psz_host><psz_path><psz_file>?<psz_option> */
313     char *psz_url;
314     if (asprintf(&psz_url, "nfs://%s%s%s%s%s%s", p_url->psz_host,
315                  p_url->psz_path != NULL ? p_url->psz_path : "",
316                  p_url->psz_path != NULL && p_url->psz_path[0] != '\0' &&
317                  p_url->psz_path[strlen(p_url->psz_path) - 1] != '/' ? "/" : "",
318                  psz_file,
319                  p_url->psz_option != NULL ? "?" : "",
320                  p_url->psz_option != NULL ? p_url->psz_option : "") == -1)
321         return NULL;
322     else
323         return psz_url;
324 }
325 
326 static int
DirRead(stream_t * p_access,input_item_node_t * p_node)327 DirRead(stream_t *p_access, input_item_node_t *p_node)
328 {
329     access_sys_t *p_sys = p_access->p_sys;
330     struct nfsdirent *p_nfsdirent;
331     int i_ret = VLC_SUCCESS;
332     assert(p_sys->p_nfsdir);
333 
334     struct vlc_readdir_helper rdh;
335     vlc_readdir_helper_init(&rdh, p_access, p_node);
336 
337     while (i_ret == VLC_SUCCESS
338         && (p_nfsdirent = nfs_readdir(p_sys->p_nfs, p_sys->p_nfsdir)) != NULL)
339     {
340         char *psz_name_encoded = vlc_uri_encode(p_nfsdirent->name);
341         if (psz_name_encoded == NULL)
342         {
343             i_ret = VLC_ENOMEM;
344             break;
345         }
346         char *psz_url = NfsGetUrl(&p_sys->encoded_url, psz_name_encoded);
347         free(psz_name_encoded);
348         if (psz_url == NULL)
349         {
350             i_ret = VLC_ENOMEM;
351             break;
352         }
353 
354         int i_type;
355         switch (p_nfsdirent->type)
356         {
357         case NF3REG:
358             i_type = ITEM_TYPE_FILE;
359             break;
360         case NF3DIR:
361             i_type = ITEM_TYPE_DIRECTORY;
362             break;
363         default:
364             i_type = ITEM_TYPE_UNKNOWN;
365         }
366         i_ret = vlc_readdir_helper_additem(&rdh, psz_url, NULL, p_nfsdirent->name,
367                                            i_type, ITEM_NET);
368         free(psz_url);
369     }
370 
371     vlc_readdir_helper_finish(&rdh, i_ret == VLC_SUCCESS);
372 
373     return i_ret;
374 }
375 
376 static int
MountRead(stream_t * p_access,input_item_node_t * p_node)377 MountRead(stream_t *p_access, input_item_node_t *p_node)
378 {
379     access_sys_t *p_sys = p_access->p_sys;
380     assert(p_sys->p_mount != NULL && p_sys->res.exports.i_count >= 0);
381     int i_ret = VLC_SUCCESS;
382 
383     struct vlc_readdir_helper rdh;
384     vlc_readdir_helper_init(&rdh, p_access, p_node);
385 
386     for (int i = 0; i < p_sys->res.exports.i_count && i_ret == VLC_SUCCESS; ++i)
387     {
388         char *psz_name = p_sys->res.exports.ppsz_names[i];
389 
390         char *psz_url = NfsGetUrl(&p_sys->encoded_url, psz_name);
391         if (psz_url == NULL)
392         {
393             i_ret = VLC_ENOMEM;
394             break;
395         }
396         i_ret = vlc_readdir_helper_additem(&rdh, psz_url, NULL, psz_name,
397                                             ITEM_TYPE_DIRECTORY, ITEM_NET);
398         free(psz_url);
399     }
400 
401     vlc_readdir_helper_finish(&rdh, i_ret == VLC_SUCCESS);
402 
403     return i_ret;
404 }
405 
406 static void
nfs_opendir_cb(int i_status,struct nfs_context * p_nfs,void * p_data,void * p_private_data)407 nfs_opendir_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
408                void *p_private_data)
409 {
410     VLC_UNUSED(p_nfs);
411     stream_t *p_access = p_private_data;
412     access_sys_t *p_sys = p_access->p_sys;
413     assert(p_sys->p_nfs == p_nfs);
414     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
415         return;
416 
417     p_sys->p_nfsdir = p_data;
418 }
419 
420 static void
nfs_open_cb(int i_status,struct nfs_context * p_nfs,void * p_data,void * p_private_data)421 nfs_open_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
422             void *p_private_data)
423 {
424     VLC_UNUSED(p_nfs);
425     stream_t *p_access = p_private_data;
426     access_sys_t *p_sys = p_access->p_sys;
427     assert(p_sys->p_nfs == p_nfs);
428     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
429         return;
430 
431     p_sys->p_nfsfh = p_data;
432 }
433 
434 static void
nfs_stat64_cb(int i_status,struct nfs_context * p_nfs,void * p_data,void * p_private_data)435 nfs_stat64_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
436               void *p_private_data)
437 {
438     VLC_UNUSED(p_nfs);
439     stream_t *p_access = p_private_data;
440     access_sys_t *p_sys = p_access->p_sys;
441     assert(p_sys->p_nfs == p_nfs);
442     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
443         return;
444 
445     struct nfs_stat_64 *p_stat = p_data;
446     p_sys->stat = *p_stat;
447 
448     if (p_sys->b_auto_guid)
449     {
450         nfs_set_uid(p_sys->p_nfs, p_sys->stat.nfs_uid);
451         nfs_set_gid(p_sys->p_nfs, p_sys->stat.nfs_gid);
452     }
453 
454     if (S_ISDIR(p_sys->stat.nfs_mode))
455     {
456         msg_Dbg(p_access, "nfs_opendir: '%s'", p_sys->p_nfs_url->file);
457         if (nfs_opendir_async(p_sys->p_nfs, p_sys->p_nfs_url->file,
458                               nfs_opendir_cb, p_access) != 0)
459         {
460             msg_Err(p_access, "nfs_opendir_async failed");
461             p_sys->b_error = true;
462         }
463     }
464     else if (S_ISREG(p_sys->stat.nfs_mode))
465     {
466         msg_Dbg(p_access, "nfs_open: '%s'", p_sys->p_nfs_url->file);
467         if (nfs_open_async(p_sys->p_nfs, p_sys->p_nfs_url->file, O_RDONLY,
468                            nfs_open_cb, p_access) < 0)
469         {
470             msg_Err(p_access, "nfs_open_async failed");
471             p_sys->b_error = true;
472         }
473     }
474     else
475     {
476         msg_Err(p_access, "nfs_stat64_cb: file type not handled");
477         p_sys->b_error = true;
478     }
479 }
480 
481 static void
nfs_mount_cb(int i_status,struct nfs_context * p_nfs,void * p_data,void * p_private_data)482 nfs_mount_cb(int i_status, struct nfs_context *p_nfs, void *p_data,
483              void *p_private_data)
484 {
485     VLC_UNUSED(p_nfs);
486     stream_t *p_access = p_private_data;
487     access_sys_t *p_sys = p_access->p_sys;
488     assert(p_sys->p_nfs == p_nfs);
489     (void) p_data;
490 
491     /* If a directory url doesn't end with '/', there is no way to know which
492      * part of the url is the export point and which part is the path. An
493      * example with "nfs://myhost/mnt/data": we can't know if /mnt or /mnt/data
494      * is the export point. Therefore, in case of EACCES error, retry to mount
495      * the url by adding a '/' to the decoded path. */
496     if (i_status == -EACCES && p_sys->psz_url_decoded_slash == NULL)
497     {
498         vlc_url_t url;
499         vlc_UrlParseFixup(&url, p_access->psz_url);
500         if (url.psz_path == NULL || url.psz_path[0] == '\0'
501          || url.psz_path[strlen(url.psz_path) - 1] == '/'
502          || (p_sys->psz_url_decoded_slash = NfsGetUrl(&url, "/")) == NULL)
503         {
504             vlc_UrlClean(&url);
505             NFS_CHECK_STATUS(p_access, i_status, p_data);
506             return;
507         }
508         else
509         {
510             vlc_UrlClean(&url);
511             msg_Warn(p_access, "trying to mount '%s' again by adding a '/'",
512                      p_access->psz_url);
513             return;
514         }
515     }
516 
517     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
518         return;
519 
520     if (nfs_stat64_async(p_sys->p_nfs, p_sys->p_nfs_url->file, nfs_stat64_cb,
521                          p_access) < 0)
522     {
523         msg_Err(p_access, "nfs_stat64_async failed");
524         p_sys->b_error = true;
525     }
526 }
527 
528 static bool
nfs_mount_open_finished_cb(stream_t * p_access)529 nfs_mount_open_finished_cb(stream_t *p_access)
530 {
531     access_sys_t *p_sys = p_access->p_sys;
532     return p_sys->p_nfsfh != NULL || p_sys->p_nfsdir != NULL
533         || p_sys->psz_url_decoded_slash != NULL;
534 }
535 
536 static bool
nfs_mount_open_slash_finished_cb(stream_t * p_access)537 nfs_mount_open_slash_finished_cb(stream_t *p_access)
538 {
539     access_sys_t *p_sys = p_access->p_sys;
540     return p_sys->p_nfsfh != NULL || p_sys->p_nfsdir != NULL;
541 }
542 
543 static void
mount_export_cb(struct rpc_context * p_ctx,int i_status,void * p_data,void * p_private_data)544 mount_export_cb(struct rpc_context *p_ctx, int i_status, void *p_data,
545                 void *p_private_data)
546 {
547     VLC_UNUSED(p_ctx);
548     stream_t *p_access = p_private_data;
549     access_sys_t *p_sys = p_access->p_sys;
550     assert(p_sys->p_mount == p_ctx);
551     if (NFS_CHECK_STATUS(p_access, i_status, p_data))
552         return;
553 
554     exports p_export = *(exports *)p_data;
555     p_sys->res.exports.i_count = 0;
556 
557     /* Dup the export linked list into an array of const char * */
558     while (p_export != NULL)
559     {
560         p_sys->res.exports.i_count++;
561         p_export = p_export->ex_next;
562     }
563     if (p_sys->res.exports.i_count == 0)
564         return;
565 
566     p_sys->res.exports.ppsz_names = calloc(p_sys->res.exports.i_count,
567                                            sizeof(char *));
568     if (p_sys->res.exports.ppsz_names == NULL)
569     {
570         p_sys->b_error = true;
571         return;
572     }
573 
574     p_export = *(exports *)p_data;
575     unsigned int i_idx = 0;
576     while (p_export != NULL)
577     {
578         p_sys->res.exports.ppsz_names[i_idx] = strdup(p_export->ex_dir);
579         if (p_sys->res.exports.ppsz_names[i_idx] == NULL)
580         {
581             for (unsigned int i = 0; i < i_idx; ++i)
582                 free(p_sys->res.exports.ppsz_names[i]);
583             free(p_sys->res.exports.ppsz_names);
584             p_sys->res.exports.ppsz_names = NULL;
585             p_sys->res.exports.i_count = 0;
586             p_sys->b_error = true;
587             return;
588         }
589         i_idx++;
590         p_export = p_export->ex_next;
591     }
592 }
593 
594 static bool
mount_getexports_finished_cb(stream_t * p_access)595 mount_getexports_finished_cb(stream_t *p_access)
596 {
597     access_sys_t *p_sys = p_access->p_sys;
598     return p_sys->res.exports.i_count != -1;
599 }
600 
601 static int
NfsInit(stream_t * p_access,const char * psz_url_decoded)602 NfsInit(stream_t *p_access, const char *psz_url_decoded)
603 {
604     access_sys_t *p_sys = p_access->p_sys;
605     p_sys->p_nfs = nfs_init_context();
606     if (p_sys->p_nfs == NULL)
607     {
608         msg_Err(p_access, "nfs_init_context failed");
609         return -1;
610     }
611 
612     p_sys->p_nfs_url = nfs_parse_url_incomplete(p_sys->p_nfs, psz_url_decoded);
613     if (p_sys->p_nfs_url == NULL || p_sys->p_nfs_url->server == NULL)
614     {
615         msg_Err(p_access, "nfs_parse_url_incomplete failed: '%s'",
616                 nfs_get_error(p_sys->p_nfs));
617         return -1;
618     }
619     return 0;
620 }
621 
622 static int
Open(vlc_object_t * p_obj)623 Open(vlc_object_t *p_obj)
624 {
625     stream_t *p_access = (stream_t *)p_obj;
626     access_sys_t *p_sys = vlc_obj_calloc(p_obj, 1, sizeof (*p_sys));
627 
628     if (unlikely(p_sys == NULL))
629         return VLC_ENOMEM;
630     p_access->p_sys = p_sys;
631 
632     p_sys->b_auto_guid = var_InheritBool(p_obj, "nfs-auto-guid");
633 
634     /* nfs_* functions need a decoded url */
635     p_sys->psz_url_decoded = vlc_uri_decode_duplicate(p_access->psz_url);
636     if (p_sys->psz_url_decoded == NULL)
637         goto error;
638 
639     /* Parse the encoded URL */
640     if (vlc_UrlParseFixup(&p_sys->encoded_url, p_access->psz_url) != 0)
641         goto error;
642     if (p_sys->encoded_url.psz_option)
643     {
644         if (strstr(p_sys->encoded_url.psz_option, "uid")
645          || strstr(p_sys->encoded_url.psz_option, "gid"))
646             p_sys->b_auto_guid = false;
647     }
648 
649     if (NfsInit(p_access, p_sys->psz_url_decoded) == -1)
650         goto error;
651 
652     if (p_sys->p_nfs_url->path != NULL && p_sys->p_nfs_url->file != NULL)
653     {
654         /* The url has a valid path and file, mount the path and open/opendir
655          * the file */
656         msg_Dbg(p_access, "nfs_mount: server: '%s', path: '%s'",
657                 p_sys->p_nfs_url->server, p_sys->p_nfs_url->path);
658 
659         if (nfs_mount_async(p_sys->p_nfs, p_sys->p_nfs_url->server,
660                             p_sys->p_nfs_url->path, nfs_mount_cb, p_access) < 0)
661         {
662             msg_Err(p_access, "nfs_mount_async failed");
663             goto error;
664         }
665 
666         if (vlc_nfs_mainloop(p_access, nfs_mount_open_finished_cb) < 0)
667             goto error;
668 
669         if (p_sys->psz_url_decoded_slash != NULL)
670         {
671             /* Retry to mount by adding a '/' to the path, see comment in
672              * nfs_mount_cb */
673             nfs_destroy_url(p_sys->p_nfs_url);
674             nfs_destroy_context(p_sys->p_nfs);
675             p_sys->p_nfs_url = NULL;
676             p_sys->p_nfs = NULL;
677 
678             if (NfsInit(p_access, p_sys->psz_url_decoded_slash) == -1
679              || p_sys->p_nfs_url->path == NULL || p_sys->p_nfs_url->file == NULL)
680                 goto error;
681 
682             if (nfs_mount_async(p_sys->p_nfs, p_sys->p_nfs_url->server,
683                                 p_sys->p_nfs_url->path, nfs_mount_cb, p_access) < 0)
684             {
685                 msg_Err(p_access, "nfs_mount_async failed");
686                 goto error;
687             }
688 
689             if (vlc_nfs_mainloop(p_access, nfs_mount_open_slash_finished_cb) < 0)
690                 goto error;
691         }
692 
693         if (p_sys->p_nfsfh != NULL)
694         {
695             p_access->pf_read = FileRead;
696             p_access->pf_seek = FileSeek;
697             p_access->pf_control = FileControl;
698         }
699         else if (p_sys->p_nfsdir != NULL)
700         {
701             p_access->pf_readdir = DirRead;
702             p_access->pf_seek = NULL;
703             p_access->pf_control = access_vaDirectoryControlHelper;
704         }
705         else
706             vlc_assert_unreachable();
707     }
708     else
709     {
710         /* url is just a server: fetch exports point */
711         nfs_destroy_context(p_sys->p_nfs);
712         p_sys->p_nfs = NULL;
713 
714         p_sys->p_mount = rpc_init_context();
715         if (p_sys->p_mount == NULL)
716         {
717             msg_Err(p_access, "rpc_init_context failed");
718             goto error;
719         }
720 
721         p_sys->res.exports.ppsz_names = NULL;
722         p_sys->res.exports.i_count = -1;
723 
724         if (mount_getexports_async(p_sys->p_mount, p_sys->p_nfs_url->server,
725                                    mount_export_cb, p_access) < 0)
726         {
727             msg_Err(p_access, "mount_getexports_async failed");
728             goto error;
729         }
730 
731         if (vlc_mount_mainloop(p_access, mount_getexports_finished_cb) < 0)
732             goto error;
733 
734         p_access->pf_readdir = MountRead;
735         p_access->pf_seek = NULL;
736         p_access->pf_control = access_vaDirectoryControlHelper;
737     }
738 
739     return VLC_SUCCESS;
740 
741 error:
742     Close(p_obj);
743     return VLC_EGENERIC;
744 }
745 
746 static void
Close(vlc_object_t * p_obj)747 Close(vlc_object_t *p_obj)
748 {
749     stream_t *p_access = (stream_t *)p_obj;
750     access_sys_t *p_sys = p_access->p_sys;
751 
752     if (p_sys->p_nfsfh != NULL)
753         nfs_close(p_sys->p_nfs, p_sys->p_nfsfh);
754 
755     if (p_sys->p_nfsdir != NULL)
756         nfs_closedir(p_sys->p_nfs, p_sys->p_nfsdir);
757 
758     if (p_sys->p_nfs != NULL)
759         nfs_destroy_context(p_sys->p_nfs);
760 
761     if (p_sys->p_mount != NULL)
762     {
763         for (int i = 0; i < p_sys->res.exports.i_count; ++i)
764             free(p_sys->res.exports.ppsz_names[i]);
765         free(p_sys->res.exports.ppsz_names);
766         rpc_destroy_context(p_sys->p_mount);
767     }
768 
769     if (p_sys->p_nfs_url != NULL)
770         nfs_destroy_url(p_sys->p_nfs_url);
771 
772     vlc_UrlClean(&p_sys->encoded_url);
773 
774     free(p_sys->psz_url_decoded);
775     free(p_sys->psz_url_decoded_slash);
776 }
777