1
//! Types and functions to configure a Tor Relay.
2

            
3
// TODO: It would be nice to remove the builder aspect of these config objects, as we don't need
4
// them for arti-relay. But I don't think we can do so while still using tor-config. See:
5
// https://gitlab.torproject.org/tpo/core/arti/-/issues/2253
6

            
7
mod dns;
8
mod listen;
9
mod relay;
10

            
11
use std::borrow::Cow;
12
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
13
use std::path::PathBuf;
14

            
15
use derive_deftly::Deftly;
16
use derive_more::AsRef;
17
use directories::ProjectDirs;
18
use fs_mistrust::{Mistrust, MistrustBuilder};
19
use serde::{Deserialize, Serialize};
20
use std::sync::LazyLock;
21
use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
22
use tor_circmgr::{CircuitTiming, PathConfig, PreemptiveCircuitConfig};
23
use tor_config::derive::prelude::*;
24
use tor_config::{
25
    ConfigBuildError, ExplicitOrAuto, extend_builder::extend_with_replace, mistrust::BuilderExt,
26
};
27
use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
28
use tor_config_shared::metrics::{MetricsConfig, MetricsConfigBuilder};
29
use tor_dircommon::config::{NetworkConfig, NetworkConfigBuilder};
30
use tor_dircommon::fallback::FallbackList;
31
use tor_guardmgr::bridge::BridgeConfig;
32
use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder, VanguardMode};
33
use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
34
use tor_netdoc::types::{ContactInfo, Nickname};
35
use tracing::metadata::Level;
36
use tracing_subscriber::filter::EnvFilter;
37

            
38
use crate::util::NonEmptyList;
39

            
40
pub(crate) use dns::{DnsConfig, DnsConfigBuilder};
41
use listen::Listen;
42

            
43
/// Paths used for default configuration files.
44
1
pub(crate) fn default_config_paths() -> Result<Vec<PathBuf>, CfgPathError> {
45
    // the base path resolver includes the 'ARTI_RELAY_CONFIG' variable
46
1
    let resolver = base_resolver();
47
1
    [
48
1
        "${ARTI_RELAY_CONFIG}/arti-relay.toml",
49
1
        "${ARTI_RELAY_CONFIG}/arti-relay.d/",
50
1
    ]
51
1
    .into_iter()
52
2
    .map(|f| CfgPath::new(f.into()).path(&resolver))
53
1
    .collect()
54
1
}
55

            
56
/// A [`CfgPathResolver`] with the base variables configured for a Tor relay.
57
///
58
/// A relay should have a single `CfgPathResolver` that is passed around where needed to ensure that
59
/// all parts of the relay are resolving paths consistently using the same variables.
60
/// If you need to resolve a path,
61
/// you likely want a reference to the existing resolver,
62
/// and not to create a new one here.
63
///
64
/// The supported variables are:
65
///   - `ARTI_RELAY_CACHE`:
66
///     An arti-specific cache directory.
67
///   - `ARTI_RELAY_CONFIG`:
68
///     An arti-specific configuration directory.
69
///   - `ARTI_RELAY_LOCAL_DATA`:
70
///     An arti-specific directory in the user's "local data" space.
71
///   - `PROGRAM_DIR`:
72
///     The directory of the currently executing binary.
73
///     See documentation for [`std::env::current_exe`] for security notes.
74
///   - `USER_HOME`:
75
///     The user's home directory.
76
///
77
/// These variables are implemented using the [`directories`] crate,
78
/// and so should use appropriate system-specific overrides under the hood.
79
/// (Some of those overrides are based on environment variables.)
80
/// For more information, see that crate's documentation.
81
//
82
// NOTE: We intentionally don't expose an `ARTI_RELAY_SHARED_DATA`
83
// (analogous to `ARTI_SHARED_DATA` in arti).
84
// This is almost certainly never intended over `ARTI_RELAY_LOCAL_DATA`,
85
// so by removing it we don't need to worry about bugs from mixing them up.
86
// We can introduce it later if really needed.
87
2
pub(crate) fn base_resolver() -> CfgPathResolver {
88
2
    let arti_relay_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
89
2
    let arti_relay_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
90
2
    let arti_relay_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
91
2
    let program_dir = get_program_dir().map(Cow::Owned);
92
2
    let user_home = tor_config_path::home().map(Cow::Borrowed);
93

            
94
2
    let mut resolver = CfgPathResolver::default();
95

            
96
2
    resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
97
2
    resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
98
2
    resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
99
2
    resolver.set_var("PROGRAM_DIR", program_dir);
100
2
    resolver.set_var("USER_HOME", user_home);
101

            
102
2
    resolver
103
2
}
104

            
105
/// The directory holding the currently executing program.
106
3
fn get_program_dir() -> Result<PathBuf, CfgPathError> {
107
3
    let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
108
3
    let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
109
3
    Ok(directory.to_owned())
110
3
}
111

            
112
/// A `ProjectDirs` object for Arti relays.
113
7
fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
114
    /// lazy lock holding the ProjectDirs object.
115
    static PROJECT_DIRS: LazyLock<Option<ProjectDirs>> =
116
1
        LazyLock::new(|| ProjectDirs::from("org", "torproject", "Arti-Relay"));
117

            
118
7
    PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
119
7
}
120

            
121
/// A configuration used by a TorRelay.
122
///
123
/// This is a builder so that it works with tor-config.
124
/// We don't expect to ever use it as a builder since we don't provide this as a public rust API.
125
#[derive(Clone, Deftly, Debug, Eq, PartialEq, AsRef)]
126
#[derive_deftly(TorConfig)]
127
#[deftly(tor_config(no_default_trait))]
128
#[non_exhaustive]
129
pub(crate) struct TorRelayConfig {
130
    /// Configuration for the "relay" part of the relay.
131
    // TODO: Add a better doc comment here once we figure out exactly how we want the config to be
132
    // structured.
133
    #[deftly(tor_config(sub_builder))]
134
    pub(crate) relay: RelayConfig,
135

            
136
    /// Information about the Tor network we want to connect to.
137
    #[deftly(tor_config(sub_builder))]
138
    pub(crate) tor_network: NetworkConfig,
139

            
140
    /// Logging configuration
141
    #[deftly(tor_config(sub_builder))]
142
    pub(crate) logging: LoggingConfig,
143

            
144
    /// Metrics configuration
145
    #[deftly(tor_config(sub_builder))]
146
    pub(crate) metrics: MetricsConfig,
147

            
148
    /// Directories for storing information on disk
149
    #[deftly(tor_config(sub_builder))]
150
    pub(crate) storage: StorageConfig,
151

            
152
    /// Information about how to build paths through the network.
153
    #[deftly(tor_config(sub_builder))]
154
    pub(crate) channel: ChannelConfig,
155

            
156
    /// Configuration for system resources
157
    #[deftly(tor_config(sub_builder))]
158
    pub(crate) system: SystemConfig,
159

            
160
    /// Information about how to build paths through the network.
161
    // We don't expose this field in the config.
162
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
163
    // Needed to implement `CircMgrConfig`.
164
    #[as_ref]
165
    pub(crate) path_rules: PathConfig,
166

            
167
    /// Information about vanguards.
168
    // We don't expose this field in the config.
169
    #[deftly(tor_config(
170
        skip,
171
        build = r#"|_|
172
        VanguardConfigBuilder::default()
173
            .mode(ExplicitOrAuto::Explicit(VanguardMode::Disabled))
174
            .build()
175
            .expect("Could not build a disabled `VanguardConfig`")"#
176
    ))]
177
    // Needed to implement `CircMgrConfig`.
178
    #[as_ref]
179
    pub(crate) vanguards: VanguardConfig,
180

            
181
    /// Information about how to retry and expire circuits and request for circuits.
182
    // We don't expose this field in the config.
183
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
184
    // Needed to implement `CircMgrConfig`.
185
    #[as_ref]
186
    pub(crate) circuit_timing: CircuitTiming,
187

            
188
    /// Information about preemptive circuits.
189
    // We don't expose this field in the config.
190
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
191
    // Needed to implement `CircMgrConfig`.
192
    #[as_ref]
193
    pub(crate) preemptive_circuits: PreemptiveCircuitConfig,
194
}
195

            
196
impl tor_config::load::TopLevel for TorRelayConfig {
197
    type Builder = TorRelayConfigBuilder;
198
}
199

            
200
impl tor_circmgr::CircMgrConfig for TorRelayConfig {}
201

            
202
// Needed to implement `GuardMgrConfig`.
203
impl AsRef<FallbackList> for TorRelayConfig {
204
    fn as_ref(&self) -> &FallbackList {
205
        self.tor_network.fallback_caches()
206
    }
207
}
208

            
209
// Needed to implement `GuardMgrConfig`.
210
impl AsRef<[BridgeConfig]> for TorRelayConfig {
211
    fn as_ref(&self) -> &[BridgeConfig] {
212
        // Relays don't use bridges.
213
        &[]
214
    }
215
}
216

            
217
impl tor_guardmgr::GuardMgrConfig for TorRelayConfig {
218
    fn bridges_enabled(&self) -> bool {
219
        // Relays don't use bridges.
220
        false
221
    }
222
}
223

            
224
/// Configuration for the "relay" part of the relay.
225
///
226
/// TODO: I'm not really sure what to call this yet. I'm expecting that we'll rename and reorganize
227
/// things as we add more options. But we should come back to this and update the name and/or doc
228
/// comment.
229
///
230
/// TODO: There's a high-level issue for discussing these options:
231
/// <https://gitlab.torproject.org/tpo/core/arti/-/issues/2252>
232
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
233
#[derive_deftly(TorConfig)]
234
#[deftly(tor_config(no_default_trait))]
235
pub(crate) struct RelayConfig {
236
    /// The nickname of this relay.
237
    ///
238
    /// Nicknames are a legacy (and fun!) mechanism that is occasionally useful for
239
    /// debugging. They should never be used to uniquely identify a relay. Nothing
240
    /// prevents two relays from having the same nickname.
241
    ///
242
    /// It must be between 1 and 19 ASCII alphanumeric characters inclusive. Default
243
    /// value is `Unnamed`.
244
    #[deftly(tor_config(default = "relay::default_nickname()"))]
245
    pub(crate) nickname: Nickname,
246

            
247
    /// Contact information for the operator(s) of this relay.
248
    ///
249
    /// This is published in the descriptor so that the network health team can reach you
250
    /// if there is a problem with the relay. It is free-form text but it must be a
251
    /// single line and must not start with whitespace.
252
    ///
253
    /// If unset, the descriptor is published without a contact information.
254
    //
255
    // TODO(relay): Before stable, we need to settle with Network Health team on the
256
    // format of that contact info. It will still be a free-form string in the descriptor
257
    // but we'll likely enforce a format at the config level.
258
    #[deftly(tor_config(default))]
259
    pub(crate) contact: Option<ContactInfo>,
260

            
261
    /// Addresses to listen on for incoming OR connections.
262
    #[deftly(tor_config(no_default))]
263
    pub(crate) listen: Listen,
264

            
265
    /// The DNS configuration.
266
    #[deftly(tor_config(sub_builder))]
267
    pub(crate) dns: DnsConfig,
268

            
269
    /// Addresses to advertise on the network for receiving OR connections.
270
    // For now, we've decided that we don't want to include any IP address auto-detection in
271
    // arti-relay, so we require users to provide the addresses to advertise. (So no `Option` and
272
    // `builder(default)` here).
273
    #[deftly(tor_config(no_default))]
274
    pub(crate) advertise: Advertise,
275
}
276

            
277
/// The address(es) to advertise on the network.
278
// TODO: We'll want to make sure we check that the addresses are valid before uploading them in a
279
// server descriptor (for example no `INADDR_ANY`, multicast, etc). We can't do that validation here
280
// during parsing, since we don't know exactly which addresses are valid or not. For example we
281
// don't know if local addresses are allowed as we don't know here whether the user plans to run a
282
// testing tor network. We also don't want to do the validation too late (for example when uploading
283
// the server descriptor) as it's better to validate at startup. A better place might be to perform
284
// the validation in the `RelayConfig` builder validate.
285
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
286
pub(crate) struct Advertise {
287
    /// All relays must advertise an IPv4 address.
288
    ipv4: NonEmptyList<SocketAddrV4>,
289
    /// Relays may optionally advertise an IPv6 address.
290
    ipv6: Vec<SocketAddrV6>,
291
}
292

            
293
impl Advertise {
294
    /// Return all addresses (both IPv4 and IPv6) as in IP + Port ([`SocketAddr`]).
295
    pub(crate) fn all_addr(&self) -> Vec<SocketAddr> {
296
        self.ipv4
297
            .iter()
298
            .map(|s| (*s).into())
299
            .chain(self.ipv6.iter().map(|s| (*s).into()))
300
            .collect()
301
    }
302
}
303

            
304
/// Default log level.
305
pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
306

            
307
/// Logging configuration options.
308
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
309
#[derive_deftly(TorConfig)]
310
#[deftly(tor_config(pre_build = "Self::validate"))]
311
#[non_exhaustive]
312
pub(crate) struct LoggingConfig {
313
    /// Filtering directives that determine tracing levels as described at
314
    /// <https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html#impl-FromStr-for-Targets>
315
    ///
316
    /// You can override this setting with the `-l`, `--log-level` command line parameter.
317
    ///
318
    /// Example: "info,tor_proto::channel=trace"
319
    #[deftly(tor_config(default = "DEFAULT_LOG_LEVEL.to_string()"))]
320
    pub(crate) console: String,
321

            
322
    /// If set to false, we avoid logging sensitive information at level `info` or higher.
323
    /// This `info` level distinction is not enforced through any technical means,
324
    /// but is according to our `doc/dev/Safelogging.md` guidelines.
325
    ///
326
    /// If set to true, we disable safe logging on all logs,
327
    /// and store potentially sensitive information at all log levels.
328
    ///
329
    /// This can be useful for debugging, but it increases the value of your logs to an attacker.
330
    /// Do not turn this on in production unless you have a good log rotation mechanism.
331
    #[deftly(tor_config(default))]
332
    pub(crate) log_sensitive_information: bool,
333

            
334
    /// Configuration for logging spans with OpenTelemetry.
335
    #[deftly(tor_config(
336
        sub_builder,
337
        cfg = r#" feature = "opentelemetry" "#,
338
        cfg_desc = "with opentelemetry support"
339
    ))]
340
    opentelemetry: tor_config_shared::opentelemetry::OpentelemetryConfig,
341
}
342

            
343
impl LoggingConfigBuilder {
344
    /// Validate the options provided to the builder.
345
2
    fn validate(&self) -> Result<(), ConfigBuildError> {
346
2
        if let Some(console) = &self.console {
347
            EnvFilter::builder()
348
                .parse(console)
349
                .map_err(|e| ConfigBuildError::Invalid {
350
                    field: "console".to_string(),
351
                    problem: e.to_string(),
352
                })?;
353
2
        }
354
2
        Ok(())
355
2
    }
356
}
357

            
358
/// Configuration for where information should be stored on disk.
359
///
360
/// By default, cache information will be stored in `${ARTI_RELAY_CACHE}`, and
361
/// persistent state will be stored in `${ARTI_RELAY_LOCAL_DATA}`. That means that
362
/// _all_ programs using these defaults will share their cache and state data.
363
/// If that isn't what you want, you'll need to override these directories.
364
///
365
/// On unix, the default directories will typically expand to `~/.cache/arti`
366
/// and `~/.local/share/arti/` respectively, depending on the user's
367
/// environment. Other platforms will also use suitable defaults. For more
368
/// information, see the documentation for [`CfgPath`].
369
///
370
/// This section is for read/write storage.
371
///
372
/// You cannot change this section on a running relay.
373
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
374
#[derive_deftly(TorConfig)]
375
#[non_exhaustive]
376
pub(crate) struct StorageConfig {
377
    /// Location on disk for cached information.
378
    ///
379
    /// This follows the rules for `/var/cache`: "sufficiently old" filesystem objects
380
    /// in it may be deleted outside of the control of Arti,
381
    /// and Arti will continue to function properly.
382
    /// It is also fine to delete the directory as a whole, while Arti is not running.
383
    ///
384
    /// Should be accessed through the `cache_dir()` getter to provide better error messages when
385
    /// resolving the path.
386
    //
387
    // Usage note, for implementations of Arti components:
388
    //
389
    // When files in this directory are to be used by a component, the cache_dir
390
    // value should be passed through to the component as-is, and the component is
391
    // then responsible for constructing an appropriate sub-path (for example,
392
    // tor-dirmgr receives cache_dir, and appends components such as "dir_blobs".
393
    //
394
    // (This consistency rule is not current always followed by every component.)
395
    #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
396
    cache_dir: CfgPath,
397

            
398
    /// Location on disk for less-sensitive persistent state information.
399
    ///
400
    /// Should be accessed through the `state_dir()` getter to provide better error messages when
401
    /// resolving the path.
402
    // Usage note: see the note for `cache_dir`, above.
403
    #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
404
    state_dir: CfgPath,
405

            
406
    /// Location on disk for the Arti keystore.
407
    #[deftly(tor_config(sub_builder))]
408
    keystore: ArtiKeystoreConfig,
409

            
410
    /// Configuration about which permissions we want to enforce on our files.
411
    // NOTE: This 'build_for_arti()' hard-codes the config field name as `permissions` and the
412
    // environment variable as `ARTI_FS_DISABLE_PERMISSION_CHECKS`. These things should be
413
    // configured by the application, not lower-level libraries, but some other lower-level
414
    // libraries like `tor-hsservice` also use 'build_for_arti()'. So we're stuck with it for now.
415
    // It might be confusing in the future if relays use some environment variables prefixed with
416
    // "ARTI_" and others with "ARTI_RELAY_", so we should probably stick to just "ARTI_".
417
    #[deftly(tor_config(
418
        sub_builder(build_fn = "build_for_arti"),
419
        extend_with = "extend_with_replace"
420
    ))]
421
    permissions: Mistrust,
422
}
423

            
424
impl StorageConfig {
425
    /// Return the FS permissions to use for state and cache directories.
426
    pub(crate) fn permissions(&self) -> &Mistrust {
427
        &self.permissions
428
    }
429

            
430
    /// Return the fully expanded path of the state directory.
431
    pub(crate) fn state_dir(
432
        &self,
433
        resolver: &CfgPathResolver,
434
    ) -> Result<PathBuf, ConfigBuildError> {
435
        resolve_cfg_path(&self.state_dir, "state_dir", resolver)
436
    }
437

            
438
    /// Return the fully expanded path of the cache directory.
439
    pub(crate) fn cache_dir(
440
        &self,
441
        resolver: &CfgPathResolver,
442
    ) -> Result<PathBuf, ConfigBuildError> {
443
        resolve_cfg_path(&self.cache_dir, "cache_dir", resolver)
444
    }
445
}
446

            
447
/// Configuration for system resources used by the relay.
448
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
449
#[derive_deftly(TorConfig)]
450
#[non_exhaustive]
451
pub(crate) struct SystemConfig {
452
    /// Memory limits (approximate)
453
    #[deftly(tor_config(sub_builder))]
454
    pub(crate) memory: tor_memquota::Config,
455
}
456

            
457
/// Return the default cache directory.
458
2
fn default_cache_dir() -> CfgPath {
459
2
    CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
460
2
}
461

            
462
/// Return the default state directory.
463
2
fn default_state_dir() -> CfgPath {
464
2
    CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
465
2
}
466

            
467
/// Helper to return a `ConfigBuildError` if the path could not be resolved.
468
fn resolve_cfg_path(
469
    path: &CfgPath,
470
    name: &str,
471
    resolver: &CfgPathResolver,
472
) -> Result<PathBuf, ConfigBuildError> {
473
    path.path(resolver).map_err(|e| ConfigBuildError::Invalid {
474
        field: name.to_owned(),
475
        problem: e.to_string(),
476
    })
477
}
478

            
479
#[cfg(test)]
480
mod test {
481
    // @@ begin test lint list maintained by maint/add_warning @@
482
    #![allow(clippy::bool_assert_comparison)]
483
    #![allow(clippy::clone_on_copy)]
484
    #![allow(clippy::dbg_macro)]
485
    #![allow(clippy::mixed_attributes_style)]
486
    #![allow(clippy::print_stderr)]
487
    #![allow(clippy::print_stdout)]
488
    #![allow(clippy::single_char_pattern)]
489
    #![allow(clippy::unwrap_used)]
490
    #![allow(clippy::unchecked_time_subtraction)]
491
    #![allow(clippy::useless_vec)]
492
    #![allow(clippy::needless_pass_by_value)]
493
    #![allow(clippy::string_slice)] // See arti#2571
494
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
495

            
496
    use super::*;
497

            
498
    fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
499
        let project_dirs = project_dirs().unwrap();
500
        let list = [
501
            ("ARTI_RELAY_CACHE", project_dirs.cache_dir()),
502
            ("ARTI_RELAY_CONFIG", project_dirs.config_dir()),
503
            ("ARTI_RELAY_LOCAL_DATA", project_dirs.data_local_dir()),
504
            ("PROGRAM_DIR", &get_program_dir().unwrap()),
505
            ("USER_HOME", tor_config_path::home().unwrap()),
506
        ];
507

            
508
        list.into_iter()
509
            .map(|(a, b)| (a, b.to_owned()))
510
            .collect::<Vec<_>>()
511
    }
512

            
513
    #[cfg(not(target_family = "windows"))]
514
    #[test]
515
    fn expand_variables() {
516
        let path_resolver = base_resolver();
517

            
518
        for (var, val) in cfg_variables() {
519
            let p = CfgPath::new(format!("${{{var}}}/example"));
520
            assert_eq!(p.to_string(), format!("${{{var}}}/example"));
521

            
522
            let expected = val.join("example");
523
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
524
        }
525

            
526
        let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
527
        assert!(p.path(&path_resolver).is_err());
528
    }
529

            
530
    #[cfg(target_family = "windows")]
531
    #[test]
532
    fn expand_variables() {
533
        let path_resolver = base_resolver();
534

            
535
        for (var, val) in cfg_variables() {
536
            let p = CfgPath::new(format!("${{{var}}}\\example"));
537
            assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
538

            
539
            let expected = val.join("example");
540
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
541
        }
542

            
543
        let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
544
        assert!(p.path(&path_resolver).is_err());
545
    }
546
}