func createDevices(rootDir string, uid, gid int) error { nullDir := fp.Join(rootDir, os.DevNull) if err := osutil.Mknod(nullDir, unix.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil { return err } if err := os.Lchown(nullDir, uid, gid); err != nil { log.Debugf("Failed to lchown %s: %s\n", nullDir, err) return err } zeroDir := fp.Join(rootDir, "/dev/zero") if err := osutil.Mknod(zeroDir, unix.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil { return err } if err := os.Lchown(zeroDir, uid, gid); err != nil { log.Debugf("Failed to lchown %s: %s\n", zeroDir, err) return err } for _, f := range []string{"/dev/random", "/dev/urandom"} { randomDir := fp.Join(rootDir, f) if err := osutil.Mknod(randomDir, unix.S_IFCHR|uint32(os.FileMode(0666)), 1*256+9); err != nil { return err } if err := os.Lchown(randomDir, uid, gid); err != nil { log.Debugf("Failed to lchown %s: %s\n", randomDir, err) return err } } return nil }
func createDevices(rootDir string, uid, gid int) error { nullDir := fp.Join(rootDir, os.DevNull) if err := osutil.Mknod(nullDir, syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil { return err } if err := os.Lchown(nullDir, uid, gid); err != nil { return errwrap.Wrapff(err, "Failed to lchown %s: {{err}}", nullDir) } zeroDir := fp.Join(rootDir, "/dev/zero") if err := osutil.Mknod(zeroDir, syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil { return err } if err := os.Lchown(zeroDir, uid, gid); err != nil { return errwrap.Wrapff(err, "Failed to lchown %s:", zeroDir) } for _, f := range []string{"/dev/random", "/dev/urandom"} { randomDir := fp.Join(rootDir, f) if err := osutil.Mknod(randomDir, syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+9); err != nil { return err } if err := os.Lchown(randomDir, uid, gid); err != nil { return errwrap.Wrapff(err, "Failed to lchown %s: {{err}}", randomDir) } } return nil }
func NewUidShiftingFilePermEditor(uidRange *uid.UidRange) (FilePermissionsEditor, error) { if os.Geteuid() != 0 { return func(_ string, _, _ int, _ byte, _ os.FileInfo) error { // The files are owned by the current user on creation. // If we do nothing, they will remain so. return nil }, nil } return func(path string, uid, gid int, typ byte, fi os.FileInfo) error { shiftedUid, shiftedGid, err := uidRange.ShiftRange(uint32(uid), uint32(gid)) if err != nil { return err } if err := os.Lchown(path, int(shiftedUid), int(shiftedGid)); err != nil { return err } // lchown(2) says that, depending on the linux kernel version, it // can change the file's mode also if executed as root. So call // os.Chmod after it. if typ != tar.TypeSymlink { if err := os.Chmod(path, fi.Mode()); err != nil { return err } } return nil }, nil }
func main() { // Create a hard link // You will have two file names that point to the same contents // Changing the contents of one will change the other // Deleting/renaming one will not affect the other err := os.Link("original.txt", "original_also.txt") if err != nil { log.Fatal(err) } fmt.Println("creating sym") // Create a symlink err = os.Symlink("original.txt", "original_sym.txt") if err != nil { log.Fatal(err) } // Lstat will return file info, but if it is actually // a symlink, it will return info about the symlink. // It will not follow the link and give information // about the real file // Symlinks do not work in Windows fileInfo, err := os.Lstat("original_sym.txt") if err != nil { log.Fatal(err) } fmt.Printf("Link info: %+v", fileInfo) // Change ownership of a symlink only // and not the file it points to err = os.Lchown("original_sym.txt", os.Getuid(), os.Getgid()) if err != nil { log.Fatal(err) } }
func SetSymlinkUser(filename, username string, live bool) (bool, error) { userData, err := user.Lookup(username) if err != nil { return false, err } uid, err := strconv.Atoi(userData.Uid) if err != nil { return false, err } var stat syscall.Stat_t err = syscall.Lstat(filename, &stat) if err != nil { return false, err } if int(stat.Uid) == uid { return false, nil } if live { err = os.Lchown(filename, uid, int(stat.Gid)) if err != nil { return false, err } } return true, nil }
func (f *StatData) Set(which Field) (needswriting bool, e os.Error) { stat, e := Stat(f.Name) if e != nil { _, ispatherror := e.(*os.PathError) // *os.PathError means file doesn't exist (which is okay) if e != nil && !ispatherror { return false, e } return true, nil } if stat.Mode != f.Mode { return true, os.Remove(f.Name) } if Perms&which != 0 && stat.Perms != f.Perms { e = os.Chmod(f.Name, f.Perms) if e != nil { return false, e } } else { f.Perms = stat.Perms } // Set the ownership if needed... if Gid&which == 0 { f.Gid = stat.Gid } if Uid&which == 0 { f.Uid = stat.Uid } if Uid&which != 0 || Gid&which != 0 { e = os.Lchown(f.Name, f.Uid, f.Gid) if e != nil { return false, e } } return false, nil }
func EnsureFileOwner(destPath string, owner string, groupName string) (bool, error) { changed := false stat, err := os.Lstat(destPath) if err != nil { return changed, fmt.Errorf("error getting file stat for %q: %v", destPath, err) } user, err := LookupUser(owner) //user.Lookup(owner) if err != nil { return changed, fmt.Errorf("error looking up user %q: %v", owner, err) } group, err := LookupGroup(groupName) if err != nil { return changed, fmt.Errorf("error looking up group %q: %v", groupName, err) } if int(stat.Sys().(*syscall.Stat_t).Uid) == user.Uid && int(stat.Sys().(*syscall.Stat_t).Gid) == group.Gid { return changed, nil } glog.Infof("Changing file owner/group for %q to %s:%s", destPath, owner, group) err = os.Lchown(destPath, user.Uid, group.Gid) if err != nil { return changed, fmt.Errorf("error setting file owner/group for %q: %v", destPath, err) } changed = true return changed, nil }
func fixPermissions(source, destination string, uid, gid int) error { // The copied root permission should not be changed for previously existing // directories. s, err := os.Stat(destination) if err != nil && !os.IsNotExist(err) { return err } fixRootPermission := (err != nil) || !s.IsDir() // We Walk on the source rather than on the destination because we don't // want to change permissions on things we haven't created or modified. return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error { // Do not alter the walk root itself as it potentially existed before. if !fixRootPermission && (source == fullpath) { return nil } // Path is prefixed by source: substitute with destination instead. cleaned, err := filepath.Rel(source, fullpath) if err != nil { return err } fullpath = path.Join(destination, cleaned) return os.Lchown(fullpath, uid, gid) }) }
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error { // If the destination didn't already exist, or the destination isn't a // directory, then we should Lchown the destination. Otherwise, we shouldn't // Lchown the destination. destStat, err := os.Stat(destination) if err != nil { // This should *never* be reached, because the destination must've already // been created while untar-ing the context. return err } doChownDestination := !destExisted || !destStat.IsDir() // We Walk on the source rather than on the destination because we don't // want to change permissions on things we haven't created or modified. return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error { // Do not alter the walk root iff. it existed before, as it doesn't fall under // the domain of "things we should chown". if !doChownDestination && (source == fullpath) { return nil } // Path is prefixed by source: substitute with destination instead. cleaned, err := filepath.Rel(source, fullpath) if err != nil { return err } fullpath = path.Join(destination, cleaned) return os.Lchown(fullpath, uid, gid) }) }
func lchown(name string, uid, gid int) error { p, err := winPath(name) if err != nil { return newPathError("lchown", name, err) } return os.Lchown(p, uid, gid) }
func (node Node) restoreMetadata(path string) error { var err error err = os.Lchown(path, int(node.UID), int(node.GID)) if err != nil { return errors.Annotate(err, "Lchown") } if node.Type != "symlink" { err = os.Chmod(path, node.Mode) if err != nil { return errors.Annotate(err, "Chmod") } } if node.Type != "dir" { err = node.RestoreTimestamps(path) if err != nil { debug.Log("Node.restoreMetadata", "error restoring timestamps for dir %v: %v", path, err) return err } } return nil }
func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how string) error { convert := func(path string, fi os.FileInfo, err error) (e error) { uid, gid, err := getOwner(path) if err != nil { return err } var newuid, newgid int switch how { case "in": newuid, newgid = set.ShiftIntoNs(uid, gid) case "out": newuid, newgid = set.ShiftFromNs(uid, gid) } if testmode { fmt.Printf("I would shift %q to %d %d\n", path, newuid, newgid) } else { err = os.Lchown(path, int(newuid), int(newgid)) if err == nil { m := fi.Mode() if m&os.ModeSymlink == 0 { err = os.Chmod(path, m) if err != nil { fmt.Printf("Error resetting mode on %q, continuing\n", path) } } } } return nil } if !PathExists(dir) { return fmt.Errorf("No such file or directory: %q", dir) } return filepath.Walk(dir, convert) }
func (f *Directory) Set(which Field) (needswriting bool, e os.Error) { needswriting, e = f.StatData.Set(which) if e != nil { return } if needswriting { // This means it doesn't currently exist... e = os.Mkdir(f.Name, f.Perms) if e != nil { return } // Set owner... e = os.Lchown(f.Name, f.Uid, f.Gid) if e != nil { return false, e } // Chmod it in any case, because umask may have modified the // desired permissions. e = os.Chmod(f.Name, f.Perms) if e != nil { return false, e } } if Contents&which != 0 { for _, c := range f.Contents { // Set all the contents as well! c.Set(which) } } return }
// CopyDirectory copies the files under the source directory // to dest directory. The dest directory is created if it // does not exist. func CopyDirectory(source string, dest string) error { fi, err := os.Stat(source) if err != nil { return err } // Get owner. st, ok := fi.Sys().(*syscall.Stat_t) if !ok { return fmt.Errorf("could not convert to syscall.Stat_t") } // We have to pick an owner here anyway. if err := MkdirAllNewAs(dest, fi.Mode(), int(st.Uid), int(st.Gid)); err != nil { return err } return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Get the relative path relPath, err := filepath.Rel(source, path) if err != nil { return nil } if info.IsDir() { // Skip the source directory. if path != source { // Get the owner. st, ok := info.Sys().(*syscall.Stat_t) if !ok { return fmt.Errorf("could not convert to syscall.Stat_t") } uid := int(st.Uid) gid := int(st.Gid) if err := os.Mkdir(filepath.Join(dest, relPath), info.Mode()); err != nil { return err } if err := os.Lchown(filepath.Join(dest, relPath), uid, gid); err != nil { return err } } return nil } // Copy the file. if err := CopyFile(path, filepath.Join(dest, relPath)); err != nil { return err } return nil }) }
func fixPermissions(destination string, uid, gid int) error { return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error { if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) { return err } return nil }) }
func mkdirAll(path string, hdr Metadata) (stack []string, topMTime time.Time, err error) { // Following code derives from the golang standard library, so you can consider it BSDish if you like. // Our changes are licensed under Apache for the sake of overall license simplicity of the project. // Ref: https://github.com/golang/go/blob/883bc6ed0ea815293fe6309d66f967ea60630e87/src/os/path.go#L12 // Fast path: if we can tell whether path is a directory or file, stop with success or error. dir, err := os.Stat(path) if err == nil { if dir.IsDir() { return nil, dir.ModTime(), nil } return nil, dir.ModTime(), &os.PathError{"mkdir", path, syscall.ENOTDIR} } // Slow path: make sure parent exists and then call Mkdir for path. i := len(path) for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. i-- } j := i for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. j-- } if j > 1 { // Create parent stack, topMTime, err = mkdirAll(path[0:j-1], hdr) if err != nil { return stack, topMTime, err } } // Parent now exists; invoke Mkdir and use its result. err = os.Mkdir(path, 0755) if err != nil { // Handle arguments like "foo/." by // double-checking that directory doesn't exist. dir, err1 := os.Lstat(path) if err1 == nil && dir.IsDir() { return stack, topMTime, nil } return stack, topMTime, err } stack = append(stack, path) // Apply standardizations. if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { return stack, topMTime, err } if err := os.Chmod(path, hdr.FileMode()); err != nil { return stack, topMTime, err } // Except for time, because as usual with dirs, that requires walking backwards again at the end. // That'll be done one function out. return stack, topMTime, nil }
func extractOne(hdr *tar.Header, r io.Reader, root string, flag int) error { // clean before joining to remove all .. elements path := filepath.Join(root, filepath.Clean(hdr.Name)) targ := filepath.Join(root, filepath.Clean(hdr.Linkname)) switch hdr.Typeflag { case tar.TypeReg, tar.TypeRegA: f, err := os.Create(path) if err != nil { return err } if _, err = io.Copy(f, r); err != nil { return err } if err = f.Close(); err != nil { return err } case tar.TypeLink: if flag&Link != 0 { if err := os.Link(targ, path); err != nil { return err } } case tar.TypeSymlink: if flag&Symlink != 0 { if err := os.Symlink(targ, path); err != nil { return err } } case tar.TypeDir: if err := os.MkdirAll(path, 0777); err != nil { return err } case tar.TypeCont, tar.TypeXHeader, tar.TypeXGlobalHeader: return nil case tar.TypeChar, tar.TypeBlock, tar.TypeFifo: return fmt.Errorf("tarutil: unsupported type %q: %s", hdr.Typeflag, hdr.Name) } if flag&Chtimes != 0 { if err := os.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil { return err } } if flag&Chmod != 0 { mode := os.FileMode(hdr.Mode) if err := os.Chmod(path, mode); err != nil { return err } } if flag&Chown != 0 { uid, gid := hdr.Uid, hdr.Gid if err := os.Lchown(path, uid, gid); err != nil { return err } } return nil }
// Like os.MkdirAll but new components created have the given UID and GID. func mkdirAllWithOwner(absPath string, perm os.FileMode, uid, gid int) error { // From os/path.go. // Fast path: if we can tell whether path is a directory or file, stop with success or error. dir, err := os.Stat(absPath) if err == nil { if dir.IsDir() { return nil } return &os.PathError{"mkdir", absPath, syscall.ENOTDIR} } // Slow path: make sure parent exists and then call Mkdir for path. i := len(absPath) for i > 0 && os.IsPathSeparator(absPath[i-1]) { // Skip trailing path separator. i-- } j := i for j > 0 && !os.IsPathSeparator(absPath[j-1]) { // Scan backward over element. j-- } if j > 1 { // Create parent err = mkdirAllWithOwner(absPath[0:j-1], perm, uid, gid) if err != nil { return err } } // Parent now exists; invoke Mkdir and use its result. err = os.Mkdir(absPath, perm) if err != nil { // Handle arguments like "foo/." by double-checking that directory // doesn't exist. dir, err1 := os.Lstat(absPath) if err1 == nil && dir.IsDir() { return nil } return err } if uid >= 0 || gid >= 0 { if uid < 0 { uid = os.Getuid() } if gid < 0 { gid = os.Getgid() } err = os.Lchown(absPath, uid, gid) // ignore errors in case we aren't root log.Errore(err, "cannot chown ", absPath) } return nil }
func (inode *SpecialInode) writeMetadata(name string) error { if err := os.Lchown(name, int(inode.Uid), int(inode.Gid)); err != nil { return err } if err := syscall.Chmod(name, uint32(inode.Mode)); err != nil { return err } t := time.Unix(inode.MtimeSeconds, int64(inode.MtimeNanoSeconds)) return os.Chtimes(name, t, t) }
// Chown implements pathfs.Filesystem. func (fs *FS) Chown(path string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.getBackingPath(path) if err != nil { return fuse.ToStatus(err) } return fuse.ToStatus(os.Lchown(cPath, int(uid), int(gid))) }
func (d *driver) Lchown(name, uidStr, gidStr string) error { uid, err := strconv.Atoi(uidStr) if err != nil { return err } gid, err := strconv.Atoi(gidStr) if err != nil { return err } return os.Lchown(name, uid, gid) }
// Symlink implements pathfs.Filesystem. func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) { tlog.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName) if fs.isFiltered(linkName) { return fuse.EPERM } cPath, err := fs.getBackingPath(linkName) if err != nil { return fuse.ToStatus(err) } if fs.args.PlaintextNames { err = os.Symlink(target, cPath) return fuse.ToStatus(err) } // Symlinks are encrypted like file contents (GCM) and base64-encoded cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil) cTarget := base64.URLEncoding.EncodeToString(cBinTarget) // Handle long file name cName := filepath.Base(cPath) if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() // Create ".name" file err = fs.nameTransform.WriteLongName(dirfd, cName, linkName) if err != nil { return fuse.ToStatus(err) } // Create "gocryptfs.longfile." symlink // TODO use syscall.Symlinkat once it is available in Go err = syscall.Symlink(cTarget, cPath) if err != nil { nametransform.DeleteLongName(dirfd, cName) } } else { // Create symlink err = os.Symlink(cTarget, cPath) } if err != nil { return fuse.ToStatus(err) } // Set owner if fs.args.PreserveOwner { err = os.Lchown(cPath, int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mknod: Lchown failed: %v", err) } } return fuse.OK }
func processZipUIDGidField(data []byte, file *zip.FileHeader) error { var ugField ZipUIDGidField err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &ugField) if err != nil { return err } if !(ugField.Version == 1 && ugField.UIDSize == 4 && ugField.GIDSize == 4) { return errors.New("uid/gid data not supported") } return os.Lchown(file.Name, int(ugField.UID), int(ugField.Gid)) }
func (inode *DirectoryInode) write(name string) error { if inode.make(name) != nil { os.RemoveAll(name) if err := inode.make(name); err != nil { return err } } if err := os.Lchown(name, int(inode.Uid), int(inode.Gid)); err != nil { return err } if inode.Mode & ^modePerm != syscall.S_IFDIR { if err := syscall.Chmod(name, uint32(inode.Mode)); err != nil { return err } } return nil }
// Mknod implements pathfs.Filesystem. func (fs *FS) Mknod(path string, mode uint32, dev uint32, context *fuse.Context) (code fuse.Status) { if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.getBackingPath(path) if err != nil { return fuse.ToStatus(err) } // Create ".name" file to store long file name cName := filepath.Base(cPath) if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() err = fs.nameTransform.WriteLongName(dirfd, cName, path) if err != nil { return fuse.ToStatus(err) } // Create "gocryptfs.longfile." device node err = syscallcompat.Mknodat(int(dirfd.Fd()), cName, mode, int(dev)) if err != nil { nametransform.DeleteLongName(dirfd, cName) } } else { // Create regular device node err = syscall.Mknod(cPath, mode, int(dev)) } if err != nil { return fuse.ToStatus(err) } // Set owner if fs.args.PreserveOwner { err = os.Lchown(cPath, int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mknod: Lchown failed: %v", err) } } return fuse.OK }
func shift(path string, amount int) { info, err := os.Lstat(path) if err != nil { panic(err) } if err := os.Lchown(path, int(info.Sys().(*syscall.Stat_t).Uid+uint32(amount)), int(info.Sys().(*syscall.Stat_t).Gid+uint32(amount))); err != nil { panic(err) } if info.IsDir() { children, err := ioutil.ReadDir(path) if err != nil { panic(err) } for _, child := range children { shift(path+"/"+child.Name(), amount) } } }
func SetSymlinkGroup(filename, groupname string, live bool) (bool, error) { file, err := os.Open("/etc/group") if err != nil { return false, err } defer file.Close() scanner := bufio.NewScanner(file) gid := -1 for scanner.Scan() { line := strings.Split(scanner.Text(), ":") if line[0] == groupname { gid, err = strconv.Atoi(line[2]) if err != nil { return false, err } break } } err = scanner.Err() if err != nil { return false, err } if gid < 0 { return false, fmt.Errorf("could not lookup group %q", groupname) } var stat syscall.Stat_t err = syscall.Lstat(filename, &stat) if err != nil { return false, err } if int(stat.Gid) == gid { return false, nil } if live { err = os.Lchown(filename, int(stat.Uid), gid) if err != nil { return false, err } } return true, nil }
func main() { fi, err := os.Stat("Hello.go") if err != nil { fmt.Printf("Error: %s\n", err) return } fmt.Printf("Hello.go: uid=%d, gid=%d\n", fi.Sys().(*syscall.Stat_t).Uid, fi.Sys().(*syscall.Stat_t).Gid) err = os.Lchown("Hello.go", 99, 99) //nobody, nobody if err != nil { fmt.Printf("Error: %s\n", err) return } fi, err = os.Stat("Hello.go") if err != nil { fmt.Printf("Error: %s\n", err) return } fmt.Printf("Now Hello.go: uid=%d, gid=%d\n", fi.Sys().(*syscall.Stat_t).Uid, fi.Sys().(*syscall.Stat_t).Gid) }
// Creates a directory dst from the information found in src // First error is fatal, other errors are issues replicating attributes func MkdirFrom(src, dst string) (error, []error) { var errs []error src_st, err := os.Stat(src) if err != nil { return err, nil } err = os.Mkdir(dst, src_st.Mode()) if err != nil && !os.IsExist(err) { return err, nil } if stat, ok := src_st.Sys().(*syscall.Stat_t); ok { err = os.Lchown(dst, int(stat.Uid), int(stat.Gid)) if err != nil { errs = append(errs, err) } atime := time.Unix(stat.Atim.Sec, stat.Atim.Nsec) err = os.Chtimes(dst, atime, src_st.ModTime()) if err != nil { errs = append(errs, err) } } xattr, values, err := attrs.GetList(src) if err != nil { errs = append(errs, err) } for i, attrname := range xattr { err = attrs.Set(src, attrname, values[i]) if err != nil { errs = append(errs, err) } } return nil, errs }
func (cnt *Img) Init() { initPath := cnt.path if cnt.args.Path != "" { initPath = cnt.args.Path } log.Get().Info("Setting up files three") uid := "0" gid := "0" if os.Getenv("SUDO_UID") != "" { uid = os.Getenv("SUDO_UID") gid = os.Getenv("SUDO_GID") } uidInt, err := strconv.Atoi(uid) gidInt, err := strconv.Atoi(gid) if err != nil { log.Get().Panic(err) } folderList := []string{ RUNLEVELS, RUNLEVELS_PRESTART, RUNLEVELS_LATESTART, RUNLEVELS_BUILD, RUNLEVELS_BUILD_SETUP, RUNLEVELS_BUILD_INHERIT_EARLY, RUNLEVELS_BUILD_INHERIT_LATE, CONFD, CONFD_TEMPLATE, CONFD_CONFIG, ATTRIBUTES, FILES_PATH, } for _, folder := range folderList { fpath := initPath + "/" + folder os.MkdirAll(fpath, 0777) os.Lchown(fpath, uidInt, gidInt) } }