acl_rs/
lib.rs

1#[cfg(test)]
2mod tests {
3    #[test]
4    fn it_works() {
5        use crate::*;
6        let mut acl = Acl::init(10).unwrap();
7        assert_eq!(acl.valid().unwrap(), Some(nix::errno::Errno::EINVAL));
8
9        let mut entry = acl.create_entry().unwrap();
10        let tag = AclTag::UserObj;
11        entry.set_tag_type(&tag).unwrap();
12        let mut permset = entry.get_permset().unwrap();
13        permset.add_perm(AclPerm::Read).unwrap();
14        assert_eq!(entry.get_tag_type().unwrap(), tag);
15
16        let mut entry = acl.create_entry().unwrap();
17        let tag = AclTag::GroupObj;
18        entry.set_tag_type(&tag).unwrap();
19        let mut permset = entry.get_permset().unwrap();
20        permset.add_perm(AclPerm::Write).unwrap();
21        assert_eq!(entry.get_tag_type().unwrap(), tag);
22
23        let mut entry = acl.create_entry().unwrap();
24        let tag = AclTag::Other;
25        entry.set_tag_type(&tag).unwrap();
26        let mut permset = entry.get_permset().unwrap();
27        permset.add_perm(AclPerm::Execute).unwrap();
28        assert_eq!(entry.get_tag_type().unwrap(), tag);
29
30        let mut entry = acl.create_entry().unwrap();
31        let tag = AclTag::User;
32        entry.set_tag_type(&tag).unwrap();
33        entry
34            .set_qualifier(&Qualifier::User(nix::unistd::Uid::from_raw(0)))
35            .unwrap();
36        let mut permset = entry.get_permset().unwrap();
37        permset.add_perm(AclPerm::Execute).unwrap();
38        permset.add_perm(AclPerm::Read).unwrap();
39        permset.add_perm(AclPerm::Write).unwrap();
40        assert_eq!(entry.get_tag_type().unwrap(), tag);
41
42        // mask is necessary when User/Group is set
43        acl.calc_mask().unwrap();
44        assert_eq!(entry.get_tag_type().unwrap(), tag);
45        assert_eq!(acl.valid().unwrap(), None);
46
47        let path = std::path::PathBuf::from("./file_to_test_acls");
48        if !path.exists() {
49            std::fs::File::create(&path).unwrap();
50        }
51        acl.set_for_file(&path, &AclType::TypeAccess).unwrap();
52
53        let new_acl = Acl::for_file(&path, &AclType::TypeAccess).unwrap();
54        std::fs::remove_file(&path).unwrap();
55        let new_alc_str = String::from_utf8(new_acl.to_text().unwrap()).unwrap();
56        assert_eq!(
57            "user::r--\nuser:root:rwx\ngroup::-w-\nmask::rwx\nother::--x\n",
58            &new_alc_str
59        );
60
61        // There are 5 entries: userobj, user, group, other, mask
62        // TODO check those permsets. I dont know how that works. Linux has an extension "acl_get_perm" but thats not exposed by acl_sys?
63        let entry1 = new_acl.get_entry(&EntryId::FirstEntry).unwrap().unwrap();
64        let _permset1 = entry1.get_permset().unwrap();
65
66        let entry2 = new_acl.get_entry(&EntryId::NextEntry).unwrap().unwrap();
67        let _permset2 = entry2.get_permset().unwrap();
68
69        let entry3 = new_acl.get_entry(&EntryId::NextEntry).unwrap().unwrap();
70        let _permset3 = entry3.get_permset().unwrap();
71
72        let entry4 = new_acl.get_entry(&EntryId::NextEntry).unwrap().unwrap();
73        let _permset4 = entry4.get_permset().unwrap();
74
75        let entry5 = new_acl.get_entry(&EntryId::NextEntry).unwrap().unwrap();
76        let _permset5 = entry5.get_permset().unwrap();
77
78        let err_entry = new_acl.get_entry(&EntryId::NextEntry).unwrap();
79        assert_eq!(err_entry, None);
80    }
81}
82
83extern crate acl_sys;
84extern crate nix;
85
86use std::cell::RefCell;
87use std::rc::Rc;
88
89#[derive(PartialEq, Eq, Debug)]
90pub struct AclEntry {
91    raw_ptr: acl_sys::acl_entry_t,
92    // if the parent acl was moved this has to become invalid
93    is_valid: Rc<RefCell<bool>>,
94}
95
96pub enum AclPerm {
97    Read,
98    Write,
99    Execute,
100}
101
102impl AclPerm {
103    pub fn to_raw(&self) -> u32 {
104        match self {
105            AclPerm::Read => acl_sys::ACL_READ,
106            AclPerm::Write => acl_sys::ACL_WRITE,
107            AclPerm::Execute => acl_sys::ACL_EXECUTE,
108        }
109    }
110}
111
112pub struct AclPermSet {
113    raw_ptr: acl_sys::acl_permset_t,
114    // if the parent acl was moved this has to become invalid
115    is_valid: Rc<RefCell<bool>>,
116}
117
118#[derive(Copy, Clone, PartialEq, Eq, Debug)]
119pub enum AclType {
120    TypeAccess,
121    TypeDefault,
122}
123
124impl AclType {
125    pub fn to_raw(&self) -> u32 {
126        match self {
127            AclType::TypeAccess => acl_sys::ACL_TYPE_ACCESS,
128            AclType::TypeDefault => acl_sys::ACL_TYPE_DEFAULT,
129        }
130    }
131}
132
133#[derive(Copy, Clone, PartialEq, Eq, Debug)]
134pub enum AclTag {
135    UserObj,
136    User,
137    GroupObj,
138    Group,
139    Mask,
140    Other,
141    Invalid,
142}
143
144impl AclTag {
145    pub fn to_raw(&self) -> i32 {
146        match self {
147            AclTag::UserObj => acl_sys::ACL_USER_OBJ,
148            AclTag::User => acl_sys::ACL_USER,
149            AclTag::GroupObj => acl_sys::ACL_GROUP_OBJ,
150            AclTag::Group => acl_sys::ACL_GROUP,
151            AclTag::Mask => acl_sys::ACL_MASK,
152            AclTag::Other => acl_sys::ACL_OTHER,
153            AclTag::Invalid => acl_sys::ACL_UNDEFINED_TAG,
154        }
155    }
156    pub fn from_raw(raw: i32) -> Self {
157        match raw {
158            acl_sys::ACL_USER_OBJ => AclTag::UserObj,
159            acl_sys::ACL_USER => AclTag::User,
160            acl_sys::ACL_GROUP_OBJ => AclTag::GroupObj,
161            acl_sys::ACL_GROUP => AclTag::Group,
162            acl_sys::ACL_MASK => AclTag::Mask,
163            acl_sys::ACL_OTHER => AclTag::Other,
164            _ => AclTag::Invalid,
165        }
166    }
167}
168
169#[derive(Debug)]
170pub struct Acl {
171    raw_ptr: acl_sys::acl_t,
172
173    // if the acl is moved in create_entry this is set to true
174    // and replaced with a new RefCell
175    is_valid: Rc<RefCell<bool>>,
176}
177
178#[derive(Clone, Copy, Debug)]
179pub enum AclError {
180    Errno(nix::errno::Errno),
181    UnknownReturn(i32),
182    WasMoved,
183}
184
185#[derive(Clone, Copy, Debug)]
186pub enum EntryId {
187    NextEntry,
188    FirstEntry,
189}
190
191pub enum Qualifier {
192    User(nix::unistd::Uid),
193    Group(nix::unistd::Gid),
194}
195
196impl EntryId {
197    fn to_raw(&self) -> i32 {
198        match self {
199            // TODO check if thats the same for bsds
200            EntryId::NextEntry => 1,
201            EntryId::FirstEntry => 0,
202        }
203    }
204}
205
206impl Acl {
207    pub fn init(count: i32) -> Result<Self, AclError> {
208        let raw_ptr = unsafe { acl_sys::acl_init(count as i32) };
209        if raw_ptr.is_null() {
210            let errno = nix::errno::errno();
211            Err(AclError::Errno(nix::errno::from_i32(errno)))
212        } else {
213            Ok(Acl {
214                raw_ptr,
215                is_valid: Rc::new(RefCell::new(true)),
216            })
217        }
218    }
219
220    pub fn from_text(text: &str) -> Result<Self, AclError> {
221        let text_i8 = text.bytes().map(|x| x as i8).collect::<Vec<_>>();
222        let raw_ptr = unsafe { acl_sys::acl_from_text(text_i8.as_ptr()) };
223        if raw_ptr.is_null() {
224            let errno = nix::errno::errno();
225            Err(AclError::Errno(nix::errno::from_i32(errno)))
226        } else {
227            Ok(Acl {
228                raw_ptr,
229                is_valid: Rc::new(RefCell::new(true)),
230            })
231        }
232    }
233
234    pub fn to_text(&self) -> Result<Vec<u8>, AclError> {
235        let mut len = 0;
236        let raw_text = unsafe { acl_sys::acl_to_text(self.raw_ptr, &mut len) };
237
238        if raw_text.is_null() {
239            let errno = nix::errno::errno();
240            Err(AclError::Errno(nix::errno::from_i32(errno)))
241        } else {
242            let mut text_bytes = Vec::new();
243            let mut iter_ptr = raw_text;
244            for _idx in 0..len {
245                let byte = unsafe { *iter_ptr } as u8;
246                text_bytes.push(byte);
247                iter_ptr = unsafe { iter_ptr.add(1) };
248            }
249
250            unsafe { acl_sys::acl_free(std::mem::transmute(raw_text)) };
251
252            Ok(text_bytes)
253        }
254    }
255
256    pub fn for_fd(fd: i32) -> Result<Self, AclError> {
257        let raw_ptr = unsafe { acl_sys::acl_get_fd(fd) };
258        if raw_ptr.is_null() {
259            let errno = nix::errno::errno();
260            Err(AclError::Errno(nix::errno::from_i32(errno)))
261        } else {
262            Ok(Acl {
263                raw_ptr,
264                is_valid: Rc::new(RefCell::new(true)),
265            })
266        }
267    }
268
269    pub fn set_for_fd(&self, fd: i32) -> Result<(), AclError> {
270        let result = unsafe { acl_sys::acl_set_fd(fd, self.raw_ptr) };
271        match result {
272            0 => Ok(()),
273            -1 => {
274                let errno = nix::errno::errno();
275                Err(AclError::Errno(nix::errno::from_i32(errno)))
276            }
277            _ => Err(AclError::UnknownReturn(result)),
278        }
279    }
280
281    pub fn for_file(file_path: &std::path::PathBuf, typ: &AclType) -> Result<Self, AclError> {
282        use std::os::unix::ffi::OsStrExt;
283        let path_bytes = file_path.as_os_str().as_bytes().to_vec();
284        let path_i8 = path_bytes.into_iter().map(|x| x as i8).collect::<Vec<_>>();
285        let raw_ptr = unsafe { acl_sys::acl_get_file(path_i8.as_ptr(), typ.to_raw()) };
286        if raw_ptr.is_null() {
287            let errno = nix::errno::errno();
288            Err(AclError::Errno(nix::errno::from_i32(errno)))
289        } else {
290            Ok(Acl {
291                raw_ptr,
292                is_valid: Rc::new(RefCell::new(true)),
293            })
294        }
295    }
296
297    pub fn set_for_file(
298        &self,
299        file_path: &std::path::PathBuf,
300        typ: &AclType,
301    ) -> Result<(), AclError> {
302        use std::os::unix::ffi::OsStrExt;
303        let path_bytes = file_path.as_os_str().as_bytes().to_vec();
304        let path_i8 = path_bytes.into_iter().map(|x| x as i8).collect::<Vec<_>>();
305        let result = unsafe { acl_sys::acl_set_file(path_i8.as_ptr(), typ.to_raw(), self.raw_ptr) };
306        match result {
307            0 => Ok(()),
308            -1 => {
309                let errno = nix::errno::errno();
310                Err(AclError::Errno(nix::errno::from_i32(errno)))
311            }
312            _ => Err(AclError::UnknownReturn(result)),
313        }
314    }
315
316    pub fn get_entry(&self, id: &EntryId) -> Result<Option<AclEntry>, AclError> {
317        let mut entry_ptr = std::ptr::null_mut();
318        let entry_ptr_ptr = &mut entry_ptr as *mut acl_sys::acl_entry_t;
319        let result = unsafe { acl_sys::acl_get_entry(self.raw_ptr, id.to_raw(), entry_ptr_ptr) };
320
321        match result {
322            0 => Ok(None),
323            1 => Ok(Some(AclEntry {
324                raw_ptr: entry_ptr,
325                is_valid: self.is_valid.clone(),
326            })),
327            -1 => {
328                let errno = nix::errno::errno();
329                Err(AclError::Errno(nix::errno::from_i32(errno)))
330            }
331            _ => Err(AclError::UnknownReturn(result)),
332        }
333    }
334
335    /// This is dangerous. The lifetime of all permsets and entries is bound to the liftime of the Acls raw_pointer.
336    /// Since this operation might move the Acl to a bigger allocation this might introduce unsoundness.
337    ///
338    /// This should be prevented since we check in each call if is_valid is still true but it is pretty hacky.
339    pub fn create_entry(&mut self) -> Result<AclEntry, AclError> {
340        let mut entry_ptr = std::ptr::null_mut();
341        let entry_ptr_ptr = &mut entry_ptr as *mut acl_sys::acl_entry_t;
342
343        let acl_ptr_before = self.raw_ptr;
344        let result = unsafe { acl_sys::acl_create_entry(&mut self.raw_ptr, entry_ptr_ptr) };
345
346        if !acl_ptr_before.eq(&self.raw_ptr) {
347            // The acl was moved. Need to invalidate all entries/permsets
348            *self.is_valid.borrow_mut() = false;
349            // The new acl is obviously valid again
350            self.is_valid = Rc::new(RefCell::new(true));
351        }
352
353        match result {
354            0 => Ok(AclEntry {
355                raw_ptr: entry_ptr,
356                is_valid: self.is_valid.clone(),
357            }),
358            -1 => {
359                let errno = nix::errno::errno();
360                Err(AclError::Errno(nix::errno::from_i32(errno)))
361            }
362            _ => Err(AclError::UnknownReturn(result)),
363        }
364    }
365
366    /// consumes the entry but returns it if an error occurs
367    pub fn delete_entry(&mut self, entry: AclEntry) -> Result<(), (AclEntry, AclError)> {
368        let result = unsafe { acl_sys::acl_delete_entry(self.raw_ptr, entry.raw_ptr) };
369
370        match result {
371            0 => Ok(()),
372            -1 => {
373                let errno = nix::errno::errno();
374                Err((entry, AclError::Errno(nix::errno::from_i32(errno))))
375            }
376            _ => Err((entry, AclError::UnknownReturn(result))),
377        }
378    }
379
380    /// Use with care. Acl may not be used after this.
381    /// This will also be called when dropped so maybe just let drop handle this
382    pub fn free(mut self) -> Result<(), (Self, AclError)> {
383        let result = unsafe { acl_sys::acl_free(self.raw_ptr) };
384        match result {
385            0 => {
386                self.raw_ptr = std::ptr::null_mut();
387                Ok(())
388            }
389            -1 => {
390                let errno = nix::errno::errno();
391                Err((self, AclError::Errno(nix::errno::from_i32(errno))))
392            }
393            _ => Err((self, AclError::UnknownReturn(result))),
394        }
395    }
396
397    pub fn calc_mask(&mut self) -> Result<(), AclError> {
398        let result = unsafe { acl_sys::acl_calc_mask(&mut self.raw_ptr) };
399        match result {
400            0 => Ok(()),
401            -1 => {
402                let errno = nix::errno::errno();
403                Err(AclError::Errno(nix::errno::from_i32(errno)))
404            }
405            _ => Err(AclError::UnknownReturn(result)),
406        }
407    }
408
409    pub fn dup(&self) -> Result<Acl, AclError> {
410        let new_acl = unsafe { acl_sys::acl_dup(self.raw_ptr) };
411
412        if new_acl.is_null() {
413            let errno = nix::errno::errno();
414            Err(AclError::Errno(nix::errno::from_i32(errno)))
415        } else {
416            Ok(Acl {
417                raw_ptr: new_acl,
418                is_valid: self.is_valid.clone(),
419            })
420        }
421    }
422
423    pub fn size(&self) -> Result<usize, AclError> {
424        let result = unsafe { acl_sys::acl_size(self.raw_ptr) };
425        match result {
426            -1 => {
427                let errno = nix::errno::errno();
428                Err(AclError::Errno(nix::errno::from_i32(errno)))
429            }
430            _ => Ok(result as usize),
431        }
432    }
433
434    pub fn valid(&self) -> Result<Option<nix::errno::Errno>, AclError> {
435        let result = unsafe { acl_sys::acl_valid(self.raw_ptr) };
436        match result {
437            0 => Ok(None),
438            -1 => Ok(Some(nix::errno::from_i32(nix::errno::errno()))),
439            _ => Err(AclError::UnknownReturn(result)),
440        }
441    }
442}
443
444impl Drop for Acl {
445    fn drop(&mut self) {
446        unsafe { acl_sys::acl_free(self.raw_ptr) };
447        self.raw_ptr = std::ptr::null_mut();
448    }
449}
450
451impl AclPermSet {
452    pub fn check_valid(&self) -> Result<(), AclError> {
453        if *self.is_valid.borrow() == true {
454            Ok(())
455        } else {
456            Err(AclError::WasMoved)
457        }
458    }
459
460    pub fn add_perm(&mut self, perm: AclPerm) -> Result<(), AclError> {
461        self.check_valid()?;
462        let result = unsafe { acl_sys::acl_add_perm(self.raw_ptr, perm.to_raw()) };
463        match result {
464            0 => Ok(()),
465            -1 => {
466                let errno = nix::errno::errno();
467                Err(AclError::Errno(nix::errno::from_i32(errno)))
468            }
469            _ => Err(AclError::UnknownReturn(result)),
470        }
471    }
472
473    pub fn delete_perms(&mut self, perm: AclPerm) -> Result<(), AclError> {
474        self.check_valid()?;
475        let result = unsafe { acl_sys::acl_delete_perms(self.raw_ptr, perm.to_raw()) };
476        match result {
477            0 => Ok(()),
478            -1 => {
479                let errno = nix::errno::errno();
480                Err(AclError::Errno(nix::errno::from_i32(errno)))
481            }
482            _ => Err(AclError::UnknownReturn(result)),
483        }
484    }
485
486    pub fn clear_perms(&mut self) -> Result<(), AclError> {
487        self.check_valid()?;
488        let result = unsafe { acl_sys::acl_clear_perms(self.raw_ptr) };
489        match result {
490            0 => Ok(()),
491            -1 => {
492                let errno = nix::errno::errno();
493                Err(AclError::Errno(nix::errno::from_i32(errno)))
494            }
495            _ => Err(AclError::UnknownReturn(result)),
496        }
497    }
498}
499
500impl AclEntry {
501    pub fn check_valid(&self) -> Result<(), AclError> {
502        if *self.is_valid.borrow() == true {
503            Ok(())
504        } else {
505            Err(AclError::WasMoved)
506        }
507    }
508
509    pub fn copy_to(&self, dest: &mut AclEntry) -> Result<(), AclError> {
510        self.check_valid()?;
511        let result = unsafe { acl_sys::acl_copy_entry(dest.raw_ptr, self.raw_ptr) };
512        match result {
513            0 => Ok(()),
514            -1 => {
515                let errno = nix::errno::errno();
516                Err(AclError::Errno(nix::errno::from_i32(errno)))
517            }
518            _ => Err(AclError::UnknownReturn(result)),
519        }
520    }
521
522    pub fn get_permset(&self) -> Result<AclPermSet, AclError> {
523        self.check_valid()?;
524        let mut permset_ptr = std::ptr::null_mut();
525        let permset_ptr_ptr = &mut permset_ptr as *mut acl_sys::acl_entry_t;
526        let result = unsafe { acl_sys::acl_get_permset(self.raw_ptr, permset_ptr_ptr) };
527
528        match result {
529            0 => Ok(AclPermSet {
530                raw_ptr: permset_ptr,
531                is_valid: self.is_valid.clone(),
532            }),
533            -1 => {
534                let errno = nix::errno::errno();
535                Err(AclError::Errno(nix::errno::from_i32(errno)))
536            }
537            _ => Err(AclError::UnknownReturn(result)),
538        }
539    }
540
541    pub fn set_permset(&mut self, permset: &AclPermSet) -> Result<(), AclError> {
542        self.check_valid()?;
543        let result = unsafe { acl_sys::acl_set_permset(self.raw_ptr, permset.raw_ptr) };
544
545        match result {
546            0 => Ok(()),
547            -1 => {
548                let errno = nix::errno::errno();
549                Err(AclError::Errno(nix::errno::from_i32(errno)))
550            }
551            _ => Err(AclError::UnknownReturn(result)),
552        }
553    }
554
555    pub fn get_tag_type(&self) -> Result<AclTag, AclError> {
556        self.check_valid()?;
557        let mut raw = 0 as acl_sys::acl_tag_t;
558        let raw_ptr = &mut raw as *mut acl_sys::acl_tag_t;
559        let result = unsafe { acl_sys::acl_get_tag_type(self.raw_ptr, raw_ptr) };
560
561        match result {
562            0 => Ok(AclTag::from_raw(raw)),
563            -1 => {
564                let errno = nix::errno::errno();
565                Err(AclError::Errno(nix::errno::from_i32(errno)))
566            }
567            _ => Err(AclError::UnknownReturn(result)),
568        }
569    }
570
571    pub fn set_tag_type(&mut self, tag_type: &AclTag) -> Result<(), AclError> {
572        self.check_valid()?;
573        let result = unsafe { acl_sys::acl_set_tag_type(self.raw_ptr, tag_type.to_raw()) };
574
575        match result {
576            0 => Ok(()),
577            -1 => {
578                let errno = nix::errno::errno();
579                Err(AclError::Errno(nix::errno::from_i32(errno)))
580            }
581            _ => Err(AclError::UnknownReturn(result)),
582        }
583    }
584
585    pub fn get_qualifier(&self) -> Result<Qualifier, AclError> {
586        self.check_valid()?;
587        match self.get_tag_type()? {
588            AclTag::User => {
589                let raw_ptr = unsafe { acl_sys::acl_get_qualifier(self.raw_ptr) };
590                if raw_ptr.is_null() {
591                    let errno = nix::errno::errno();
592                    Err(AclError::Errno(nix::errno::from_i32(errno)))
593                } else {
594                    let raw: u32 = unsafe { *(std::mem::transmute::<_, *const u32>(raw_ptr)) };
595                    Ok(Qualifier::User(nix::unistd::Uid::from_raw(raw)))
596                }
597            }
598            AclTag::Group => {
599                let raw_ptr = unsafe { acl_sys::acl_get_qualifier(self.raw_ptr) };
600                if raw_ptr.is_null() {
601                    let errno = nix::errno::errno();
602                    Err(AclError::Errno(nix::errno::from_i32(errno)))
603                } else {
604                    let raw: u32 = unsafe { *(std::mem::transmute::<_, *const u32>(raw_ptr)) };
605                    Ok(Qualifier::User(nix::unistd::Uid::from_raw(raw)))
606                }
607            }
608            _ => return Err(AclError::Errno(nix::errno::Errno::EINVAL)),
609        }
610    }
611
612    pub fn set_qualifier(&mut self, qual: &Qualifier) -> Result<(), AclError> {
613        self.check_valid()?;
614        let result = match qual {
615            Qualifier::User(id) => {
616                let raw = id.as_raw();
617                let raw_ptr = &raw;
618                unsafe { acl_sys::acl_set_qualifier(self.raw_ptr, std::mem::transmute(raw_ptr)) }
619            }
620            Qualifier::Group(id) => unsafe {
621                acl_sys::acl_set_qualifier(self.raw_ptr, std::mem::transmute(&id.as_raw()))
622            },
623        };
624        match result {
625            0 => Ok(()),
626            -1 => {
627                let errno = nix::errno::errno();
628                Err(AclError::Errno(nix::errno::from_i32(errno)))
629            }
630            _ => Err(AclError::UnknownReturn(result)),
631        }
632    }
633}
634
635pub fn delete_def_file(file_path: &std::path::PathBuf) -> Result<(), AclError> {
636    use std::os::unix::ffi::OsStrExt;
637    let path_bytes = file_path.as_os_str().as_bytes().to_vec();
638    let path_i8 = path_bytes.into_iter().map(|x| x as i8).collect::<Vec<_>>();
639    let path_ptr = path_i8.as_ptr();
640
641    let result = unsafe { acl_sys::acl_delete_def_file(path_ptr) };
642    match result {
643        0 => Ok(()),
644        -1 => {
645            let errno = nix::errno::errno();
646            Err(AclError::Errno(nix::errno::from_i32(errno)))
647        }
648        _ => Err(AclError::UnknownReturn(result)),
649    }
650}