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