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