// renameFile attempts to rename an existing file to a destination // and set the right attributes on it. func (f *rwFolder) renameFile(source, target protocol.FileInfo) { // Used in the defer closure below, updated by the function body. Take // care not declare another err. var err error events.Default.Log(events.ItemStarted, map[string]string{ "folder": f.folderID, "item": source.Name, "type": "file", "action": "delete", }) events.Default.Log(events.ItemStarted, map[string]string{ "folder": f.folderID, "item": target.Name, "type": "file", "action": "update", }) defer func() { events.Default.Log(events.ItemFinished, map[string]interface{}{ "folder": f.folderID, "item": source.Name, "error": events.Error(err), "type": "file", "action": "delete", }) events.Default.Log(events.ItemFinished, map[string]interface{}{ "folder": f.folderID, "item": target.Name, "error": events.Error(err), "type": "file", "action": "update", }) }() l.Debugln(f, "taking rename shortcut", source.Name, "->", target.Name) from, err := rootedJoinedPath(f.dir, source.Name) if err != nil { f.newError(source.Name, err) return } to, err := rootedJoinedPath(f.dir, target.Name) if err != nil { f.newError(target.Name, err) return } if f.versioner != nil { err = osutil.Copy(from, to) if err == nil { err = osutil.InWritableDir(f.versioner.Archive, from) } } else { err = osutil.TryRename(from, to) } if err == nil { // The file was renamed, so we have handled both the necessary delete // of the source and the creation of the target. Fix-up the metadata, // and update the local index of the target file. f.dbUpdates <- dbUpdateJob{source, dbUpdateDeleteFile} err = f.shortcutFile(target) if err != nil { l.Infof("Puller (folder %q, file %q): rename from %q metadata: %v", f.folderID, target.Name, source.Name, err) f.newError(target.Name, err) return } f.dbUpdates <- dbUpdateJob{target, dbUpdateHandleFile} } else { // We failed the rename so we have a source file that we still need to // get rid of. Attempt to delete it instead so that we make *some* // progress. The target is unhandled. err = osutil.InWritableDir(os.Remove, from) if err != nil { l.Infof("Puller (folder %q, file %q): delete %q after failed rename: %v", f.folderID, target.Name, source.Name, err) f.newError(target.Name, err) return } f.dbUpdates <- dbUpdateJob{source, dbUpdateDeleteFile} } }
// renameFile attempts to rename an existing file to a destination // and set the right attributes on it. func (p *rwFolder) renameFile(source, target protocol.FileInfo) { var err error events.Default.Log(events.ItemStarted, map[string]string{ "folder": p.folder, "item": source.Name, "type": "file", "action": "delete", }) events.Default.Log(events.ItemStarted, map[string]string{ "folder": p.folder, "item": target.Name, "type": "file", "action": "update", }) defer func() { events.Default.Log(events.ItemFinished, map[string]interface{}{ "folder": p.folder, "item": source.Name, "error": events.Error(err), "type": "file", "action": "delete", }) events.Default.Log(events.ItemFinished, map[string]interface{}{ "folder": p.folder, "item": target.Name, "error": events.Error(err), "type": "file", "action": "update", }) }() if debug { l.Debugln(p, "taking rename shortcut", source.Name, "->", target.Name) } from := filepath.Join(p.dir, source.Name) to := filepath.Join(p.dir, target.Name) if p.versioner != nil { err = osutil.Copy(from, to) if err == nil { err = osutil.InWritableDir(p.versioner.Archive, from) } } else { err = osutil.TryRename(from, to) } if err == nil { // The file was renamed, so we have handled both the necessary delete // of the source and the creation of the target. Fix-up the metadata, // and update the local index of the target file. p.dbUpdates <- dbUpdateJob{source, dbUpdateDeleteFile} err = p.shortcutFile(target) if err != nil { l.Infof("Puller (folder %q, file %q): rename from %q metadata: %v", p.folder, target.Name, source.Name, err) p.newError(target.Name, err) return } p.dbUpdates <- dbUpdateJob{target, dbUpdateHandleFile} } else { // We failed the rename so we have a source file that we still need to // get rid of. Attempt to delete it instead so that we make *some* // progress. The target is unhandled. err = osutil.InWritableDir(osutil.Remove, from) if err != nil { l.Infof("Puller (folder %q, file %q): delete %q after failed rename: %v", p.folder, target.Name, source.Name, err) p.newError(target.Name, err) return } p.dbUpdates <- dbUpdateJob{source, dbUpdateDeleteFile} } }
func (f *rwFolder) performFinish(state *sharedPullerState) error { // Set the correct permission bits on the new file if !f.ignorePermissions(state.file) { if err := os.Chmod(state.tempName, os.FileMode(state.file.Permissions&0777)); err != nil { return err } } if stat, err := f.mtimeFS.Lstat(state.realName); err == nil { // There is an old file or directory already in place. We need to // handle that. switch { case stat.IsDir() || stat.Mode()&os.ModeSymlink != 0: // It's a directory or a symlink. These are not versioned or // archived for conflicts, only removed (which of course fails for // non-empty directories). // TODO: This is the place where we want to remove temporary files // and future hard ignores before attempting a directory delete. // Should share code with f.deletDir(). if err = osutil.InWritableDir(os.Remove, state.realName); err != nil { return err } case f.inConflict(state.version, state.file.Version): // The new file has been changed in conflict with the existing one. We // should file it away as a conflict instead of just removing or // archiving. Also merge with the version vector we had, to indicate // we have resolved the conflict. state.file.Version = state.file.Version.Merge(state.version) if err = osutil.InWritableDir(f.moveForConflict, state.realName); err != nil { return err } case f.versioner != nil: // If we should use versioning, let the versioner archive the old // file before we replace it. Archiving a non-existent file is not // an error. if err = f.versioner.Archive(state.realName); err != nil { return err } } } // Replace the original content with the new one. If it didn't work, // leave the temp file in place for reuse. if err := osutil.TryRename(state.tempName, state.realName); err != nil { return err } // Set the correct timestamp on the new file f.mtimeFS.Chtimes(state.realName, state.file.ModTime(), state.file.ModTime()) // never fails // Record the updated file in the index f.dbUpdates <- dbUpdateJob{state.file, dbUpdateHandleFile} return nil }
func alterFiles(dir string) error { err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if os.IsNotExist(err) { // Something we deleted. Never mind. return nil } info, err = os.Stat(path) if err != nil { // Something we deleted while walking. Ignore. return nil } if strings.HasPrefix(filepath.Base(path), "test-") { return nil } switch filepath.Base(path) { case ".stfolder": return nil case ".stversions": return nil } // File structure is base/x/xy/xyz12345... // comps == 1: base (don't touch) // comps == 2: base/x (must be dir) // comps == 3: base/x/xy (must be dir) // comps > 3: base/x/xy/xyz12345... (can be dir or file) comps := len(strings.Split(path, string(os.PathSeparator))) r := rand.Intn(10) switch { case r == 0 && comps > 2: // Delete every tenth file or directory, except top levels return removeAll(path) case r == 1 && info.Mode().IsRegular(): if info.Mode()&0200 != 0200 { // Not owner writable. Fix. if err = os.Chmod(path, 0644); err != nil { return err } } // Overwrite a random kilobyte of every tenth file fd, err := os.OpenFile(path, os.O_RDWR, 0644) if err != nil { return err } if info.Size() > 1024 { _, err = fd.Seek(rand.Int63n(info.Size()), os.SEEK_SET) if err != nil { return err } } _, err = io.Copy(fd, io.LimitReader(cr.Reader, 1024)) if err != nil { return err } return fd.Close() // Change capitalization case r == 2 && comps > 3 && rand.Float64() < 0.2: if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { // Syncthing is currently broken for case-only renames on case- // insensitive platforms. // https://github.com/syncthing/syncthing/issues/1787 return nil } base := []rune(filepath.Base(path)) for i, r := range base { if rand.Float64() < 0.5 { base[i] = unicode.ToLower(r) } else { base[i] = unicode.ToUpper(r) } } newPath := filepath.Join(filepath.Dir(path), string(base)) if newPath != path { return osutil.TryRename(path, newPath) } // Switch between files and directories case r == 3 && comps > 3 && rand.Float64() < 0.2: if !info.Mode().IsRegular() { err = removeAll(path) if err != nil { return err } d1 := []byte("I used to be a dir: " + path) err := ioutil.WriteFile(path, d1, 0644) if err != nil { return err } } else { err := osutil.Remove(path) if err != nil { return err } err = os.MkdirAll(path, 0755) if err != nil { return err } generateFiles(path, 10, 20, "../LICENSE") } return err /* This fails. Bug? // Rename the file, while potentially moving it up in the directory hierarchy case r == 4 && comps > 2 && (info.Mode().IsRegular() || rand.Float64() < 0.2): rpath := filepath.Dir(path) if rand.Float64() < 0.2 { for move := rand.Intn(comps - 1); move > 0; move-- { rpath = filepath.Join(rpath, "..") } } return osutil.TryRename(path, filepath.Join(rpath, randomName())) */ } return nil }) if err != nil { return err } return generateFiles(dir, 25, 20, "../LICENSE") }