Index: appfsd.c ================================================================== --- appfsd.c +++ appfsd.c @@ -816,10 +816,12 @@ static int appfs_fuse_mknod(const char *path, mode_t mode, dev_t device) { char *real_path; int mknod_ret; + APPFS_DEBUG("Enter (path = %s, ...)", path); + if ((mode & S_IFCHR) == S_IFCHR) { return(-EPERM); } if ((mode & S_IFBLK) == S_IFBLK) { @@ -844,10 +846,12 @@ static int appfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) { int fd; int chmod_ret; + APPFS_DEBUG("Enter (path = %s, ...)", path); + fd = appfs_fuse_open(path, fi); if (fd < 0) { return(fd); } @@ -863,10 +867,12 @@ static int appfs_fuse_truncate(const char *path, off_t size) { char *real_path; int truncate_ret; + APPFS_DEBUG("Enter (path = %s, ...)", path); + real_path = appfs_localpath(path); if (real_path == NULL) { return(-EIO); } @@ -876,11 +882,57 @@ if (truncate_ret != 0) { return(errno * -1); } - return(truncate_ret); + return(0); +} + +static int appfs_fuse_unlink_rmdir(const char *path) { + Tcl_Interp *interp; + int tcl_ret; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + interp = appfs_TclInterp(); + if (interp == NULL) { + return(-EIO); + } + + tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::unlinkpath", path); + if (tcl_ret != TCL_OK) { + APPFS_DEBUG("::appfs::unlinkpath(%s) failed.", path); + APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp)); + + return(-EIO); + } + + return(0); +} + +static int appfs_fuse_mkdir(const char *path, mode_t mode) { + char *real_path; + int mkdir_ret; + + APPFS_DEBUG("Enter (path = %s, ...)", path); + + real_path = appfs_prepare_to_create(path); + if (real_path == NULL) { + return(-EIO); + } + + mkdir_ret = mkdir(real_path, mode); + + free(real_path); + + if (mkdir_ret != 0) { + if (errno != EEXIST) { + return(errno * -1); + } + } + + return(0); } /* * SQLite3 mode: Execute raw SQL and return success or failure */ @@ -974,10 +1026,13 @@ .read = appfs_fuse_read, .write = appfs_fuse_write, .mknod = appfs_fuse_mknod, .create = appfs_fuse_create, .truncate = appfs_fuse_truncate, + .unlink = appfs_fuse_unlink_rmdir, + .rmdir = appfs_fuse_unlink_rmdir, + .mkdir = appfs_fuse_mkdir, }; /* * FUSE option parsing callback */ Index: appfsd.tcl ================================================================== --- appfsd.tcl +++ appfsd.tcl @@ -459,10 +459,19 @@ set dir [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] foreach file [glob -nocomplain -tails -directory $dir -types {d f l} {{.,}*}] { if {$file == "." || $file == ".."} { continue } + + if {[string match "*.APPFS.WHITEOUT" $file]} { + set remove [string range $file 0 end-15] + set idx [lsearch -exact $retval $remove] + if {$idx != -1} { + set retval [lreplace $retval $idx $idx] + } + continue + } if {[lsearch -exact $retval $file] != -1} { continue } @@ -529,48 +538,52 @@ set retval(childcount) 2; } } } "files" { - set retval(packaged) 1 set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] set retval(localpath) $localpath - if {[file exists $localpath]} { - catch { - file lstat $localpath localpathinfo - set retval(time) $localpathinfo(mtime) - - switch -- $localpathinfo(type) { - "directory" { - set retval(type) "directory" - set retval(childcount) 2 - } - "file" { - set retval(type) "file" - set retval(size) $localpathinfo(size) - if {[file executable $localpath]} { - set retval(perms) "x" - } else { - set retval(perms) "" - } - } - "link" { - set retval(type) "symlink" - set retval(source) [file readlink $localpath] - } - } - } err - } else { - set work [split $pathinfo(file) "/"] - set directory [join [lrange $work 0 end-1] "/"] - set file [lindex $work end] - ::appfs::db eval {SELECT type, time, source, size, perms FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_directory = $directory AND file_name = $file;} retval {} - unset -nocomplain retval(*) + if {![file exists "${localpath}.APPFS.WHITEOUT"]} { + if {[file exists $localpath]} { + set retval(is_localfile) 1 + catch { + file lstat $localpath localpathinfo + set retval(time) $localpathinfo(mtime) + + switch -- $localpathinfo(type) { + "directory" { + set retval(type) "directory" + set retval(childcount) 2 + } + "file" { + set retval(type) "file" + set retval(size) $localpathinfo(size) + if {[file executable $localpath]} { + set retval(perms) "x" + } else { + set retval(perms) "" + } + } + "link" { + set retval(type) "symlink" + set retval(source) [file readlink $localpath] + } + } + } err + } else { + set retval(is_remotefile) 1 + + set work [split $pathinfo(file) "/"] + set directory [join [lrange $work 0 end-1] "/"] + set file [lindex $work end] + ::appfs::db eval {SELECT type, time, source, size, perms FROM files WHERE package_sha1 = $pathinfo(package_sha1) AND file_directory = $directory AND file_name = $file;} retval {} + unset -nocomplain retval(*) + } } } } @@ -588,15 +601,17 @@ return -code error "invalid type" } set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] - if {[file exists $localpath]} { + if {$mode == "create"} { + file delete -- "${localpath}.APPFS.WHITEOUT" + return $localpath } - if {$mode == "create"} { + if {[file exists $localpath]} { return $localpath } set work [split $pathinfo(file) "/"] set directory [join [lrange $work 0 end-1] "/"] @@ -667,24 +682,10 @@ file mkdir $dirname return $filename } - proc prepare_to_create {path} { - if {[exists $path] != ""} { - return -code error "File already exists" - } - - set filename [openpath $path "create"] - - set dirname [file dirname $filename] - - file mkdir $dirname - - return $filename - } - proc localpath {path} { array set pathinfo [_parsepath $path] if {$pathinfo(_type) != "files"} { return -code error "invalid type" @@ -692,6 +693,70 @@ set localpath [_localpath $pathinfo(package) $pathinfo(hostname) $pathinfo(file)] return $localpath } + + proc _delete_files_except_whiteout {path} { + foreach file [glob -nocomplain -directory $path {{.,}*}] { + if {[string match "*/.." $file] || [string match "*/." $file]} { + continue + } + + if {[file isdirectory $file]} { + _delete_files_except_whiteout $file + } + + if {[string match "*.APPFS.WHITEOUT" $file]} { + continue + } + + catch { + file delete -- $file + } + } + } + + proc unlinkpath {path} { + array set pathattrs [exists $path] + + if {![info exists pathattrs(packaged)]} { + return -code error "invalid type" + } + + set localpath $pathattrs(localpath) + + set whiteout 0 + set isdirectory 0 + if {[info exists pathattrs(is_localfile)]} { + if {[file isdirectory $localpath]} { + set isdirectory 1 + set whiteout 1 + _delete_files_except_whiteout $localpath + } else { + file delete -force -- $localpath + } + } elseif {[info exists pathattrs(is_remotefile)]} { + if {$pathattrs(type) == "directory"} { + set isdirectory 1 + } + + set whiteout 1 + } else { + return -code error "Unknown if file is remote or local !?" + } + + if {$isdirectory} { + set children [getchildren $path] + if {$children != [list]} { + return -code error "Asked to delete non-empty directory" + } + } + + if {$whiteout} { + set whiteoutfile "${localpath}.APPFS.WHITEOUT" + set whiteoutdir [file dirname $whiteoutfile] + file mkdir $whiteoutdir + close [open $whiteoutfile w] + } + } }