Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSX: mounting acme filesystem causes recurring empty windows #136

Open
mennis opened this issue Jan 29, 2018 · 20 comments
Open

OSX: mounting acme filesystem causes recurring empty windows #136

mennis opened this issue Jan 29, 2018 · 20 comments

Comments

@mennis
Copy link

mennis commented Jan 29, 2018

Might be a beta issue. Running Fuse 3.7.1

167 0 0xffffff7f8330e000 0x19000 0x19000 com.github.osxfuse.filesystems.osxfuse (3.7.1) 8C89BD7B-DA6D-347D-AC76-759C45AE6099 <7 5 4 3 1>

and OSX 10.13.4 Beta (17E139j) when I run acme without -m /mnt/acme it behaves normally. When using acme with this option the acme's looks normal when exploring it from the command line but periodically an empty buffer with no path appears in acme.

@kare
Copy link
Contributor

kare commented Jan 30, 2018

I can reproduce your behavior with:

  • macos 10.13.3 (17D47)
  • osxfuse 3.7.1

@mkhl
Copy link
Contributor

mkhl commented Feb 6, 2018

I've looked at this a bit and I don't think osxfuse is to blame.

Acme creates a new window in response to Twalk of the new directory (see $PLAN9/src/cmd/acme/fsys.c:/newwindowthread), you can observe this using 9p ls acme/new, which creates two new windows on my system without me having mounted acme.

Using 9pfuse -D $(namespace)/acme /mnt/acme (which is basically what acme does with the -m flag, except the -D flag which logs all FUSE and 9P messages) we can check where the accesses to new originate.
A sequence of related messages looks like this:

FUSE -> len 4 unique 0x3 uid 501 gid 20 pid 404 Lookup nodeid 0x1 name 'new'
<- Twalk tag 0 fid 0 newfid 10 nwname 1 0:new
-> Rwalk tag 0 nwqid 1 0:(0000000000001600 0 d)

This tells us that the process with PID 404 (Finder.app), started by the user with UID 501 (myself) sent a Lookup message for new, which 9pfuse translated into a Twalk request for new. (Rwalk is the corresponding response.)

Letting it run for a while I can see messages like this from Finder and from fseventsd.
I'm not completely sure why these programs are doing this (fseventsd is presumably scanning for changes, Finder may be looking for display information like a volume icon), but I don’t think osxfuse can prevent this (the nobrowse mount option doesn't seem to help).
The only way I can think to work around this is by not opening a new window in response to Twalk but when opening a file inside new, the way the manpage describes it.
As far as I understand this should be compatible to both what acme(4) says and to the way lib/acme.rc operates, but I haven't tested it yet.

@yarikk
Copy link

yarikk commented Feb 6, 2018

What fixed this for me was disabling "Show icon preview" in Finder's view options.

@NeoTeo
Copy link

NeoTeo commented May 15, 2018

Not solving it for me on either Fuse 3.7.1 and 3.8.0

@Micrified
Copy link

Has any progress been made? I'm also using Acme via plan9ports on macOS 10.11.6, but mounting it results in the same stream of blank windows opening constantly in the program.

I've looked at the source file @mkhl mentioned.


                        if(strcmp(x->fcall.wname[i], "new") == 0){
                                if(w)
                                        error("w set in walk to new");
                                sendp(cnewwindow, nil); /* signal newwindowthread */
                                w = recvp(cnewwindow);  /* receive new window */
                                incref(&w->ref);
                                type = QTDIR;
                                path = QID(w->id, Qdir);
                                id = w->id;
                                dir = dirtabw;
                                goto Accept;
                        }

I was contemplating replacing the body of this block with:

                        if(strcmp(x->fcall.wname[i], "new") == 0) {
                                if(w) error("w set in walk to new");
                                continue;
                        }

But I'm hesitant to screw up anything I might not understand. Has anyone found a way to avoid this without editing the source code?

@mkhl
Copy link
Contributor

mkhl commented Jul 31, 2018

I was contemplating replacing the body of this block with:

I believe that would prevent scripts from opening windows by reading from new.
For example, acme.rc provides this:

fn newwindow {
        winctl=`{9p read acme/new/ctl}
        winid=$winctl(1)
}

Has anyone found a way to avoid this without editing the source code?

I haven’t.

@Micrified
Copy link

So the actual mechanic for opening windows in Acme is to read acme/new/ctl, and then grab the returned window identifier so you can manipulate the associated tag files and whatnot? This means there is essentially no distinction between my filesystem daemon meandering through the mounted file system and an intentional read to create a new window?

What can realistically be done here? Is there any way to get the file system daemon to ignore the mounted filesystem, or finder for that matter?

@mkhl
Copy link
Contributor

mkhl commented Jul 31, 2018

So the actual mechanic for opening windows in Acme is to read acme/new/ctl, and then grab the returned window identifier so you can manipulate the associated tag files and whatnot?

The actual mechanism is doing anything inside acme/new. Writing to acme/new/body works too, for example. This way has the advantage of giving you the winid though so you can continue manipulating the window.

Is there any way to get the file system daemon to ignore the mounted filesystem, or finder for that matter?

I hope there’s a way to do that, but I haven’t been able to dig in beyond what I wrote above.
One could try to figure out what exactly Finder and fseventsd do and if/how one can tell them not to.
It’s also possible that there are (or should be) mount options that we could use, so figuring that out might make sense.

@Micrified
Copy link

It’s also possible that there are (or should be) mount options that we could use, so figuring that out might make sense.

Perhaps FUSE can be instructed to make the mounted filesystem only visible to a certain process group. I'd have to look at how Acme executes other programs, but if all it does is fork and exec then maybe that would work.

@mkhl
Copy link
Contributor

mkhl commented Jul 31, 2018

Other programs can interact with the filesystem by using 9P libraries or 9p if they can shell out.
The mounts are for programs that cannot do either or where an actual filesystem has benefits over pipes.
Using programs that interact with Acme from outside of Acme is quite common.

@Micrified
Copy link

Micrified commented Aug 1, 2018

Okay. This morning I took another look at how FUSE is integrated with Acme, and decided I'd have it ignore any Lookup's that come from Finder or fseventsd. By ignore, I mean it actually reports there is no such file or directory to them.

I added the following segment in $PLAN9/src/cmd/9pfuse/main.c above fuse lookup.

/* List of processes you don't want browsing the mounted filesystem. */
const char *procBlacklist[] = {
    "fseventsd",
    "Finder",
    NULL
};

/* Returns nonzero if the given string is contained in the list. */
int containsString (const char *s, const char **ss) {
    while (*ss != NULL && strcmp(*ss, s) != 0) {
        ss++;
    }
    return (*ss != NULL);
}

/* Segments string with delimiter character 'd'. Returns last segment. */
const char *segmentedStringSuffix (const char *s, char d) {
    char *p;
    
    // If input is NULL, return NULL. If no delimiter exists, return last segment.
    if (s == NULL || (p = strchr(s, d)) == NULL) {
        return s;
    }
    
    // Find last segment and return it.
    do {
        s = p + 1;
    } while ((p = strchr(s, d)) != NULL);
    
    return s;
}

/* Returns the command of a process given it's PID. */
const char *getProcComm (pid_t pid) {
    int child, fds[2];
    size_t len;
    static char buf[1024];
    char pid_buf[6];
    
    // Prepare pid argument buffer.
    snprintf(pid_buf, 6, "%" PRIdMAX, (intmax_t)pid);
    
    // Create pipe.
    if (pipe(fds) == -1) {
        fprint(3, "getProcComm: Pipe failed!");
    }
    
    // Fork. If Child: redirect output to pipe and execute ps.
    if ((child = fork()) == 0) {
        close(fds[0]);
        close(STDOUT_FILENO);
        dup(fds[1], -1);
        execlp("ps", "-p", pid_buf , "-o", "comm=", NULL);
    } else {
        close(fds[1]);
    }
    
    // Read result into buffer. Error out if zero.
    len = read(fds[0], buf, 1024);
    
    // Append null character on newline.
    buf[len - 1] = '\0';
    
    // Wait on child.
    wait();
    
    return buf;
}

/* Returns nonzero if the given PID is blacklisted. */
int blacklisted (pid_t pid) {
    const char *command = getProcComm(pid);
    return containsString(segmentedStringSuffix(command, '/'), procBlacklist);
}

I also had to include:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>

The changed file is also linked here.

Edit: I forgot the most important part, how I modified the lookup function:

void
fuselookup(FuseMsg *m)
{
	char *name;
	Fusefid *ff;
	CFid *fid, *newfid;
	Dir *d;
	struct fuse_entry_out out;
    
    /* Ignore Lookup's by blacklisted PIDs. */
    if (blacklisted(m->hdr->pid)) {
        replyfuseerrno(m, ENOENT);
        return;
    }
    ...
}

Now, I don't know what secondary effects this will have, but I've managed to stop the trickle of windows appearing in Acme, while still being able to explicitly trigger them myself. I must say I find it hard to understand exactly what is going on in these files. They are sparsely commented and written in a style that is unfamiliar to me. Hopefully someone with a much better knowledge of this can help in finding an elegant solution (if mine even works).

I'll update this thread as I find out more.

@ilanpillemer
Copy link

Has this worked out for you well?

@Micrified
Copy link

@ilanpillemer Yeah, it solved it for me. It's just an "ugly" workaround and I wouldn't consider it appropriate for merging with the actual source code. But if you make the changes I did and recompile Acme then it should no longer spawn windows.

@ilanpillemer
Copy link

Thanks.... that window spawning is maddening.

@Micrified
Copy link

No problem. Hopefully it works for you too.

kare added a commit to kare/dotfiles that referenced this issue Sep 11, 2018
@mennis
Copy link
Author

mennis commented Oct 10, 2018

I think this might go away if you use a directory in /tmp for your mount point. Also I've opened a TSI with apple to see it there is an official way to do this. I'll post their response if it contains information.

@mennis
Copy link
Author

mennis commented Oct 15, 2018

@mkhl Where are you attempting to insert the no browse directive? I'd like to test that.

@mkhl
Copy link
Contributor

mkhl commented Oct 15, 2018

@mennis I inserted the option in the appropriate call to /usr/local/plan9/src/cmd/9pfuse/fuse.c:/execl/ (the first one on my system).

The available options are documented on the osxfuse wiki and summarised when calling mount_osxfuse without arguments.

kare added a commit to kare/dotfiles that referenced this issue Nov 8, 2018
Using mountpoint /mnt/acme causes recurring empty windows. This seems to
fix it. See 9fans/plan9port#136 for details.
kare added a commit to kare/dotfiles that referenced this issue Nov 8, 2018
Using mountpoint /mnt/acme causes recurring empty windows. This seems to
fix it. See 9fans/plan9port#136 for details.
@mennis
Copy link
Author

mennis commented Jan 10, 2019

I got a response from Apple. I was hoping to fix this myself but haven't and it's been a while since they responded. Essentially I was told that if one wants to insure that the volume is ignored by fseventsd it needs to be mounted outside of /User with "MNT_DONTBROWSE” set on it. In addition calling statfs inside acme the file system should not illicit action in the way that writing to acme/new does.

@ilanpillemer
Copy link

ilanpillemer commented Jan 11, 2019 via email

jinyangustc added a commit to jinyangustc/acme-editor that referenced this issue Feb 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants