• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::fs::{asyncify, File};
2 
3 use std::io;
4 use std::path::Path;
5 
6 #[cfg(test)]
7 mod mock_open_options;
8 #[cfg(test)]
9 use mock_open_options::MockOpenOptions as StdOpenOptions;
10 #[cfg(not(test))]
11 use std::fs::OpenOptions as StdOpenOptions;
12 
13 #[cfg(unix)]
14 use std::os::unix::fs::OpenOptionsExt;
15 #[cfg(windows)]
16 use std::os::windows::fs::OpenOptionsExt;
17 
18 /// Options and flags which can be used to configure how a file is opened.
19 ///
20 /// This builder exposes the ability to configure how a [`File`] is opened and
21 /// what operations are permitted on the open file. The [`File::open`] and
22 /// [`File::create`] methods are aliases for commonly used options using this
23 /// builder.
24 ///
25 /// Generally speaking, when using `OpenOptions`, you'll first call [`new`],
26 /// then chain calls to methods to set each option, then call [`open`], passing
27 /// the path of the file you're trying to open. This will give you a
28 /// [`io::Result`] with a [`File`] inside that you can further operate
29 /// on.
30 ///
31 /// This is a specialized version of [`std::fs::OpenOptions`] for usage from
32 /// the Tokio runtime.
33 ///
34 /// `From<std::fs::OpenOptions>` is implemented for more advanced configuration
35 /// than the methods provided here.
36 ///
37 /// [`new`]: OpenOptions::new
38 /// [`open`]: OpenOptions::open
39 /// [`File`]: File
40 /// [`File::open`]: File::open
41 /// [`File::create`]: File::create
42 ///
43 /// # Examples
44 ///
45 /// Opening a file to read:
46 ///
47 /// ```no_run
48 /// use tokio::fs::OpenOptions;
49 /// use std::io;
50 ///
51 /// #[tokio::main]
52 /// async fn main() -> io::Result<()> {
53 ///     let file = OpenOptions::new()
54 ///         .read(true)
55 ///         .open("foo.txt")
56 ///         .await?;
57 ///
58 ///     Ok(())
59 /// }
60 /// ```
61 ///
62 /// Opening a file for both reading and writing, as well as creating it if it
63 /// doesn't exist:
64 ///
65 /// ```no_run
66 /// use tokio::fs::OpenOptions;
67 /// use std::io;
68 ///
69 /// #[tokio::main]
70 /// async fn main() -> io::Result<()> {
71 ///     let file = OpenOptions::new()
72 ///         .read(true)
73 ///         .write(true)
74 ///         .create(true)
75 ///         .open("foo.txt")
76 ///         .await?;
77 ///
78 ///     Ok(())
79 /// }
80 /// ```
81 #[derive(Clone, Debug)]
82 pub struct OpenOptions(StdOpenOptions);
83 
84 impl OpenOptions {
85     /// Creates a blank new set of options ready for configuration.
86     ///
87     /// All options are initially set to `false`.
88     ///
89     /// This is an async version of [`std::fs::OpenOptions::new`][std]
90     ///
91     /// [std]: std::fs::OpenOptions::new
92     ///
93     /// # Examples
94     ///
95     /// ```no_run
96     /// use tokio::fs::OpenOptions;
97     ///
98     /// let mut options = OpenOptions::new();
99     /// let future = options.read(true).open("foo.txt");
100     /// ```
new() -> OpenOptions101     pub fn new() -> OpenOptions {
102         OpenOptions(StdOpenOptions::new())
103     }
104 
105     /// Sets the option for read access.
106     ///
107     /// This option, when true, will indicate that the file should be
108     /// `read`-able if opened.
109     ///
110     /// This is an async version of [`std::fs::OpenOptions::read`][std]
111     ///
112     /// [std]: std::fs::OpenOptions::read
113     ///
114     /// # Examples
115     ///
116     /// ```no_run
117     /// use tokio::fs::OpenOptions;
118     /// use std::io;
119     ///
120     /// #[tokio::main]
121     /// async fn main() -> io::Result<()> {
122     ///     let file = OpenOptions::new()
123     ///         .read(true)
124     ///         .open("foo.txt")
125     ///         .await?;
126     ///
127     ///     Ok(())
128     /// }
129     /// ```
read(&mut self, read: bool) -> &mut OpenOptions130     pub fn read(&mut self, read: bool) -> &mut OpenOptions {
131         self.0.read(read);
132         self
133     }
134 
135     /// Sets the option for write access.
136     ///
137     /// This option, when true, will indicate that the file should be
138     /// `write`-able if opened.
139     ///
140     /// This is an async version of [`std::fs::OpenOptions::write`][std]
141     ///
142     /// [std]: std::fs::OpenOptions::write
143     ///
144     /// # Examples
145     ///
146     /// ```no_run
147     /// use tokio::fs::OpenOptions;
148     /// use std::io;
149     ///
150     /// #[tokio::main]
151     /// async fn main() -> io::Result<()> {
152     ///     let file = OpenOptions::new()
153     ///         .write(true)
154     ///         .open("foo.txt")
155     ///         .await?;
156     ///
157     ///     Ok(())
158     /// }
159     /// ```
write(&mut self, write: bool) -> &mut OpenOptions160     pub fn write(&mut self, write: bool) -> &mut OpenOptions {
161         self.0.write(write);
162         self
163     }
164 
165     /// Sets the option for the append mode.
166     ///
167     /// This option, when true, means that writes will append to a file instead
168     /// of overwriting previous contents.  Note that setting
169     /// `.write(true).append(true)` has the same effect as setting only
170     /// `.append(true)`.
171     ///
172     /// For most filesystems, the operating system guarantees that all writes are
173     /// atomic: no writes get mangled because another process writes at the same
174     /// time.
175     ///
176     /// One maybe obvious note when using append-mode: make sure that all data
177     /// that belongs together is written to the file in one operation. This
178     /// can be done by concatenating strings before passing them to [`write()`],
179     /// or using a buffered writer (with a buffer of adequate size),
180     /// and calling [`flush()`] when the message is complete.
181     ///
182     /// If a file is opened with both read and append access, beware that after
183     /// opening, and after every write, the position for reading may be set at the
184     /// end of the file. So, before writing, save the current position (using
185     /// [`seek`]`(`[`SeekFrom`]`::`[`Current`]`(0))`), and restore it before the next read.
186     ///
187     /// This is an async version of [`std::fs::OpenOptions::append`][std]
188     ///
189     /// [std]: std::fs::OpenOptions::append
190     ///
191     /// ## Note
192     ///
193     /// This function doesn't create the file if it doesn't exist. Use the [`create`]
194     /// method to do so.
195     ///
196     /// [`write()`]: crate::io::AsyncWriteExt::write
197     /// [`flush()`]: crate::io::AsyncWriteExt::flush
198     /// [`seek`]: crate::io::AsyncSeekExt::seek
199     /// [`SeekFrom`]: std::io::SeekFrom
200     /// [`Current`]: std::io::SeekFrom::Current
201     /// [`create`]: OpenOptions::create
202     ///
203     /// # Examples
204     ///
205     /// ```no_run
206     /// use tokio::fs::OpenOptions;
207     /// use std::io;
208     ///
209     /// #[tokio::main]
210     /// async fn main() -> io::Result<()> {
211     ///     let file = OpenOptions::new()
212     ///         .append(true)
213     ///         .open("foo.txt")
214     ///         .await?;
215     ///
216     ///     Ok(())
217     /// }
218     /// ```
append(&mut self, append: bool) -> &mut OpenOptions219     pub fn append(&mut self, append: bool) -> &mut OpenOptions {
220         self.0.append(append);
221         self
222     }
223 
224     /// Sets the option for truncating a previous file.
225     ///
226     /// If a file is successfully opened with this option set it will truncate
227     /// the file to 0 length if it already exists.
228     ///
229     /// The file must be opened with write access for truncate to work.
230     ///
231     /// This is an async version of [`std::fs::OpenOptions::truncate`][std]
232     ///
233     /// [std]: std::fs::OpenOptions::truncate
234     ///
235     /// # Examples
236     ///
237     /// ```no_run
238     /// use tokio::fs::OpenOptions;
239     /// use std::io;
240     ///
241     /// #[tokio::main]
242     /// async fn main() -> io::Result<()> {
243     ///     let file = OpenOptions::new()
244     ///         .write(true)
245     ///         .truncate(true)
246     ///         .open("foo.txt")
247     ///         .await?;
248     ///
249     ///     Ok(())
250     /// }
251     /// ```
truncate(&mut self, truncate: bool) -> &mut OpenOptions252     pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {
253         self.0.truncate(truncate);
254         self
255     }
256 
257     /// Sets the option for creating a new file.
258     ///
259     /// This option indicates whether a new file will be created if the file
260     /// does not yet already exist.
261     ///
262     /// In order for the file to be created, [`write`] or [`append`] access must
263     /// be used.
264     ///
265     /// This is an async version of [`std::fs::OpenOptions::create`][std]
266     ///
267     /// [std]: std::fs::OpenOptions::create
268     /// [`write`]: OpenOptions::write
269     /// [`append`]: OpenOptions::append
270     ///
271     /// # Examples
272     ///
273     /// ```no_run
274     /// use tokio::fs::OpenOptions;
275     /// use std::io;
276     ///
277     /// #[tokio::main]
278     /// async fn main() -> io::Result<()> {
279     ///     let file = OpenOptions::new()
280     ///         .write(true)
281     ///         .create(true)
282     ///         .open("foo.txt")
283     ///         .await?;
284     ///
285     ///     Ok(())
286     /// }
287     /// ```
create(&mut self, create: bool) -> &mut OpenOptions288     pub fn create(&mut self, create: bool) -> &mut OpenOptions {
289         self.0.create(create);
290         self
291     }
292 
293     /// Sets the option to always create a new file.
294     ///
295     /// This option indicates whether a new file will be created.  No file is
296     /// allowed to exist at the target location, also no (dangling) symlink.
297     ///
298     /// This option is useful because it is atomic. Otherwise between checking
299     /// whether a file exists and creating a new one, the file may have been
300     /// created by another process (a TOCTOU race condition / attack).
301     ///
302     /// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are
303     /// ignored.
304     ///
305     /// The file must be opened with write or append access in order to create a
306     /// new file.
307     ///
308     /// This is an async version of [`std::fs::OpenOptions::create_new`][std]
309     ///
310     /// [std]: std::fs::OpenOptions::create_new
311     /// [`.create()`]: OpenOptions::create
312     /// [`.truncate()`]: OpenOptions::truncate
313     ///
314     /// # Examples
315     ///
316     /// ```no_run
317     /// use tokio::fs::OpenOptions;
318     /// use std::io;
319     ///
320     /// #[tokio::main]
321     /// async fn main() -> io::Result<()> {
322     ///     let file = OpenOptions::new()
323     ///         .write(true)
324     ///         .create_new(true)
325     ///         .open("foo.txt")
326     ///         .await?;
327     ///
328     ///     Ok(())
329     /// }
330     /// ```
create_new(&mut self, create_new: bool) -> &mut OpenOptions331     pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
332         self.0.create_new(create_new);
333         self
334     }
335 
336     /// Opens a file at `path` with the options specified by `self`.
337     ///
338     /// This is an async version of [`std::fs::OpenOptions::open`][std]
339     ///
340     /// [std]: std::fs::OpenOptions::open
341     ///
342     /// # Errors
343     ///
344     /// This function will return an error under a number of different
345     /// circumstances. Some of these error conditions are listed here, together
346     /// with their [`ErrorKind`]. The mapping to [`ErrorKind`]s is not part of
347     /// the compatibility contract of the function, especially the `Other` kind
348     /// might change to more specific kinds in the future.
349     ///
350     /// * [`NotFound`]: The specified file does not exist and neither `create`
351     ///   or `create_new` is set.
352     /// * [`NotFound`]: One of the directory components of the file path does
353     ///   not exist.
354     /// * [`PermissionDenied`]: The user lacks permission to get the specified
355     ///   access rights for the file.
356     /// * [`PermissionDenied`]: The user lacks permission to open one of the
357     ///   directory components of the specified path.
358     /// * [`AlreadyExists`]: `create_new` was specified and the file already
359     ///   exists.
360     /// * [`InvalidInput`]: Invalid combinations of open options (truncate
361     ///   without write access, no access mode set, etc.).
362     /// * [`Other`]: One of the directory components of the specified file path
363     ///   was not, in fact, a directory.
364     /// * [`Other`]: Filesystem-level errors: full disk, write permission
365     ///   requested on a read-only file system, exceeded disk quota, too many
366     ///   open files, too long filename, too many symbolic links in the
367     ///   specified path (Unix-like systems only), etc.
368     ///
369     /// # Examples
370     ///
371     /// ```no_run
372     /// use tokio::fs::OpenOptions;
373     /// use std::io;
374     ///
375     /// #[tokio::main]
376     /// async fn main() -> io::Result<()> {
377     ///     let file = OpenOptions::new().open("foo.txt").await?;
378     ///     Ok(())
379     /// }
380     /// ```
381     ///
382     /// [`ErrorKind`]: std::io::ErrorKind
383     /// [`AlreadyExists`]: std::io::ErrorKind::AlreadyExists
384     /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput
385     /// [`NotFound`]: std::io::ErrorKind::NotFound
386     /// [`Other`]: std::io::ErrorKind::Other
387     /// [`PermissionDenied`]: std::io::ErrorKind::PermissionDenied
open(&self, path: impl AsRef<Path>) -> io::Result<File>388     pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
389         let path = path.as_ref().to_owned();
390         let opts = self.0.clone();
391 
392         let std = asyncify(move || opts.open(path)).await?;
393         Ok(File::from_std(std))
394     }
395 
396     /// Returns a mutable reference to the underlying `std::fs::OpenOptions`
397     #[cfg(any(windows, unix))]
as_inner_mut(&mut self) -> &mut StdOpenOptions398     pub(super) fn as_inner_mut(&mut self) -> &mut StdOpenOptions {
399         &mut self.0
400     }
401 }
402 
403 feature! {
404     #![unix]
405 
406     impl OpenOptions {
407         /// Sets the mode bits that a new file will be created with.
408         ///
409         /// If a new file is created as part of an `OpenOptions::open` call then this
410         /// specified `mode` will be used as the permission bits for the new file.
411         /// If no `mode` is set, the default of `0o666` will be used.
412         /// The operating system masks out bits with the system's `umask`, to produce
413         /// the final permissions.
414         ///
415         /// # Examples
416         ///
417         /// ```no_run
418         /// use tokio::fs::OpenOptions;
419         /// use std::io;
420         ///
421         /// #[tokio::main]
422         /// async fn main() -> io::Result<()> {
423         ///     let mut options = OpenOptions::new();
424         ///     options.mode(0o644); // Give read/write for owner and read for others.
425         ///     let file = options.open("foo.txt").await?;
426         ///
427         ///     Ok(())
428         /// }
429         /// ```
430         pub fn mode(&mut self, mode: u32) -> &mut OpenOptions {
431             self.as_inner_mut().mode(mode);
432             self
433         }
434 
435         /// Passes custom flags to the `flags` argument of `open`.
436         ///
437         /// The bits that define the access mode are masked out with `O_ACCMODE`, to
438         /// ensure they do not interfere with the access mode set by Rusts options.
439         ///
440         /// Custom flags can only set flags, not remove flags set by Rusts options.
441         /// This options overwrites any previously set custom flags.
442         ///
443         /// # Examples
444         ///
445         /// ```no_run
446         /// use tokio::fs::OpenOptions;
447         /// use std::io;
448         ///
449         /// #[tokio::main]
450         /// async fn main() -> io::Result<()> {
451         ///     let mut options = OpenOptions::new();
452         ///     options.write(true);
453         ///     if cfg!(unix) {
454         ///         options.custom_flags(libc::O_NOFOLLOW);
455         ///     }
456         ///     let file = options.open("foo.txt").await?;
457         ///
458         ///     Ok(())
459         /// }
460         /// ```
461         pub fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {
462             self.as_inner_mut().custom_flags(flags);
463             self
464         }
465     }
466 }
467 
468 cfg_windows! {
469     impl OpenOptions {
470         /// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`]
471         /// with the specified value.
472         ///
473         /// This will override the `read`, `write`, and `append` flags on the
474         /// `OpenOptions` structure. This method provides fine-grained control over
475         /// the permissions to read, write and append data, attributes (like hidden
476         /// and system), and extended attributes.
477         ///
478         /// # Examples
479         ///
480         /// ```no_run
481         /// use tokio::fs::OpenOptions;
482         ///
483         /// # #[tokio::main]
484         /// # async fn main() -> std::io::Result<()> {
485         /// // Open without read and write permission, for example if you only need
486         /// // to call `stat` on the file
487         /// let file = OpenOptions::new().access_mode(0).open("foo.txt").await?;
488         /// # Ok(())
489         /// # }
490         /// ```
491         ///
492         /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
493         pub fn access_mode(&mut self, access: u32) -> &mut OpenOptions {
494             self.as_inner_mut().access_mode(access);
495             self
496         }
497 
498         /// Overrides the `dwShareMode` argument to the call to [`CreateFile`] with
499         /// the specified value.
500         ///
501         /// By default `share_mode` is set to
502         /// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. This allows
503         /// other processes to read, write, and delete/rename the same file
504         /// while it is open. Removing any of the flags will prevent other
505         /// processes from performing the corresponding operation until the file
506         /// handle is closed.
507         ///
508         /// # Examples
509         ///
510         /// ```no_run
511         /// use tokio::fs::OpenOptions;
512         ///
513         /// # #[tokio::main]
514         /// # async fn main() -> std::io::Result<()> {
515         /// // Do not allow others to read or modify this file while we have it open
516         /// // for writing.
517         /// let file = OpenOptions::new()
518         ///     .write(true)
519         ///     .share_mode(0)
520         ///     .open("foo.txt").await?;
521         /// # Ok(())
522         /// # }
523         /// ```
524         ///
525         /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
526         pub fn share_mode(&mut self, share: u32) -> &mut OpenOptions {
527             self.as_inner_mut().share_mode(share);
528             self
529         }
530 
531         /// Sets extra flags for the `dwFileFlags` argument to the call to
532         /// [`CreateFile2`] to the specified value (or combines it with
533         /// `attributes` and `security_qos_flags` to set the `dwFlagsAndAttributes`
534         /// for [`CreateFile`]).
535         ///
536         /// Custom flags can only set flags, not remove flags set by Rust's options.
537         /// This option overwrites any previously set custom flags.
538         ///
539         /// # Examples
540         ///
541         /// ```no_run
542         /// use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_DELETE_ON_CLOSE;
543         /// use tokio::fs::OpenOptions;
544         ///
545         /// # #[tokio::main]
546         /// # async fn main() -> std::io::Result<()> {
547         /// let file = OpenOptions::new()
548         ///     .create(true)
549         ///     .write(true)
550         ///     .custom_flags(FILE_FLAG_DELETE_ON_CLOSE)
551         ///     .open("foo.txt").await?;
552         /// # Ok(())
553         /// # }
554         /// ```
555         ///
556         /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
557         /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
558         pub fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions {
559             self.as_inner_mut().custom_flags(flags);
560             self
561         }
562 
563         /// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to
564         /// the specified value (or combines it with `custom_flags` and
565         /// `security_qos_flags` to set the `dwFlagsAndAttributes` for
566         /// [`CreateFile`]).
567         ///
568         /// If a _new_ file is created because it does not yet exist and
569         /// `.create(true)` or `.create_new(true)` are specified, the new file is
570         /// given the attributes declared with `.attributes()`.
571         ///
572         /// If an _existing_ file is opened with `.create(true).truncate(true)`, its
573         /// existing attributes are preserved and combined with the ones declared
574         /// with `.attributes()`.
575         ///
576         /// In all other cases the attributes get ignored.
577         ///
578         /// # Examples
579         ///
580         /// ```no_run
581         /// use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_HIDDEN;
582         /// use tokio::fs::OpenOptions;
583         ///
584         /// # #[tokio::main]
585         /// # async fn main() -> std::io::Result<()> {
586         /// let file = OpenOptions::new()
587         ///     .write(true)
588         ///     .create(true)
589         ///     .attributes(FILE_ATTRIBUTE_HIDDEN)
590         ///     .open("foo.txt").await?;
591         /// # Ok(())
592         /// # }
593         /// ```
594         ///
595         /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
596         /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
597         pub fn attributes(&mut self, attributes: u32) -> &mut OpenOptions {
598             self.as_inner_mut().attributes(attributes);
599             self
600         }
601 
602         /// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to
603         /// the specified value (or combines it with `custom_flags` and `attributes`
604         /// to set the `dwFlagsAndAttributes` for [`CreateFile`]).
605         ///
606         /// By default `security_qos_flags` is not set. It should be specified when
607         /// opening a named pipe, to control to which degree a server process can
608         /// act on behalf of a client process (security impersonation level).
609         ///
610         /// When `security_qos_flags` is not set, a malicious program can gain the
611         /// elevated privileges of a privileged Rust process when it allows opening
612         /// user-specified paths, by tricking it into opening a named pipe. So
613         /// arguably `security_qos_flags` should also be set when opening arbitrary
614         /// paths. However the bits can then conflict with other flags, specifically
615         /// `FILE_FLAG_OPEN_NO_RECALL`.
616         ///
617         /// For information about possible values, see [Impersonation Levels] on the
618         /// Windows Dev Center site. The `SECURITY_SQOS_PRESENT` flag is set
619         /// automatically when using this method.
620         ///
621         /// # Examples
622         ///
623         /// ```no_run
624         /// use windows_sys::Win32::Storage::FileSystem::SECURITY_IDENTIFICATION;
625         /// use tokio::fs::OpenOptions;
626         ///
627         /// # #[tokio::main]
628         /// # async fn main() -> std::io::Result<()> {
629         /// let file = OpenOptions::new()
630         ///     .write(true)
631         ///     .create(true)
632         ///
633         ///     // Sets the flag value to `SecurityIdentification`.
634         ///     .security_qos_flags(SECURITY_IDENTIFICATION)
635         ///
636         ///     .open(r"\\.\pipe\MyPipe").await?;
637         /// # Ok(())
638         /// # }
639         /// ```
640         ///
641         /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
642         /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
643         /// [Impersonation Levels]:
644         ///     https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
645         pub fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions {
646             self.as_inner_mut().security_qos_flags(flags);
647             self
648         }
649     }
650 }
651 
652 impl From<StdOpenOptions> for OpenOptions {
from(options: StdOpenOptions) -> OpenOptions653     fn from(options: StdOpenOptions) -> OpenOptions {
654         OpenOptions(options)
655     }
656 }
657 
658 impl Default for OpenOptions {
default() -> Self659     fn default() -> Self {
660         Self::new()
661     }
662 }
663