1use super::Fuse;
5use super::Mapper;
6use super::protocol::*;
7use super::reply::ReplySender;
8use super::request::FuseOperation;
9use super::request::Request;
10use super::request::RequestReader;
11use parking_lot::RwLock;
12use std::io;
13use std::sync::atomic;
14use thiserror::Error;
15use zerocopy::FromZeros;
16use zerocopy::Immutable;
17use zerocopy::KnownLayout;
18
19const DEFAULT_FLAGS: u32 = FUSE_ASYNC_READ
21 | FUSE_PARALLEL_DIROPS
22 | FUSE_AUTO_INVAL_DATA
23 | FUSE_HANDLE_KILLPRIV
24 | FUSE_ASYNC_DIO
25 | FUSE_ATOMIC_O_TRUNC
26 | FUSE_BIG_WRITES;
27
28const DEFAULT_MAX_PAGES: u32 = 256;
29
30const PAGE_SIZE: u32 = 4096;
34
35pub struct Session {
39 fs: Box<dyn Fuse + Send + Sync>,
40 initialized: atomic::AtomicBool,
43 info: RwLock<SessionInfo>,
44}
45
46impl Session {
47 pub fn new<T>(fs: T) -> Self
49 where
50 T: 'static + Fuse + Send + Sync,
51 {
52 Self {
53 fs: Box::new(fs),
54 initialized: atomic::AtomicBool::new(false),
55 info: RwLock::new(SessionInfo::default()),
56 }
57 }
58
59 pub fn is_initialized(&self) -> bool {
63 self.initialized.load(atomic::Ordering::Acquire)
64 }
65
66 pub fn dispatch(
68 &self,
69 request: Request,
70 sender: &mut impl ReplySender,
71 mapper: Option<&dyn Mapper>,
72 ) {
73 let unique = request.unique();
74 let result = if self.is_initialized() {
75 self.dispatch_helper(request, sender, mapper)
76 } else {
77 self.dispatch_init(request, sender)
78 };
79
80 match result {
81 Err(OperationError::FsError(e)) => {
82 if let Err(e) = sender.send_error(unique, e.value()) {
83 tracing::error!(
84 unique,
85 error = &e as &dyn std::error::Error,
86 "Failed to send reply",
87 );
88 }
89 }
90 Err(OperationError::SendError(e)) => {
91 if e.kind() == io::ErrorKind::NotFound {
92 tracing::trace!(unique, "Request was interrupted.");
93 } else {
94 tracing::error!(
95 unique,
96 error = &e as &dyn std::error::Error,
97 "Failed to send reply",
98 );
99 }
100 }
101 Ok(_) => (),
102 }
103 }
104
105 pub fn destroy(&self) {
112 if self.initialized.swap(false, atomic::Ordering::AcqRel) {
113 self.fs.destroy();
114 }
115 }
116
117 fn dispatch_helper(
120 &self,
121 request: Request,
122 sender: &mut impl ReplySender,
123 mapper: Option<&dyn Mapper>,
124 ) -> Result<(), OperationError> {
125 request.log();
126
127 match request.operation() {
128 FuseOperation::Invalid => {
129 return Err(lx::Error::EIO.into());
132 }
133 FuseOperation::Lookup { name } => {
134 let out = self.fs.lookup(&request, name)?;
135 sender.send_arg(request.unique(), out)?;
136 }
137 FuseOperation::Forget { arg } => {
138 self.fs.forget(request.node_id(), arg.nlookup);
139 }
140 FuseOperation::GetAttr { arg } => {
141 let out = self.fs.get_attr(&request, arg.getattr_flags, arg.fh)?;
142 sender.send_arg(request.unique(), out)?;
143 }
144 FuseOperation::SetAttr { arg } => {
145 let out = self.fs.set_attr(&request, arg)?;
146 sender.send_arg(request.unique(), out)?;
147 }
148 FuseOperation::ReadLink {} => {
149 let out = self.fs.read_link(&request)?;
150 sender.send_string(request.unique(), out)?;
151 }
152 FuseOperation::Symlink { name, target } => {
153 let out = self.fs.symlink(&request, name, target)?;
154 sender.send_arg(request.unique(), out)?;
155 }
156 FuseOperation::MkNod { arg, name } => {
157 let out = self.fs.mknod(&request, name, arg)?;
158 sender.send_arg(request.unique(), out)?;
159 }
160 FuseOperation::MkDir { arg, name } => {
161 let out = self.fs.mkdir(&request, name, arg)?;
162 sender.send_arg(request.unique(), out)?;
163 }
164 FuseOperation::Unlink { name } => {
165 self.fs.unlink(&request, name)?;
166 sender.send_empty(request.unique())?;
167 }
168 FuseOperation::RmDir { name } => {
169 self.fs.rmdir(&request, name)?;
170 sender.send_empty(request.unique())?;
171 }
172 FuseOperation::Rename {
173 arg,
174 name,
175 new_name,
176 } => {
177 self.fs.rename(&request, name, arg.newdir, new_name, 0)?;
178
179 sender.send_empty(request.unique())?;
180 }
181 FuseOperation::Link { arg, name } => {
182 let out = self.fs.link(&request, name, arg.oldnodeid)?;
183 sender.send_arg(request.unique(), out)?;
184 }
185 FuseOperation::Open { arg } => {
186 let out = self.fs.open(&request, arg.flags)?;
187 self.send_release_if_interrupted(&request, sender, out.fh, arg.flags, out, false)?;
188 }
189 FuseOperation::Read { arg } => {
190 let out = self.fs.read(&request, arg)?;
191 Self::send_max_size(sender, request.unique(), &out, arg.size)?;
192 }
193 FuseOperation::Write { arg, data } => {
194 let out = self.fs.write(&request, arg, data)?;
195 sender.send_arg(
196 request.unique(),
197 fuse_write_out {
198 size: out.try_into().unwrap(),
199 padding: 0,
200 },
201 )?;
202 }
203 FuseOperation::StatFs {} => {
204 let out = self.fs.statfs(&request)?;
205 sender.send_arg(request.unique(), fuse_statfs_out { st: out })?;
206 }
207 FuseOperation::Release { arg } => {
208 self.fs.release(&request, arg)?;
209 sender.send_empty(request.unique())?;
210 }
211 FuseOperation::FSync { arg } => {
212 self.fs.fsync(&request, arg.fh, arg.fsync_flags)?;
213 sender.send_empty(request.unique())?;
214 }
215 FuseOperation::SetXAttr { arg, name, value } => {
216 self.fs.set_xattr(&request, name, value, arg.flags)?;
217 sender.send_empty(request.unique())?;
218 }
219 FuseOperation::GetXAttr { arg, name } => {
220 if arg.size == 0 {
221 let out = self.fs.get_xattr_size(&request, name)?;
222 sender.send_arg(
223 request.unique(),
224 fuse_getxattr_out {
225 size: out,
226 padding: 0,
227 },
228 )?;
229 } else {
230 let out = self.fs.get_xattr(&request, name, arg.size)?;
231 Self::send_max_size(sender, request.unique(), &out, arg.size)?;
232 }
233 }
234 FuseOperation::ListXAttr { arg } => {
235 if arg.size == 0 {
236 let out = self.fs.list_xattr_size(&request)?;
237 sender.send_arg(
238 request.unique(),
239 fuse_getxattr_out {
240 size: out,
241 padding: 0,
242 },
243 )?;
244 } else {
245 let out = self.fs.list_xattr(&request, arg.size)?;
246 Self::send_max_size(sender, request.unique(), &out, arg.size)?;
247 }
248 }
249 FuseOperation::RemoveXAttr { name } => {
250 self.fs.remove_xattr(&request, name)?;
251 sender.send_empty(request.unique())?;
252 }
253 FuseOperation::Flush { arg } => {
254 self.fs.flush(&request, arg)?;
255 sender.send_empty(request.unique())?;
256 }
257 FuseOperation::Init { arg: _ } => {
258 tracing::warn!("Duplicate init message.");
259 return Err(lx::Error::EIO.into());
260 }
261 FuseOperation::OpenDir { arg } => {
262 let out = self.fs.open_dir(&request, arg.flags)?;
263 self.send_release_if_interrupted(&request, sender, out.fh, arg.flags, out, true)?;
264 }
265 FuseOperation::ReadDir { arg } => {
266 let out = self.fs.read_dir(&request, arg)?;
267 Self::send_max_size(sender, request.unique(), &out, arg.size)?;
268 }
269 FuseOperation::ReleaseDir { arg } => {
270 self.fs.release_dir(&request, arg)?;
271 sender.send_empty(request.unique())?;
272 }
273 FuseOperation::FSyncDir { arg } => {
274 self.fs.fsync_dir(&request, arg.fh, arg.fsync_flags)?;
275 sender.send_empty(request.unique())?;
276 }
277 FuseOperation::GetLock { arg } => {
278 let out = self.fs.get_lock(&request, arg)?;
279 sender.send_arg(request.unique(), out)?;
280 }
281 FuseOperation::SetLock { arg } => {
282 self.fs.set_lock(&request, arg, false)?;
283 sender.send_empty(request.unique())?;
284 }
285 FuseOperation::SetLockSleep { arg } => {
286 self.fs.set_lock(&request, arg, true)?;
287 sender.send_empty(request.unique())?;
288 }
289 FuseOperation::Access { arg } => {
290 self.fs.access(&request, arg.mask)?;
291 sender.send_empty(request.unique())?;
292 }
293 FuseOperation::Create { arg, name } => {
294 let out = self.fs.create(&request, name, arg)?;
295 self.send_release_if_interrupted(
296 &request,
297 sender,
298 out.open.fh,
299 arg.flags,
300 out,
301 false,
302 )?;
303 }
304 FuseOperation::Interrupt { arg: _ } => {
305 tracing::warn!("FUSE_INTERRUPT not supported.");
308 return Err(lx::Error::ENOSYS.into());
309 }
310 FuseOperation::BMap { arg } => {
311 let out = self.fs.block_map(&request, arg.block, arg.blocksize)?;
312 sender.send_arg(request.unique(), fuse_bmap_out { block: out })?;
313 }
314 FuseOperation::Destroy {} => {
315 if let Some(mapper) = mapper {
316 mapper.clear();
317 }
318 self.destroy();
319 }
320 FuseOperation::Ioctl { arg, data } => {
321 let out = self.fs.ioctl(&request, arg, data)?;
322 if out.1.len() > arg.out_size as usize {
323 return Err(lx::Error::EINVAL.into());
324 }
325
326 sender.send_arg_data(
328 request.unique(),
329 fuse_ioctl_out {
330 result: out.0,
331 flags: 0,
332 in_iovs: 0,
333 out_iovs: 0,
334 },
335 data,
336 )?;
337 }
338 FuseOperation::Poll { arg: _ } => {
339 tracing::warn!("FUSE_POLL not supported.");
343 return Err(lx::Error::ENOSYS.into());
344 }
345 FuseOperation::NotifyReply { arg: _, data: _ } => {
346 tracing::warn!("FUSE_NOTIFY_REPLY not supported.");
348 return Err(lx::Error::ENOSYS.into());
349 }
350 FuseOperation::BatchForget { arg, nodes } => {
351 self.batch_forget(arg.count, nodes);
352 }
353 FuseOperation::FAllocate { arg } => {
354 self.fs.fallocate(&request, arg)?;
355 sender.send_empty(request.unique())?;
356 }
357 FuseOperation::ReadDirPlus { arg } => {
358 let out = self.fs.read_dir_plus(&request, arg)?;
359 Self::send_max_size(sender, request.unique(), &out, arg.size)?;
360 }
361 FuseOperation::Rename2 {
362 arg,
363 name,
364 new_name,
365 } => {
366 self.fs
367 .rename(&request, name, arg.newdir, new_name, arg.flags)?;
368
369 sender.send_empty(request.unique())?;
370 }
371 FuseOperation::LSeek { arg } => {
372 let out = self.fs.lseek(&request, arg.fh, arg.offset, arg.whence)?;
373 sender.send_arg(request.unique(), fuse_lseek_out { offset: out })?;
374 }
375 FuseOperation::CopyFileRange { arg } => {
376 let out = self.fs.copy_file_range(&request, arg)?;
377 sender.send_arg(
378 request.unique(),
379 fuse_write_out {
380 size: out.try_into().unwrap(),
381 padding: 0,
382 },
383 )?;
384 }
385 FuseOperation::SetupMapping { arg } => {
386 if let Some(mapper) = mapper {
387 self.fs.setup_mapping(&request, mapper, arg)?;
388 sender.send_empty(request.unique())?;
389 } else {
390 return Err(lx::Error::ENOSYS.into());
391 }
392 }
393 FuseOperation::RemoveMapping { arg, mappings } => {
394 if let Some(mapper) = mapper {
395 self.remove_mapping(&request, mapper, arg.count, mappings)?;
396 sender.send_empty(request.unique())?;
397 } else {
398 return Err(lx::Error::ENOSYS.into());
399 }
400 }
401 FuseOperation::SyncFs { _arg } => {
402 sender.send_empty(request.unique())?;
404 }
405 FuseOperation::CanonicalPath {} => {
406 tracing::trace!("Unsupported opcode FUSE_CANONICAL_PATH");
410 sender.send_error(request.unique(), lx::Error::ENOSYS.value())?;
411 }
412 }
413
414 Ok(())
415 }
416
417 fn dispatch_init(
419 &self,
420 request: Request,
421 sender: &mut impl ReplySender,
422 ) -> Result<(), OperationError> {
423 request.log();
424 let init: &fuse_init_in = if let FuseOperation::Init { arg } = request.operation() {
425 arg
426 } else {
427 tracing::error!(opcode = request.opcode(), "Expected FUSE_INIT");
428 return Err(lx::Error::EIO.into());
429 };
430
431 let mut info = self.info.write();
432 if self.is_initialized() {
433 tracing::error!("Racy FUSE_INIT requests.");
434 return Err(lx::Error::EIO.into());
435 }
436
437 let mut out = fuse_init_out::new_zeroed();
438 out.major = FUSE_KERNEL_VERSION;
439 out.minor = FUSE_KERNEL_MINOR_VERSION;
440
441 if init.major > FUSE_KERNEL_VERSION {
444 sender.send_arg(request.unique(), out)?;
445 return Ok(());
446 }
447
448 if init.major < FUSE_KERNEL_VERSION || init.minor < 27 {
451 tracing::error!(
452 major = init.major,
453 minor = init.minor,
454 "Got unsupported kernel version",
455 );
456 return Err(lx::Error::EIO.into());
457 }
458
459 info.major = init.major;
461 info.minor = init.minor;
462 info.max_readahead = init.max_readahead;
463 info.capable = init.flags;
464 info.want = DEFAULT_FLAGS & init.flags;
465 info.time_gran = 1;
466 info.max_write = DEFAULT_MAX_PAGES * PAGE_SIZE;
467 self.fs.init(&mut info);
468
469 assert!(info.want & !info.capable == 0);
470
471 out.max_readahead = info.max_readahead;
474 out.flags = info.want;
475 out.max_background = info.max_background;
476 out.congestion_threshold = info.congestion_threshold;
477 out.max_write = info.max_write;
478 out.time_gran = info.time_gran;
479 out.max_pages = ((info.max_write - 1) / PAGE_SIZE - 1).try_into().unwrap();
480
481 sender.send_arg(request.unique(), out)?;
482
483 self.initialized.store(true, atomic::Ordering::Release);
485 Ok(())
486 }
487
488 fn send_release_if_interrupted<
490 TArg: zerocopy::IntoBytes + std::fmt::Debug + Immutable + KnownLayout,
491 >(
492 &self,
493 request: &Request,
494 sender: &mut impl ReplySender,
495 fh: u64,
496 flags: u32,
497 arg: TArg,
498 dir: bool,
499 ) -> lx::Result<()> {
500 if let Err(e) = sender.send_arg(request.unique(), arg) {
501 if e.kind() == io::ErrorKind::NotFound {
504 let arg = fuse_release_in {
505 fh,
506 flags,
507 release_flags: 0,
508 lock_owner: 0,
509 };
510
511 if dir {
512 self.fs.release_dir(request, &arg)?;
513 } else {
514 self.fs.release(request, &arg)?;
515 }
516 } else {
517 return Err(e.into());
518 }
519 }
520
521 Ok(())
522 }
523
524 fn send_max_size(
528 sender: &mut impl ReplySender,
529 unique: u64,
530 data: &[u8],
531 max_size: u32,
532 ) -> Result<(), OperationError> {
533 assert!(data.len() <= max_size as usize);
534 sender.send_data(unique, data)?;
535 Ok(())
536 }
537
538 fn batch_forget(&self, count: u32, mut nodes: &[u8]) {
540 for _ in 0..count {
541 let forget: fuse_forget_one = match nodes.read_type() {
542 Ok(f) => f,
543 Err(_) => break,
544 };
545
546 self.fs.forget(forget.nodeid, forget.nlookup);
547 }
548 }
549
550 fn remove_mapping(
552 &self,
553 request: &Request,
554 mapper: &dyn Mapper,
555 count: u32,
556 mut mappings: &[u8],
557 ) -> lx::Result<()> {
558 for _ in 0..count {
559 let mapping: fuse_removemapping_one = mappings.read_type()?;
560 self.fs
561 .remove_mapping(request, mapper, mapping.moffset, mapping.len)?;
562 }
563
564 Ok(())
565 }
566}
567
568#[derive(Default)]
570pub struct SessionInfo {
571 major: u32,
572 minor: u32,
573 pub max_readahead: u32,
574 capable: u32,
575 pub want: u32,
576 pub max_background: u16,
577 pub congestion_threshold: u16,
578 pub max_write: u32,
579 pub time_gran: u32,
580}
581
582impl SessionInfo {
583 pub fn major(&self) -> u32 {
584 self.major
585 }
586
587 pub fn minor(&self) -> u32 {
588 self.minor
589 }
590
591 pub fn capable(&self) -> u32 {
592 self.capable
593 }
594}
595
596#[derive(Debug, Error)]
597enum OperationError {
598 #[error("File system error")]
599 FsError(#[from] lx::Error),
600 #[error("Send error")]
601 SendError(#[from] io::Error),
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607 use crate::request::tests::*;
608 use parking_lot::Mutex;
609 use std::sync::Arc;
610
611 #[test]
612 fn dispatch() {
613 let mut sender = MockSender::default();
614 let fs = TestFs::default();
615 let state = fs.state.clone();
616 let session = Session::new(fs);
617 assert!(!session.is_initialized());
618 let request = Request::new(FUSE_INIT_REQUEST).unwrap();
619 session.dispatch(request, &mut sender, None);
620 assert_eq!(state.lock().called, INIT_CALLED);
621 assert!(session.is_initialized());
622 session.dispatch(
623 Request::new(FUSE_GETATTR_REQUEST).unwrap(),
624 &mut sender,
625 None,
626 );
627 assert_eq!(state.lock().called, INIT_CALLED | GETATTR_CALLED);
628
629 session.dispatch(
630 Request::new(FUSE_LOOKUP_REQUEST).unwrap(),
631 &mut sender,
632 None,
633 );
634 assert_eq!(
635 state.lock().called,
636 INIT_CALLED | GETATTR_CALLED | LOOKUP_CALLED
637 );
638 }
639
640 #[derive(Default)]
641 struct State {
642 called: u32,
643 }
644
645 #[derive(Default)]
646 struct TestFs {
647 state: Arc<Mutex<State>>,
648 }
649
650 impl Fuse for TestFs {
651 fn init(&self, info: &mut SessionInfo) {
652 assert_eq!(self.state.lock().called & INIT_CALLED, 0);
653 assert_eq!(info.major(), 7);
654 assert_eq!(info.minor(), 27);
655 assert_eq!(info.capable(), 0x3FFFFB);
656 assert_eq!(info.want, 0xC9029);
657 assert_eq!(info.max_readahead, 131072);
658 assert_eq!(info.max_background, 0);
659 assert_eq!(info.max_write, 1048576);
660 assert_eq!(info.congestion_threshold, 0);
661 assert_eq!(info.time_gran, 1);
662 self.state.lock().called |= INIT_CALLED;
663 }
664
665 fn get_attr(&self, request: &Request, flags: u32, fh: u64) -> lx::Result<fuse_attr_out> {
666 assert_eq!(self.state.lock().called & GETATTR_CALLED, 0);
667 assert_eq!(request.node_id(), 1);
668 assert_eq!(flags, 0);
669 assert_eq!(fh, 0);
670 let mut attr = fuse_attr_out::new_zeroed();
671 attr.attr.ino = 1;
672 attr.attr.mode = lx::S_IFDIR | 0o755;
673 attr.attr.nlink = 2;
674 attr.attr_valid = 1;
675 self.state.lock().called |= GETATTR_CALLED;
676 Ok(attr)
677 }
678
679 fn lookup(&self, request: &Request, name: &lx::LxStr) -> lx::Result<fuse_entry_out> {
680 assert_eq!(self.state.lock().called & LOOKUP_CALLED, 0);
681 assert_eq!(request.node_id(), 1);
682 assert_eq!(name, "hello");
683 self.state.lock().called |= LOOKUP_CALLED;
684 let mut attr = fuse_attr::new_zeroed();
685 attr.ino = 2;
686 attr.mode = lx::S_IFREG | 0o644;
687 attr.nlink = 1;
688 attr.size = 13;
689 Ok(fuse_entry_out {
690 nodeid: 2,
691 generation: 0,
692 entry_valid: 1,
693 entry_valid_nsec: 0,
694 attr_valid: 1,
695 attr_valid_nsec: 0,
696 attr,
697 })
698 }
699 }
700
701 #[derive(Default)]
702 struct MockSender {
703 state: u32,
704 }
705
706 impl ReplySender for MockSender {
707 fn send(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> {
708 let flat: Vec<u8> = bufs.iter().flat_map(|s| s.iter()).copied().collect();
709 match self.state {
710 0 => assert_eq!(flat, INIT_REPLY),
711 1 => assert_eq!(flat, GETATTR_REPLY),
712 2 => assert_eq!(flat, LOOKUP_REPLY),
713 _ => panic!("Unexpected send."),
714 }
715
716 self.state += 1;
717 Ok(())
718 }
719 }
720
721 const INIT_CALLED: u32 = 0x1;
722 const GETATTR_CALLED: u32 = 0x2;
723 const LOOKUP_CALLED: u32 = 0x4;
724
725 const INIT_REPLY: &[u8] = &[
726 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 31, 0, 0, 0, 0, 0, 2, 0, 41,
727 144, 12, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 0, 0, 0, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
728 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
729 ];
730
731 const GETATTR_REPLY: &[u8] = &[
732 120, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
733 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
734 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
735 0, 0, 237, 65, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
736 0,
737 ];
738
739 const LOOKUP_REPLY: &[u8] = &[
740 144, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
741 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
742 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
743 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0,
744 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
745 ];
746}