vfs/
symlink.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Server support for symbolic links.
6
7use crate::common::{
8    decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
9    inherit_rights_for_clone, send_on_open_with_error,
10};
11use crate::execution_scope::ExecutionScope;
12use crate::name::parse_name;
13use crate::node::Node;
14use crate::object_request::{run_synchronous_future_or_spawn, ConnectionCreator, Representation};
15use crate::request_handler::{RequestHandler, RequestListener};
16use crate::{ObjectRequest, ObjectRequestRef, ProtocolsExt, ToObjectRequest};
17use fidl::endpoints::{ControlHandle as _, Responder, ServerEnd};
18use fidl_fuchsia_io as fio;
19use std::future::{ready, Future};
20use std::ops::ControlFlow;
21use std::pin::Pin;
22use std::sync::Arc;
23use zx_status::Status;
24
25pub trait Symlink: Node {
26    fn read_target(&self) -> impl Future<Output = Result<Vec<u8>, Status>> + Send;
27
28    // Extended attributes for symlinks.
29    fn list_extended_attributes(
30        &self,
31    ) -> impl Future<Output = Result<Vec<Vec<u8>>, Status>> + Send {
32        ready(Err(Status::NOT_SUPPORTED))
33    }
34    fn get_extended_attribute(
35        &self,
36        _name: Vec<u8>,
37    ) -> impl Future<Output = Result<Vec<u8>, Status>> + Send {
38        ready(Err(Status::NOT_SUPPORTED))
39    }
40    fn set_extended_attribute(
41        &self,
42        _name: Vec<u8>,
43        _value: Vec<u8>,
44        _mode: fio::SetExtendedAttributeMode,
45    ) -> impl Future<Output = Result<(), Status>> + Send {
46        ready(Err(Status::NOT_SUPPORTED))
47    }
48    fn remove_extended_attribute(
49        &self,
50        _name: Vec<u8>,
51    ) -> impl Future<Output = Result<(), Status>> + Send {
52        ready(Err(Status::NOT_SUPPORTED))
53    }
54}
55
56pub struct Connection<T> {
57    scope: ExecutionScope,
58    symlink: Arc<T>,
59}
60
61pub struct SymlinkOptions;
62
63impl<T: Symlink> Connection<T> {
64    /// Creates a new connection to serve the symlink. The symlink will be served from a new async
65    /// `Task`, not from the current `Task`. Errors in constructing the connection are not
66    /// guaranteed to be returned, they may be sent directly to the client end of the connection.
67    /// This method should be called from within an `ObjectRequest` handler to ensure that errors
68    /// are sent to the client end of the connection.
69    pub async fn create(
70        scope: ExecutionScope,
71        symlink: Arc<T>,
72        protocols: impl ProtocolsExt,
73        object_request: ObjectRequestRef<'_>,
74    ) -> Result<(), Status> {
75        let _options = protocols.to_symlink_options()?;
76        let connection = Self { scope: scope.clone(), symlink };
77        if let Ok(requests) = object_request.take().into_request_stream(&connection).await {
78            scope.spawn(RequestListener::new(requests, connection));
79        }
80        Ok(())
81    }
82
83    /// Similar to `create` but optimized for symlinks whose implementation is synchronous and
84    /// creating the connection is being done from a non-async context.
85    pub fn create_sync(
86        scope: ExecutionScope,
87        symlink: Arc<T>,
88        options: impl ProtocolsExt,
89        object_request: ObjectRequest,
90    ) {
91        run_synchronous_future_or_spawn(
92            scope.clone(),
93            object_request.handle_async(async |object_request| {
94                Self::create(scope, symlink, options, object_request).await
95            }),
96        )
97    }
98
99    // Returns true if the connection should terminate.
100    async fn handle_request(&mut self, req: fio::SymlinkRequest) -> Result<bool, fidl::Error> {
101        match req {
102            #[cfg(fuchsia_api_level_at_least = "26")]
103            fio::SymlinkRequest::DeprecatedClone { flags, object, control_handle: _ } => {
104                self.handle_deprecated_clone(flags, object).await;
105            }
106            #[cfg(not(fuchsia_api_level_at_least = "26"))]
107            fio::SymlinkRequest::Clone { flags, object, control_handle: _ } => {
108                self.handle_deprecated_clone(flags, object).await;
109            }
110            #[cfg(fuchsia_api_level_at_least = "26")]
111            fio::SymlinkRequest::Clone { request, control_handle: _ } => {
112                self.handle_clone(ServerEnd::new(request.into_channel())).await;
113            }
114            #[cfg(not(fuchsia_api_level_at_least = "26"))]
115            fio::SymlinkRequest::Clone2 { request, control_handle: _ } => {
116                self.handle_clone(ServerEnd::new(request.into_channel())).await;
117            }
118            fio::SymlinkRequest::Close { responder } => {
119                responder.send(Ok(()))?;
120                return Ok(true);
121            }
122            fio::SymlinkRequest::LinkInto { dst_parent_token, dst, responder } => {
123                responder.send(
124                    self.handle_link_into(dst_parent_token, dst).await.map_err(|s| s.into_raw()),
125                )?;
126            }
127            fio::SymlinkRequest::GetConnectionInfo { responder } => {
128                // TODO(https://fxbug.dev/293947862): Restrict GET_ATTRIBUTES.
129                let rights = fio::Operations::GET_ATTRIBUTES;
130                responder
131                    .send(fio::ConnectionInfo { rights: Some(rights), ..Default::default() })?;
132            }
133            fio::SymlinkRequest::Sync { responder } => {
134                responder.send(Ok(()))?;
135            }
136            fio::SymlinkRequest::GetAttr { responder } => {
137                // TODO(https://fxbug.dev/293947862): Restrict GET_ATTRIBUTES.
138                let (status, attrs) = crate::common::io2_to_io1_attrs(
139                    self.symlink.as_ref(),
140                    fio::Rights::GET_ATTRIBUTES,
141                )
142                .await;
143                responder.send(status.into_raw(), &attrs)?;
144            }
145            fio::SymlinkRequest::SetAttr { responder, .. } => {
146                responder.send(Status::ACCESS_DENIED.into_raw())?;
147            }
148            fio::SymlinkRequest::GetAttributes { query, responder } => {
149                // TODO(https://fxbug.dev/293947862): Restrict GET_ATTRIBUTES.
150                let attrs = self.symlink.get_attributes(query).await;
151                responder.send(
152                    attrs
153                        .as_ref()
154                        .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
155                        .map_err(|status| status.into_raw()),
156                )?;
157            }
158            fio::SymlinkRequest::UpdateAttributes { payload: _, responder } => {
159                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
160            }
161            fio::SymlinkRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
162                self.handle_list_extended_attribute(iterator).await;
163            }
164            fio::SymlinkRequest::GetExtendedAttribute { responder, name } => {
165                let res = self.handle_get_extended_attribute(name).await.map_err(|s| s.into_raw());
166                responder.send(res)?;
167            }
168            fio::SymlinkRequest::SetExtendedAttribute { responder, name, value, mode } => {
169                let res = self
170                    .handle_set_extended_attribute(name, value, mode)
171                    .await
172                    .map_err(|s| s.into_raw());
173                responder.send(res)?;
174            }
175            fio::SymlinkRequest::RemoveExtendedAttribute { responder, name } => {
176                let res =
177                    self.handle_remove_extended_attribute(name).await.map_err(|s| s.into_raw());
178                responder.send(res)?;
179            }
180            fio::SymlinkRequest::Describe { responder } => match self.symlink.read_target().await {
181                Ok(target) => responder
182                    .send(&fio::SymlinkInfo { target: Some(target), ..Default::default() })?,
183                Err(status) => {
184                    responder.control_handle().shutdown_with_epitaph(status);
185                    return Ok(true);
186                }
187            },
188            #[cfg(fuchsia_api_level_at_least = "NEXT")]
189            fio::SymlinkRequest::GetFlags { responder } => {
190                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
191            }
192            #[cfg(fuchsia_api_level_at_least = "NEXT")]
193            fio::SymlinkRequest::SetFlags { flags: _, responder } => {
194                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
195            }
196            #[cfg(fuchsia_api_level_at_least = "NEXT")]
197            fio::SymlinkRequest::DeprecatedGetFlags { responder } => {
198                responder.send(Status::NOT_SUPPORTED.into_raw(), fio::OpenFlags::empty())?;
199            }
200            #[cfg(fuchsia_api_level_at_least = "NEXT")]
201            fio::SymlinkRequest::DeprecatedSetFlags { responder, .. } => {
202                responder.send(Status::ACCESS_DENIED.into_raw())?;
203            }
204            #[cfg(not(fuchsia_api_level_at_least = "NEXT"))]
205            fio::SymlinkRequest::GetFlags { responder } => {
206                responder.send(Status::NOT_SUPPORTED.into_raw(), fio::OpenFlags::empty())?;
207            }
208            #[cfg(not(fuchsia_api_level_at_least = "NEXT"))]
209            fio::SymlinkRequest::SetFlags { responder, .. } => {
210                responder.send(Status::ACCESS_DENIED.into_raw())?;
211            }
212            fio::SymlinkRequest::Query { responder } => {
213                responder.send(fio::SYMLINK_PROTOCOL_NAME.as_bytes())?;
214            }
215            fio::SymlinkRequest::QueryFilesystem { responder } => {
216                match self.symlink.query_filesystem() {
217                    Err(status) => responder.send(status.into_raw(), None)?,
218                    Ok(info) => responder.send(0, Some(&info))?,
219                }
220            }
221            fio::SymlinkRequest::_UnknownMethod { ordinal: _ordinal, .. } => {
222                #[cfg(any(test, feature = "use_log"))]
223                log::warn!(_ordinal; "Received unknown method")
224            }
225        }
226        Ok(false)
227    }
228
229    async fn handle_deprecated_clone(
230        &mut self,
231        flags: fio::OpenFlags,
232        server_end: ServerEnd<fio::NodeMarker>,
233    ) {
234        let flags = match inherit_rights_for_clone(fio::OpenFlags::RIGHT_READABLE, flags) {
235            Ok(updated) => updated,
236            Err(status) => {
237                send_on_open_with_error(
238                    flags.contains(fio::OpenFlags::DESCRIBE),
239                    server_end,
240                    status,
241                );
242                return;
243            }
244        };
245        flags
246            .to_object_request(server_end)
247            .handle_async(async |object_request| {
248                Self::create(self.scope.clone(), self.symlink.clone(), flags, object_request).await
249            })
250            .await;
251    }
252
253    async fn handle_clone(&mut self, server_end: ServerEnd<fio::SymlinkMarker>) {
254        let flags = fio::Flags::PROTOCOL_SYMLINK | fio::Flags::PERM_GET_ATTRIBUTES;
255        flags
256            .to_object_request(server_end)
257            .handle_async(async |object_request| {
258                Self::create(self.scope.clone(), self.symlink.clone(), flags, object_request).await
259            })
260            .await;
261    }
262
263    async fn handle_link_into(
264        &mut self,
265        target_parent_token: fidl::Event,
266        target_name: String,
267    ) -> Result<(), Status> {
268        let target_name = parse_name(target_name).map_err(|_| Status::INVALID_ARGS)?;
269
270        let target_parent = self
271            .scope
272            .token_registry()
273            .get_owner(target_parent_token.into())?
274            .ok_or(Err(Status::NOT_FOUND))?;
275
276        self.symlink.clone().link_into(target_parent, target_name).await
277    }
278
279    async fn handle_list_extended_attribute(
280        &self,
281        iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
282    ) {
283        let attributes = match self.symlink.list_extended_attributes().await {
284            Ok(attributes) => attributes,
285            Err(status) => {
286                #[cfg(any(test, feature = "use_log"))]
287                log::error!(status:?; "list extended attributes failed");
288                #[allow(clippy::unnecessary_lazy_evaluations)]
289                iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
290                    #[cfg(any(test, feature = "use_log"))]
291                    log::error!(_error:?; "failed to send epitaph")
292                });
293                return;
294            }
295        };
296        self.scope.spawn(extended_attributes_sender(iterator, attributes));
297    }
298
299    async fn handle_get_extended_attribute(
300        &self,
301        name: Vec<u8>,
302    ) -> Result<fio::ExtendedAttributeValue, Status> {
303        let value = self.symlink.get_extended_attribute(name).await?;
304        encode_extended_attribute_value(value)
305    }
306
307    async fn handle_set_extended_attribute(
308        &self,
309        name: Vec<u8>,
310        value: fio::ExtendedAttributeValue,
311        mode: fio::SetExtendedAttributeMode,
312    ) -> Result<(), Status> {
313        if name.contains(&0) {
314            return Err(Status::INVALID_ARGS);
315        }
316        let val = decode_extended_attribute_value(value)?;
317        self.symlink.set_extended_attribute(name, val, mode).await
318    }
319
320    async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
321        self.symlink.remove_extended_attribute(name).await
322    }
323}
324
325impl<T: Symlink> RequestHandler for Connection<T> {
326    type Request = Result<fio::SymlinkRequest, fidl::Error>;
327
328    async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
329        let this = self.get_mut();
330        let _guard = this.scope.active_guard();
331        match request {
332            Ok(request) => match this.handle_request(request).await {
333                Ok(false) => ControlFlow::Continue(()),
334                Ok(true) | Err(_) => ControlFlow::Break(()),
335            },
336            Err(_) => ControlFlow::Break(()),
337        }
338    }
339}
340
341impl<T: Symlink> Representation for Connection<T> {
342    type Protocol = fio::SymlinkMarker;
343
344    async fn get_representation(
345        &self,
346        requested_attributes: fio::NodeAttributesQuery,
347    ) -> Result<fio::Representation, Status> {
348        Ok(fio::Representation::Symlink(fio::SymlinkInfo {
349            attributes: if requested_attributes.is_empty() {
350                None
351            } else {
352                Some(self.symlink.get_attributes(requested_attributes).await?)
353            },
354            target: Some(self.symlink.read_target().await?),
355            ..Default::default()
356        }))
357    }
358
359    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
360        Ok(fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject {
361            target: self.symlink.read_target().await?,
362        }))
363    }
364}
365
366impl<T: Symlink> ConnectionCreator<T> for Connection<T> {
367    async fn create<'a>(
368        scope: ExecutionScope,
369        node: Arc<T>,
370        protocols: impl ProtocolsExt,
371        object_request: ObjectRequestRef<'a>,
372    ) -> Result<(), Status> {
373        Self::create(scope, node, protocols, object_request).await
374    }
375}
376
377/// Helper to open a symlink or node as required.
378pub fn serve(
379    link: Arc<impl Symlink>,
380    scope: ExecutionScope,
381    protocols: impl ProtocolsExt,
382    object_request: ObjectRequestRef<'_>,
383) -> Result<(), Status> {
384    if protocols.is_node() {
385        let options = protocols.to_node_options(link.entry_info().type_())?;
386        link.open_as_node(scope, options, object_request)
387    } else {
388        Connection::create_sync(scope, link, protocols, object_request.take());
389        Ok(())
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::{Connection, Symlink};
396    use crate::common::rights_to_posix_mode_bits;
397    use crate::directory::entry::{EntryInfo, GetEntryInfo};
398    use crate::execution_scope::ExecutionScope;
399    use crate::node::Node;
400    use crate::{immutable_attributes, ToObjectRequest};
401    use assert_matches::assert_matches;
402    use fidl::endpoints::{create_proxy, ServerEnd};
403    use fidl_fuchsia_io as fio;
404    use fuchsia_sync::Mutex;
405    use futures::StreamExt;
406    use std::collections::HashMap;
407    use std::sync::Arc;
408    use zx_status::Status;
409
410    const TARGET: &[u8] = b"target";
411
412    struct TestSymlink {
413        xattrs: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
414    }
415
416    impl TestSymlink {
417        fn new() -> Self {
418            TestSymlink { xattrs: Mutex::new(HashMap::new()) }
419        }
420    }
421
422    impl Symlink for TestSymlink {
423        async fn read_target(&self) -> Result<Vec<u8>, Status> {
424            Ok(TARGET.to_vec())
425        }
426        async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, Status> {
427            let map = self.xattrs.lock();
428            Ok(map.values().map(|x| x.clone()).collect())
429        }
430        async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, Status> {
431            let map = self.xattrs.lock();
432            map.get(&name).map(|x| x.clone()).ok_or(Status::NOT_FOUND)
433        }
434        async fn set_extended_attribute(
435            &self,
436            name: Vec<u8>,
437            value: Vec<u8>,
438            _mode: fio::SetExtendedAttributeMode,
439        ) -> Result<(), Status> {
440            let mut map = self.xattrs.lock();
441            // Don't bother replicating the mode behavior, we just care that this method is hooked
442            // up at all.
443            map.insert(name, value);
444            Ok(())
445        }
446        async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
447            let mut map = self.xattrs.lock();
448            map.remove(&name);
449            Ok(())
450        }
451    }
452
453    impl Node for TestSymlink {
454        async fn get_attributes(
455            &self,
456            requested_attributes: fio::NodeAttributesQuery,
457        ) -> Result<fio::NodeAttributes2, Status> {
458            Ok(immutable_attributes!(
459                requested_attributes,
460                Immutable {
461                    content_size: TARGET.len() as u64,
462                    storage_size: TARGET.len() as u64,
463                    protocols: fio::NodeProtocolKinds::SYMLINK,
464                    abilities: fio::Abilities::GET_ATTRIBUTES,
465                }
466            ))
467        }
468    }
469
470    impl GetEntryInfo for TestSymlink {
471        fn entry_info(&self) -> EntryInfo {
472            EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Symlink)
473        }
474    }
475
476    async fn serve_test_symlink() -> fio::SymlinkProxy {
477        let (client_end, server_end) = create_proxy::<fio::SymlinkMarker>();
478        let flags = fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK;
479
480        Connection::create_sync(
481            ExecutionScope::new(),
482            Arc::new(TestSymlink::new()),
483            flags,
484            flags.to_object_request(server_end),
485        );
486
487        client_end
488    }
489
490    #[fuchsia::test]
491    async fn test_read_target() {
492        let client_end = serve_test_symlink().await;
493
494        assert_eq!(
495            client_end.describe().await.expect("fidl failed").target.expect("missing target"),
496            b"target"
497        );
498    }
499
500    #[fuchsia::test]
501    async fn test_validate_flags() {
502        let scope = ExecutionScope::new();
503
504        let check = |mut flags: fio::OpenFlags| {
505            let (client_end, server_end) = create_proxy::<fio::SymlinkMarker>();
506            flags |= fio::OpenFlags::DESCRIBE;
507            flags.to_object_request(server_end).create_connection_sync::<Connection<_>, _>(
508                scope.clone(),
509                Arc::new(TestSymlink::new()),
510                flags,
511            );
512
513            async move {
514                Status::from_raw(
515                    client_end
516                        .take_event_stream()
517                        .next()
518                        .await
519                        .expect("no event")
520                        .expect("next failed")
521                        .into_on_open_()
522                        .expect("expected OnOpen")
523                        .0,
524                )
525            }
526        };
527
528        for flags in [
529            fio::OpenFlags::RIGHT_WRITABLE,
530            fio::OpenFlags::RIGHT_EXECUTABLE,
531            fio::OpenFlags::CREATE,
532            fio::OpenFlags::CREATE_IF_ABSENT,
533            fio::OpenFlags::TRUNCATE,
534            fio::OpenFlags::APPEND,
535            fio::OpenFlags::POSIX_WRITABLE,
536            fio::OpenFlags::POSIX_EXECUTABLE,
537            fio::OpenFlags::CLONE_SAME_RIGHTS,
538            fio::OpenFlags::BLOCK_DEVICE,
539        ] {
540            assert_eq!(check(flags).await, Status::INVALID_ARGS, "{flags:?}");
541        }
542
543        assert_eq!(
544            check(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY).await,
545            Status::OK
546        );
547    }
548
549    #[fuchsia::test]
550    async fn test_get_attr() {
551        let client_end = serve_test_symlink().await;
552
553        assert_matches!(
554            client_end.get_attr().await.expect("fidl failed"),
555            (
556                0,
557                fio::NodeAttributes {
558                    mode,
559                    id: fio::INO_UNKNOWN,
560                    content_size: 6,
561                    storage_size: 6,
562                    link_count: 1,
563                    creation_time: 0,
564                    modification_time: 0,
565                }
566            ) if mode == fio::MODE_TYPE_SYMLINK
567                | rights_to_posix_mode_bits(/*r*/ true, /*w*/ false, /*x*/ false)
568        );
569    }
570
571    #[fuchsia::test]
572    async fn test_clone() {
573        let client_end = serve_test_symlink().await;
574
575        let orig_attrs = client_end
576            .get_attributes(fio::NodeAttributesQuery::all())
577            .await
578            .expect("fidl failed")
579            .unwrap();
580        // Clone the original connection and query it's attributes, which should match the original.
581        let (cloned_client, cloned_server) = create_proxy::<fio::SymlinkMarker>();
582        client_end.clone(ServerEnd::new(cloned_server.into_channel())).unwrap();
583        let cloned_attrs = cloned_client
584            .get_attributes(fio::NodeAttributesQuery::all())
585            .await
586            .expect("fidl failed")
587            .unwrap();
588        assert_eq!(orig_attrs, cloned_attrs);
589    }
590
591    #[fuchsia::test]
592    async fn test_describe() {
593        let client_end = serve_test_symlink().await;
594
595        assert_matches!(
596            client_end.describe().await.expect("fidl failed"),
597            fio::SymlinkInfo {
598                target: Some(target),
599                ..
600            } if target == b"target"
601        );
602    }
603
604    #[fuchsia::test]
605    async fn test_xattrs() {
606        let client_end = serve_test_symlink().await;
607
608        client_end
609            .set_extended_attribute(
610                b"foo",
611                fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
612                fio::SetExtendedAttributeMode::Set,
613            )
614            .await
615            .unwrap()
616            .unwrap();
617        assert_eq!(
618            client_end.get_extended_attribute(b"foo").await.unwrap().unwrap(),
619            fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
620        );
621        let (iterator_client_end, iterator_server_end) =
622            create_proxy::<fio::ExtendedAttributeIteratorMarker>();
623        client_end.list_extended_attributes(iterator_server_end).unwrap();
624        assert_eq!(
625            iterator_client_end.get_next().await.unwrap().unwrap(),
626            (vec![b"bar".to_vec()], true)
627        );
628        client_end.remove_extended_attribute(b"foo").await.unwrap().unwrap();
629        assert_eq!(
630            client_end.get_extended_attribute(b"foo").await.unwrap().unwrap_err(),
631            Status::NOT_FOUND.into_raw(),
632        );
633    }
634}