diff --git a/pd/src/g_array.c b/pd/src/g_array.c
index a2b133bcfede06c5caa7e6f9a828e38467b4575f..661dc13b6b4023df4be8d15c94d414e381cd021b 100644
--- a/pd/src/g_array.c
+++ b/pd/src/g_array.c
@@ -7,6 +7,7 @@
 #include <stdio.h>      /* for read/write to files */
 #include "m_pd.h"
 #include "g_canvas.h"
+#include "g_all_guis.h"
 #include <math.h>
 #include <ctype.h>
 
@@ -134,6 +135,7 @@ struct _garray
     char x_saveit;          /* true if we should save this with parent */
     char x_joc;             /* true if we should "jump on click" in a graph */
     char x_hidename;        /* don't print name above graph */
+    char x_edit;            /* true if we can edit the array */
     int x_style;            /* so much simpler to keep it here */
     t_symbol *x_send;       /* send_changed hook */
     t_symbol *x_fillcolor;     /* filled area of bar in bar graph */
@@ -145,8 +147,8 @@ t_pd *garray_floattemplatecanvas;
 static char garray_arraytemplatefile[] = "\
 #N canvas 0 0 458 153 10;\n\
 #X obj 43 31 struct _float_array array z float float style\n\
-float linewidth float color symbol fillcolor symbol outlinecolor;\n\
-#X obj 43 70 plot z color linewidth 0 0 1 style fillcolor outlinecolor;\n\
+float linewidth float color symbol fillcolor symbol outlinecolor float v;\n\
+#X obj 43 70 plot -v v z color linewidth 0 0 1 style fillcolor outlinecolor;\n\
 ";
 static char garray_floattemplatefile[] = "\
 #N canvas 0 0 458 153 10;\n\
@@ -196,6 +198,7 @@ static t_garray *graph_scalar(t_glist *gl, t_symbol *s, t_symbol *templatesym,
     x->x_outlinecolor = outline;
     x->x_scalar = scalar_new(gl, templatesym);
     x->x_name = s;
+    x->x_edit = 1;
     x->x_realname = canvas_realizedollar(gl, s);
     pd_bind(&x->x_gobj.g_pd, x->x_realname);
     x->x_usedindsp = 0;
@@ -508,6 +511,7 @@ t_garray *graph_array(t_glist *gl, t_symbol *s, int argc, t_atom *argv)
         x->x_style, 1);
     template_setfloat(template, gensym("linewidth"), x->x_scalar->sc_vec, 
         ((x->x_style == PLOTSTYLE_POINTS) ? 2 : 1), 1);
+    template_setfloat(template, gensym("v"), x->x_scalar->sc_vec, 1, 1);
     template_setsymbol(template, gensym("fillcolor"), x->x_scalar->sc_vec,
         fill, 1);
     template_setsymbol(template, gensym("outlinecolor"), x->x_scalar->sc_vec,
@@ -1392,6 +1396,8 @@ static int garray_click(t_gobj *z, t_glist *glist,
     int xpix, int ypix, int shift, int alt, int dbl, int doit)
 {
     t_garray *x = (t_garray *)z;
+    if (!x->x_edit)
+        return 0;
     array_garray = x;
     return (gobj_click(&x->x_scalar->sc_gobj, glist,
         xpix, ypix, shift, alt, dbl, doit));
@@ -1826,6 +1832,62 @@ static void garray_ylabel(t_garray *x, t_symbol *s, int argc, t_atom *argv)
 {
     typedmess(&x->x_glist->gl_pd, s, argc, argv);
 }
+
+static void garray_style(t_garray *x, t_floatarg fstyle) {
+    x->x_style = (int)fstyle;
+    template_setfloat(template_findbyname(x->x_scalar->sc_template), gensym("style"), x->x_scalar->sc_vec, (t_float)x->x_style, 0);
+    glist_redraw(x->x_glist);
+}
+
+static t_symbol* garray_convertcolor(int argc, t_atom *argv) {
+    if(argv->a_type == A_SYMBOL) {
+        return argv->a_w.w_symbol;
+    } else {
+        int col = argv->a_w.w_float;
+        int bitsPerChannel;
+        char* hexCol[8]; 
+
+        if(col >= 0) {
+            col = iemgui_color_hex[col % 30];
+            bitsPerChannel = 8;
+        } else {
+            col = -1 - col;
+            bitsPerChannel = 6;
+        }
+
+        int r = (col & ((1 << bitsPerChannel) - 1)) << (8 - bitsPerChannel);
+        int g = (col & ((1 << (bitsPerChannel * 2)) - 1)) << (8 - bitsPerChannel) * 2;
+        int b = (col & ((1 << (bitsPerChannel * 3)) - 1)) << (8 - bitsPerChannel) * 3;
+
+        snprintf(hexCol, sizeof(hexCol), "#%06x", (r | g | b) & 0xFFFFFF);
+
+        return gensym(hexCol);
+    }
+}
+
+static void garray_fillcolor(t_garray *x, t_symbol *s, int argc, t_atom *argv) {
+    x->x_fillcolor = garray_convertcolor(argc, argv);
+    template_setsymbol(template_findbyname(x->x_scalar->sc_template), gensym("fillcolor"), x->x_scalar->sc_vec, x->x_fillcolor, 1);
+    glist_redraw(x->x_glist);
+}
+
+static void garray_outlinecolor(t_garray *x, t_symbol *s, int argc, t_atom *argv) {
+    x->x_outlinecolor = garray_convertcolor(argc, argv);
+    template_setsymbol(template_findbyname(x->x_scalar->sc_template), gensym("outlinecolor"), x->x_scalar->sc_vec, x->x_outlinecolor, 1);
+    glist_redraw(x->x_glist);
+}
+
+static void garray_vis_msg(t_garray *x, t_floatarg fvis)
+{
+    template_setfloat(template_findbyname(x->x_scalar->sc_template), gensym("v"), x->x_scalar->sc_vec, fvis, 0);
+    glist_redraw(x->x_glist);
+}
+
+static void garray_width(t_garray *x, t_floatarg fvis)
+{
+    template_setfloat(template_findbyname(x->x_scalar->sc_template), gensym("linewidth"), x->x_scalar->sc_vec, fvis, 0);
+    glist_redraw(x->x_glist);
+}
     /* change the name of a garray. */
 static void garray_rename(t_garray *x, t_symbol *s)
 {
@@ -1943,6 +2005,11 @@ void garray_resize(t_garray *x, t_floatarg f)
     garray_resize_long(x, f);
 }
 
+static void garray_edit(t_garray *x, t_floatarg f)
+{
+    x->x_edit = (int)f;
+}
+
 static void garray_print(t_garray *x)
 {
     t_array *array = garray_getarray(x);
@@ -1974,6 +2041,18 @@ void g_array_setup(void)
         A_FLOAT, A_FLOAT, A_FLOAT, 0);
     class_addmethod(garray_class, (t_method)garray_ylabel, gensym("ylabel"),
         A_GIMME, 0);
+    class_addmethod(garray_class, (t_method)garray_style, gensym("style"),
+        A_FLOAT, 0);
+    class_addmethod(garray_class, (t_method)garray_width, gensym("width"),
+        A_FLOAT, 0);
+    class_addmethod(garray_class, (t_method)garray_outlinecolor, gensym("color"),
+        A_GIMME, 0); // This is duplicated alongside outlinecolor for pure-data compatibility
+    class_addmethod(garray_class, (t_method)garray_outlinecolor, gensym("outlinecolor"),
+        A_GIMME, 0);
+    class_addmethod(garray_class, (t_method)garray_fillcolor, gensym("fillcolor"),
+        A_GIMME, 0);
+    class_addmethod(garray_class, (t_method)garray_vis_msg, gensym("vis"),
+        A_FLOAT, 0);
     class_addmethod(garray_class, (t_method)garray_rename, gensym("rename"),
         A_SYMBOL, 0);
     class_addmethod(garray_class, (t_method)garray_read, gensym("read"),
@@ -1982,6 +2061,8 @@ void g_array_setup(void)
         A_SYMBOL, A_NULL);
     class_addmethod(garray_class, (t_method)garray_resize, gensym("resize"),
         A_FLOAT, A_NULL);
+    class_addmethod(garray_class, (t_method)garray_edit, gensym("edit"),
+        A_FLOAT, 0);
     class_addmethod(garray_class, (t_method)garray_print, gensym("print"),
         A_NULL);
     class_addmethod(garray_class, (t_method)garray_sinesum, gensym("sinesum"),
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index 3f844260063db6fb7677418587ed474e28461116..19516a3269c6b1a853ef19b9197b9436c72bc981 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -151,6 +151,28 @@ static void canvas_takeofflist(t_canvas *x)
     }
 }
 
+/* turn message tracing on and off globally */
+
+void obj_dosettracing(t_object *ob, int onoff);
+static void canvas_dosettracing(t_canvas *x, int onoff)
+{
+    t_gobj *y;
+    for (y = x->gl_list; y; y = y->g_next)
+    {
+        t_object *ob;
+        if (pd_class(&y->g_pd) == canvas_class)
+            canvas_dosettracing((t_canvas *)y, onoff);
+        if ((ob = pd_checkobject(&y->g_pd)))
+            obj_dosettracing(ob, onoff);
+    }
+}
+
+void canvas_settracing(int onoff)
+{
+    t_glist *gl;
+    for (gl = pd_this->pd_canvaslist; gl; gl = gl->gl_next)
+        canvas_dosettracing(gl, onoff);
+}
 
 void canvas_setargs(int argc, t_atom *argv)
 {
diff --git a/pd/src/m_conf.c b/pd/src/m_conf.c
index a7c7d3e22f9a0c62b5fc59c28d19a12c2cd1fd17..dbed1e26eb4d02ff5ab794e650865c7160ffdd9f 100644
--- a/pd/src/m_conf.c
+++ b/pd/src/m_conf.c
@@ -36,6 +36,7 @@ void x_array_setup(void);
 void x_midi_setup(void);
 void x_misc_setup(void);
 void x_net_setup(void);
+void x_file_setup(void);
 void x_qlist_setup(void);
 void x_gui_setup(void);
 void x_list_setup(void);
@@ -89,6 +90,7 @@ void conf_init(void)
     x_midi_setup();
     x_misc_setup();
     x_net_setup();
+    x_file_setup();
     x_qlist_setup();
     x_gui_setup();
     x_list_setup();
diff --git a/pd/src/m_glob.c b/pd/src/m_glob.c
index 0986bb93ada9e00b26b6d7b05515df23cdc090d8..cec9231fb4e610cfa6f1a50087063b0cf6b67714 100644
--- a/pd/src/m_glob.c
+++ b/pd/src/m_glob.c
@@ -47,6 +47,7 @@ void glob_forward_files_from_secondary_instance(void);
 void glob_recent_files(t_pd *dummy);
 void glob_add_recent_file(t_pd *dummy, t_symbol *s);
 void glob_clear_recent_files(t_pd *dummy);
+void glob_settracing(void *dummy, t_float f);
 void glob_open(t_pd *ignore, t_symbol *name, t_symbol *dir, t_floatarg f);
 
 void alsa_resync( void);
@@ -233,6 +234,8 @@ void glob_init(void)
         gensym("clear-recent-files"), 0);
     class_addmethod(glob_pdobject, (t_method)glob_gui_busy, gensym("gui-busy"),
         A_DEFFLOAT, 0);
+    class_addmethod(glob_pdobject, (t_method)glob_settracing,
+        gensym("set-tracing"), A_FLOAT, 0);
 #ifdef UNIX
     class_addmethod(glob_pdobject, (t_method)glob_watchdog,
         gensym("watchdog"), 0);
diff --git a/pd/src/m_obj.c b/pd/src/m_obj.c
index 3ea7d647ab277ac66eea593dd95a50aadfdfbc59..e9dfcf7c4f105799f16f05be7200d65d036557f7 100644
--- a/pd/src/m_obj.c
+++ b/pd/src/m_obj.c
@@ -9,6 +9,7 @@ behavior for "gobjs" appears at the end of this file.  */
 #include "m_pd.h"
 #include "m_imp.h"
 #include <stdio.h>
+#include <string.h>
 
 #ifdef _WIN32
 # include <malloc.h> /* MSVC or mingw on windows */
@@ -354,48 +355,72 @@ void obj_list(t_object *x, t_symbol *s, int argc, t_atom *argv)
     else pd_symbol(&x->ob_pd, argv->a_w.w_symbol);
 } 
 
-void obj_init(void)
-{
-    inlet_class = class_new(gensym("inlet"), 0, 0,
-        sizeof(t_inlet), CLASS_PD, 0);
-    class_addbang(inlet_class, inlet_bang);
-    class_addpointer(inlet_class, inlet_pointer);
-    class_addfloat(inlet_class, inlet_float);
-    class_addsymbol(inlet_class, inlet_symbol);
-    class_addblob(inlet_class, inlet_blob); /* MP 20061226 blob type */
-    class_addlist(inlet_class, inlet_list);
-    class_addanything(inlet_class, inlet_anything);
+/* --------------------------- outlets ------------------------------ */
 
-    pointerinlet_class = class_new(gensym("inlet"), 0, 0,
-        sizeof(t_inlet), CLASS_PD, 0);
-    class_addpointer(pointerinlet_class, pointerinlet_pointer);
-    class_addanything(pointerinlet_class, inlet_wrong);
-    
-    floatinlet_class = class_new(gensym("inlet"), 0, 0,
-        sizeof(t_inlet), CLASS_PD, 0);
-    class_addfloat(floatinlet_class, (t_method)floatinlet_float);
-    class_addanything(floatinlet_class, inlet_wrong);
+struct _outconnect
+{
+    struct _outconnect *oc_next;
+    t_pd *oc_to;
+    int oc_vis;    // whether the connection should be visible
+};
 
-    symbolinlet_class = class_new(gensym("inlet"), 0, 0,
-        sizeof(t_inlet), CLASS_PD, 0);
-    class_addsymbol(symbolinlet_class, symbolinlet_symbol);
-    class_addanything(symbolinlet_class, inlet_wrong);
+int outconnect_visible(t_outconnect *oc)
+{
+    return oc->oc_vis;
+}
 
+void outconnect_setvisible(t_outconnect *oc, int vis)
+{
+    oc->oc_vis = vis;
 }
 
-/* --------------------------- outlets ------------------------------ */
+struct _outlet
+{
+    t_object *o_owner;
+    struct _outlet *o_next;
+    t_outconnect *o_connections;
+    t_symbol *o_sym;
+};
+
+/* ------- backtracer - keep track of stack for backtracing  --------- */
+
+#define NARGS 5
+typedef struct _msgstack
+{
+    struct _backtracer *m_owner;
+    t_symbol *m_sel;
+    int m_argc;
+    t_atom m_argv[NARGS];
+    struct _msgstack *m_next;
+} t_msgstack;
+
+typedef struct _backtracer
+{
+    t_pd b_pd;
+    t_outconnect *b_connections;
+    t_pd *b_owner;
+} t_backtracer;
+
+static t_msgstack *backtracer_stack;
+int backtracer_cantrace = 0;
+int backtracer_tracing;
+t_class *backtracer_class;
 
 static int stackcount = 0; /* iteration counter */
 #define STACKITER 1000 /* maximum iterations allowed */
 
 static int outlet_eventno;
 
-    /* set a stack limit (on each incoming event that can set off messages)
-    for the outlet functions to check to prevent stack overflow from message
-    recursion */
+    /* initialize stack depth count on each incoming event that can set off
+    messages so that  the outlet functions can check to prevent stack overflow]
+    from message recursion.  Also count message initiations. */
         
 void outlet_setstacklim(void)
 {
+    t_msgstack *m;
+    while ((m = backtracer_stack))
+        backtracer_stack = m->m_next; t_freebytes(m, sizeof (*m));
+    stackcount = 0;
     outlet_eventno++;
 }
 
@@ -405,30 +430,155 @@ int sched_geteventno( void)
     return (outlet_eventno);
 }
 
-struct _outconnect
+    /* get pointer to connection list for an outlet (for editing/traversing) */
+static t_outconnect **outlet_getconnectionpointer(t_outlet *x)
 {
-    struct _outconnect *oc_next;
-    t_pd *oc_to;
-    int oc_vis;    // whether the connection should be visible
-};
+    if (x->o_connections && *(x->o_connections->oc_to) == backtracer_class)
+        return (&((t_backtracer *)(x->o_connections->oc_to))->b_connections);
+    else return (&x->o_connections);
+}
 
-int outconnect_visible(t_outconnect *oc)
+static void backtracer_printmsg(t_pd *who, t_symbol *s,
+    int argc, t_atom *argv)
 {
-    return oc->oc_vis;
+    char msgbuf[104];
+    int nprint = (argc > NARGS ? NARGS : argc), nchar, i;
+    snprintf(msgbuf, 100, "%s: %s ", class_getname(*who), s->s_name);
+    nchar = strlen(msgbuf);
+    for (i = 0; i < nprint && nchar < 100; i++)
+        if (nchar < 100)
+    {
+        char buf[100];
+        atom_string(&argv[i], buf, 100);
+        snprintf(msgbuf + nchar, 100-nchar, " %s", buf);
+        nchar = strlen(msgbuf);
+    }
+    if (argc > nprint && nchar < 100)
+        sprintf(msgbuf + nchar, "...");
+    else memcpy(msgbuf+100, "...", 4); /* in case we didn't finish */
+    logpost(who, 2, "%s", msgbuf);
 }
 
-void outconnect_setvisible(t_outconnect *oc, int vis)
+static void backtracer_anything(t_backtracer *x, t_symbol *s,
+    int argc, t_atom *argv)
 {
-    oc->oc_vis = vis;
+    t_msgstack *m = (t_msgstack *)t_getbytes(sizeof(t_msgstack));
+    t_outconnect *oc;
+    int ncopy = (argc > NARGS ? NARGS : argc), i;
+    m->m_next = backtracer_stack;
+    backtracer_stack = m;
+    m->m_sel = s;
+    m->m_argc = argc;
+    for (i = 0; i < ncopy; i++)
+        m->m_argv[i] = argv[i];
+    m->m_owner = x;
+    if (backtracer_tracing)
+        backtracer_printmsg(x->b_owner, s, argc, argv);
+    for (oc = x->b_connections; oc; oc = oc->oc_next)
+        typedmess(oc->oc_to, s, argc, argv);
+    backtracer_stack = m->m_next;
+    t_freebytes(m, sizeof(*m));
 }
 
-struct _outlet
+t_backtracer *backtracer_new(t_pd *owner)
 {
-    t_object *o_owner;
-    struct _outlet *o_next;
-    t_outconnect *o_connections;
-    t_symbol *o_sym;
-};
+    t_backtracer *x = (t_backtracer *)pd_new(backtracer_class);
+    x->b_connections = 0;
+    x->b_owner = owner;
+    return (x);
+}
+
+int backtracer_settracing(void *x, int tracing)
+{
+    if (tracing)
+    {
+        if (backtracer_tracing)
+        {
+            pd_error(x, "trace: already tracing");
+            return (0);
+        }
+        else
+        {
+            backtracer_tracing = 1;
+            return (1);
+        }
+    }
+    else    /* when stopping, print backtrace to here */
+    {
+        t_msgstack *m = backtracer_stack;
+        post("backtrace:");
+        while (m)
+        {
+            backtracer_printmsg(m->m_owner->b_owner, m->m_sel,
+                m->m_argc, m->m_argv);
+            m = m->m_next;
+        }
+        backtracer_tracing = 0;
+        return (0);
+    }
+}
+
+void canvas_settracing(int onoff);
+static t_clock *backtrace_unsetclock;
+
+static void backtrace_dounsettracing(void *dummy)
+{
+    canvas_settracing(0);
+    backtracer_cantrace = 0;
+    clock_free(backtrace_unsetclock);
+    backtrace_unsetclock = 0;
+}
+
+    /* globally turn tracing on and off. */
+void glob_settracing(void *dummy, t_float f)
+{
+#ifndef PDINSTANCE  /* this won't work with pd instances so just don't */
+    if (f != 0)
+    {
+        if (backtracer_cantrace)
+            post("pd: tracing already enabled");
+        else canvas_settracing(1);
+        backtracer_cantrace = 1;
+    }
+    else
+    {
+        if (!backtracer_cantrace)
+            post("pd: tracing already disabled");
+        else if (!backtrace_unsetclock)
+        {
+            backtrace_unsetclock = clock_new(dummy,
+                (t_method)backtrace_dounsettracing);
+            clock_delay(backtrace_unsetclock, 0);
+        }
+    }
+#endif
+}
+
+    /* this is called on every object, via canvas_settracing() call above */
+void obj_dosettracing(t_object *ob, int onoff)
+{
+    t_outlet *o;
+    for (o = ob->ob_outlet; o; o = o->o_next)
+    {
+        if (onoff)
+        {
+            t_backtracer *b = backtracer_new(&ob->ob_pd);
+            b->b_connections = o->o_connections;
+            o->o_connections =  (t_outconnect *)t_getbytes(sizeof(t_outconnect));
+            o->o_connections->oc_next = 0;
+            o->o_connections->oc_to = &b->b_pd;
+        }
+        else if (o->o_connections &&
+            (*o->o_connections->oc_to == backtracer_class))
+        {
+            t_backtracer *b = (t_backtracer *)o->o_connections->oc_to;
+            t_freebytes(o->o_connections, sizeof(*o->o_connections));
+            o->o_connections = b->b_connections;
+            t_freebytes(b, sizeof(*b));
+        }
+        else bug("obj_dosettracing");
+    }
+}
 
 t_outlet *outlet_new(t_object *owner, t_symbol *s)
 {
@@ -441,7 +591,14 @@ t_outlet *outlet_new(t_object *owner, t_symbol *s)
         y->o_next = x;
     }
     else owner->ob_outlet = x;
-    x->o_connections = 0;
+    if (backtracer_cantrace)
+    {
+        t_backtracer *b = backtracer_new(&owner->ob_pd);
+        x->o_connections =  (t_outconnect *)t_getbytes(sizeof(t_outconnect));
+        x->o_connections->oc_next = 0;
+        x->o_connections->oc_to = &b->b_pd;
+    }
+    else x->o_connections = 0;
     x->o_sym = s;
     return (x);
 }
@@ -560,7 +717,7 @@ t_outconnect *obj_connect(t_object *source, int outno,
     t_inlet *i;
     t_outlet *o;
     t_pd *to;
-    t_outconnect *oc, *oc2;
+    t_outconnect *oc, *oc2, **ochead;
 
     /* ignore attempts to connect to the same object
        this occurs sometimes using undo/redo */
@@ -583,17 +740,19 @@ t_outconnect *obj_connect(t_object *source, int outno,
     if (!i) return (0);
     to = &i->i_pd;
 doit:
+    ochead = outlet_getconnectionpointer(o);
     oc = (t_outconnect *)t_getbytes(sizeof(*oc));
     oc->oc_next = 0;
     oc->oc_to = to;
         /* append it to the end of the list */
         /* LATER we might cache the last "oc" to make this faster. */
-    if ((oc2 = o->o_connections))
+    if ((oc2 = *ochead))
     {
-        while (oc2->oc_next) oc2 = oc2->oc_next;
+        while (oc2->oc_next)
+            oc2 = oc2->oc_next;
         oc2->oc_next = oc;
     }
-    else o->o_connections = oc;
+    else *ochead = oc;
     if (o->o_sym == &s_signal) canvas_update_dsp();
 
     return (oc);
@@ -604,7 +763,7 @@ void obj_disconnect(t_object *source, int outno, t_object *sink, int inno)
     t_inlet *i;
     t_outlet *o;
     t_pd *to;
-    t_outconnect *oc, *oc2;
+    t_outconnect *oc, *oc2, **ochead;
     for (o = source->ob_outlet; o && outno; o = o->o_next, outno--)
     if (!o) return;
     if (sink->ob_pd->c_firstin)
@@ -620,10 +779,11 @@ void obj_disconnect(t_object *source, int outno, t_object *sink, int inno)
     if (!i) return;
     to = &i->i_pd;
 doit:
-    if (!o || !(oc = o->o_connections)) return;
+    ochead = outlet_getconnectionpointer(o);
+    if (!(oc = *ochead)) return;
     if (oc->oc_to == to)
     {
-        o->o_connections = oc->oc_next;
+        *ochead = oc->oc_next;
         freebytes(oc, sizeof(*oc));
         goto done;
     }
@@ -680,7 +840,8 @@ t_outconnect *obj_starttraverseoutlet(t_object *x, t_outlet **op, int nout)
     t_outlet *o = x->ob_outlet;
     while (nout-- && o) o = o->o_next;
     *op = o;
-    if (o) return (o->o_connections);
+    if (o)
+        return (*outlet_getconnectionpointer(o));
     else return (0);
 }
 
@@ -880,3 +1041,36 @@ void obj_sendinlet(t_object *x, int n, t_symbol *s, int argc, t_atom *argv)
         typedmess(&i->i_pd, s, argc, argv);
     else bug("obj_sendinlet");
 }
+
+/* ------------------- setup routine, somewhat misnamed */
+void obj_init(void)
+{
+    inlet_class = class_new(gensym("inlet"), 0, 0,
+        sizeof(t_inlet), CLASS_PD, 0);
+    class_addbang(inlet_class, inlet_bang);
+    class_addpointer(inlet_class, inlet_pointer);
+    class_addfloat(inlet_class, inlet_float);
+    class_addsymbol(inlet_class, inlet_symbol);
+    class_addlist(inlet_class, inlet_list);
+    class_addanything(inlet_class, inlet_anything);
+
+    pointerinlet_class = class_new(gensym("inlet"), 0, 0,
+        sizeof(t_inlet), CLASS_PD, 0);
+    class_addpointer(pointerinlet_class, pointerinlet_pointer);
+    class_addanything(pointerinlet_class, inlet_wrong);
+
+    floatinlet_class = class_new(gensym("inlet"), 0, 0,
+        sizeof(t_inlet), CLASS_PD, 0);
+    class_addfloat(floatinlet_class, (t_method)floatinlet_float);
+    class_addanything(floatinlet_class, inlet_wrong);
+
+    symbolinlet_class = class_new(gensym("inlet"), 0, 0,
+        sizeof(t_inlet), CLASS_PD, 0);
+    class_addsymbol(symbolinlet_class, symbolinlet_symbol);
+    class_addanything(symbolinlet_class, inlet_wrong);
+
+    backtracer_class = class_new(gensym("backtracer"), 0, 0,
+        sizeof(t_backtracer), CLASS_PD, 0);
+    class_addanything(backtracer_class, backtracer_anything);
+
+}
\ No newline at end of file
diff --git a/pd/src/m_private_utils.h b/pd/src/m_private_utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..8ba72f9eeffee4e3866b43b7d5f818366f0414d8
--- /dev/null
+++ b/pd/src/m_private_utils.h
@@ -0,0 +1,110 @@
+/* Copyright (c) 1997-1999 Miller Puckette and others.
+* For information on usage and redistribution, and for a DISCLAIMER OF ALL
+* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */
+
+/* This file contains some small header-only utilities to be used by Pd's code. */
+
+/* NOTE: this file is an implementation detail of Pd, not to be in externals */
+
+#ifndef M_PRIVATE_UTILS_H
+#define M_PRIVATE_UTILS_H
+
+#ifndef PD_INTERNAL
+# error m_private_utils.h is a PRIVATE header. do *not* use it in your externals
+#endif
+
+#ifdef HAVE_CONFIG_H
+/* autotools might put all the HAVE_... defines into "config.h" */
+# include "config.h"
+#endif
+
+
+/* --------------------------- stack allocation helpers --------------------- */
+/* alloca helpers
+ * - ALLOCA(type, array, nmemb, maxnmemb)
+ *     allocates <nmemb> elements of <type> and points <array> to the newly
+ *     allocated memory. if <nmemb> exceeds <maxnmemb>, allocation is done on
+ *     the heap, otherwise on the stack
+ * - FREEA(type, array, nmemb, maxnmemb)
+ *     frees the <array> allocated by ALLOCA() (unless allocation was done on
+ *     the heap)
+ * if DONT_USE_ALLOCA is defined (and true), always allocates on the heap
+ *
+ * usage example:
+ * {
+ *   t_atom*outv;
+ *   ALLOCA(t_atom, outv, argc, 100);
+ *   // do something with 'outv'
+ *   FREEA(t_atom, outv, argc, 100);
+ * }
+ */
+# ifdef ALLOCA
+#  undef ALLOCA
+# endif
+# ifdef FREEA
+#  undef FREEA
+# endif
+
+#if DONT_USE_ALLOCA
+/* heap versions */
+# define ALLOCA(type, array, nmemb, maxnmemb) ((array) = (type *)getbytes((nmemb) * sizeof(type)))
+# define FREEA(type, array, nmemb, maxnmemb) (freebytes((array), (nmemb) * sizeof(type)))
+
+#else /* !DONT_USE_ALLOCA */
+/* stack version (unless <nmemb> exceeds <maxnmemb>) */
+
+# if defined __linux__ || defined __APPLE__
+#  include <alloca.h> /* linux, mac, mingw, cygwin,... */
+# elif defined _WIN32
+#  include <malloc.h> /* MSVC or mingw on windows */
+# else
+#  include <stdlib.h> /* BSDs for example */
+# endif
+
+# define ALLOCA(type, array, nmemb, maxnmemb) ((array) = (type *)((nmemb) < (maxnmemb) ? \
+            alloca((nmemb) * sizeof(type)) : getbytes((nmemb) * sizeof(type))))
+# define FREEA(type, array, nmemb, maxnmemb) (                          \
+        ((nmemb) < (maxnmemb) || (freebytes((array), (nmemb) * sizeof(type)), 0)))
+#endif /* !DONT_USE_ALLOCA */
+
+
+/* --------------------------- endianness helpers --------------------- */
+#ifdef HAVE_MACHINE_ENDIAN_H
+# include <machine/endian.h>
+#elif defined HAVE_ENDIAN_H
+# include <endian.h>
+#endif
+
+#ifdef __MINGW32__
+# include <sys/param.h>
+#endif
+
+/* BSD has deprecated BYTE_ORDER in favour of _BYTE_ORDER
+ * others might follow...
+ */
+#if !defined(BYTE_ORDER) && defined(_BYTE_ORDER)
+# define BYTE_ORDER _BYTE_ORDER
+#endif
+#if !defined(LITTLE_ENDIAN) && defined(_LITTLE_ENDIAN)
+# define LITTLE_ENDIAN _LITTLE_ENDIAN
+#endif
+
+#ifdef _MSC_VER
+/* _MSVC lacks BYTE_ORDER and LITTLE_ENDIAN */
+# if !defined(LITTLE_ENDIAN)
+#  define LITTLE_ENDIAN 0x0001
+# endif
+# if !defined(BYTE_ORDER)
+#  define BYTE_ORDER LITTLE_ENDIAN
+# endif
+#endif
+
+#if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN)
+# if defined(__GNUC__) && defined(_XOPEN_SOURCE)
+#  warning unable to detect endianness (continuing anyhow)
+# else
+#  error unable to detect endianness
+# endif
+#endif
+
+#endif /* M_PRIVATE_UTILS_H */
diff --git a/pd/src/makefile.in b/pd/src/makefile.in
index c26e61330358bd7bbf7142ea6a851f51bad01ee4..6d7c3e1b879132fbd0493a1d53dafd9cb2fde3a0 100644
--- a/pd/src/makefile.in
+++ b/pd/src/makefile.in
@@ -47,7 +47,7 @@ WARN_CFLAGS = -Wall -W -Wstrict-prototypes \
     -Wno-unused-parameter -Wno-parentheses -Wno-switch -Wno-cast-function-type
 #WARN_CFLAGS += -Werror=implicit-function-declaration
 
-ARCH_CFLAGS = -DPD 
+ARCH_CFLAGS = -DPD -DPD_INTERNAL
 
 CFLAGS = @CFLAGS@ $(ARCH_CFLAGS) $(WARN_CFLAGS) $(CPPFLAGS) $(MORECFLAGS)
 
@@ -76,7 +76,7 @@ OPT_SAFE_SRC = g_canvas.c g_graph.c g_text.c g_rtext.c g_array.c g_template.c g_
     d_ugen.c d_arithmetic.c d_dac.c d_misc.c \
     d_fft.c d_global.c \
     d_resample.c \
-    x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c \
+    x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c x_file.c \
     x_time.c x_acoustics.c x_net.c x_text.c x_gui.c x_list.c x_array.c x_preset.c\
 	x_scalar.c x_vexp.c x_vexp_if.c x_vexp_fun.c import.c \
     $(SYSSRC)
diff --git a/pd/src/makefile.mingw b/pd/src/makefile.mingw
index ee9935213005e9d811e1b03461af7960487262c8..f449342fb4addd82244389ddb0669d3b088cb842 100755
--- a/pd/src/makefile.mingw
+++ b/pd/src/makefile.mingw
@@ -123,7 +123,7 @@ SRC = g_canvas.c g_graph.c g_text.c g_rtext.c g_array.c g_template.c g_io.c \
     s_print.c s_loader.c s_path.c s_entry.c s_audio.c s_midi.c s_net.c \
     d_ugen.c d_ctl.c d_arithmetic.c d_osc.c d_filter.c d_dac.c d_misc.c \
     d_math.c d_fft.c d_fft_mayer.c d_fftroutine.c d_array.c d_global.c \
-    d_delay.c d_resample.c x_arithmetic.c x_connective.c x_interface.c \
+    d_delay.c d_resample.c x_arithmetic.c x_connective.c x_interface.c x_file.c \
     x_midi.c x_misc.c x_time.c x_acoustics.c x_net.c x_text.c x_gui.c \
     x_list.c x_array.c d_soundfile.c g_slider.c import.c \
     x_scalar.c x_vexp.c x_vexp_if.c x_vexp_fun.c \
diff --git a/pd/src/makefile.nt b/pd/src/makefile.nt
index e3af34af6608e5e91ce58807f71afe962f2fd5c0..e20c382b45c7c518c3074f895b28f1a35f5f3fe9 100644
--- a/pd/src/makefile.nt
+++ b/pd/src/makefile.nt
@@ -19,7 +19,7 @@ LIB = /NODEFAULTLIB:libcmt /NODEFAULTLIB:oldnames /NODEFAULTLIB:libc \
     $(LD2)\libcmt.lib $(LD2)\oldnames.lib
 
 CFLAGS = /nologo /W3 /DMSW /DNT /DPD /DPD_INTERNAL /DWIN32 /DWINDOWS /Ox \
-	-DPA_LITTLE_ENDIAN -DUSEAPI_MMIO -DUSEAPI_PORTAUDIO -D__i386__ -DPA19 \
+	-DPD -DPD_INTERNAL -DPA_LITTLE_ENDIAN -DUSEAPI_MMIO -DUSEAPI_PORTAUDIO -D__i386__ -DPA19 \
         -D_CRT_SECURE_NO_WARNINGS
 LFLAGS = /nologo
 
@@ -37,7 +37,7 @@ SRC = g_canvas.c g_graph.c g_text.c g_rtext.c g_array.c g_template.c g_io.c \
     d_ugen.c d_ctl.c d_arithmetic.c d_osc.c d_filter.c d_dac.c d_misc.c \
     d_math.c d_fft.c d_fft_mayer.c d_fftroutine.c d_array.c d_global.c \
     d_delay.c d_resample.c \
-    x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c \
+    x_arithmetic.c x_connective.c x_interface.c x_midi.c x_misc.c x_file.c \
     x_time.c x_acoustics.c x_net.c x_text.c x_gui.c x_list.c x_array.c d_soundfile.c \
     x_scalar.c x_vexp.c x_vexp_if.c x_vexp_fun.c $(SYSSRC)
 
diff --git a/pd/src/s_print.c b/pd/src/s_print.c
index 52d86e9a3239b25aa2e409f6cd187bccd0b5b9f4..3339282ee471f4d43c942c787421a3e86e8b69bb 100644
--- a/pd/src/s_print.c
+++ b/pd/src/s_print.c
@@ -17,6 +17,58 @@ t_printhook sys_printhook;
 t_printhook sys_printhook_error;
 int sys_printtostderr;
 
+#ifdef _WIN32
+
+    /* NB: Unlike vsnprintf(), _vsnprintf() does *not* null-terminate
+    the output if the resulting string is too large to fit into the buffer.
+    Also, it just returns -1 instead of the required number of bytes.
+    Strictly speaking, the UCRT in Windows 10 actually contains a standard-
+    conforming vsnprintf() function that is not just an alias for _vsnprintf().
+    However, MinGW traditionally links against the old msvcrt.dll runtime library.
+    Recent versions of MinGW seem to have their own (standard-conformating)
+    implementation of vsnprintf(), but to ensure portability we rather use our
+    own implementation for all Windows builds. */
+int pd_vsnprintf(char *buf, size_t size, const char *fmt, va_list argptr)
+{
+    int ret = _vsnprintf(buf, size, fmt, argptr);
+    if (ret < 0)
+    {
+            /* null-terminate the buffer and get the required number of bytes. */
+        ret = _vscprintf(fmt, argptr);
+        buf[size - 1] = '\0';
+    }
+    return ret;
+}
+
+int pd_snprintf(char *buf, size_t size, const char *fmt, ...)
+{
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = pd_vsnprintf(buf, size, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+#else
+
+int pd_vsnprintf(char *buf, size_t size, const char *fmt, va_list argptr)
+{
+    return vsnprintf(buf, size, fmt, argptr);
+}
+
+int pd_snprintf(char *buf, size_t size, const char *fmt, ...)
+{
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = vsnprintf(buf, size, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+#endif
+
 static char* strnpointerid(char *dest, const void *pointer, size_t len)
 {
     *dest=0;
diff --git a/pd/src/s_stuff.h b/pd/src/s_stuff.h
index 53af1dad7a8a2ad5906ae158de7b6387954926e1..a37edd030e259764340c93cce7e20eb857adfd61 100644
--- a/pd/src/s_stuff.h
+++ b/pd/src/s_stuff.h
@@ -443,4 +443,9 @@ EXTERN void alist_anything(t_alist *x, t_symbol *s, int argc, t_atom *argv);
 EXTERN void alist_toatoms(t_alist *x, t_atom *to, int onset, int count);
 EXTERN void alist_clone(t_alist *x, t_alist *y, int onset, int count);
 
+/* safe cross-platform alternatives to snprintf and vsnprintf. */
+EXTERN int pd_snprintf(char *buf, size_t size, const char *fmt, ...);
+EXTERN int pd_vsnprintf(char *buf, size_t size, const char *fmt,
+    va_list argptr);
+
 #endif /* __s_stuff_h_ */
diff --git a/pd/src/x_file.c b/pd/src/x_file.c
new file mode 100644
index 0000000000000000000000000000000000000000..b658bc26533b0deda87e351460f622b56ddb30e1
--- /dev/null
+++ b/pd/src/x_file.c
@@ -0,0 +1,1964 @@
+/* Copyright (c) 2021 IOhannes m zmölnig.
+ * For information on usage and redistribution, and for a DISCLAIMER OF ALL
+ * WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */
+/* The "file" object. */
+#define _XOPEN_SOURCE 600
+#define _DEFAULT_SOURCE
+
+#include "m_pd.h"
+#include "g_canvas.h"
+#include "s_stuff.h"
+#include "s_utf8.h"
+
+#define PD_INTERNAL
+#include "m_private_utils.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <errno.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <direct.h>
+# include <io.h>
+#else
+# include <glob.h>
+# include <ftw.h>
+#endif
+
+#ifdef _MSC_VER
+# include <BaseTsd.h>
+typedef unsigned int mode_t;
+typedef SSIZE_T ssize_t;
+# define wstat _wstat
+#endif
+
+#ifndef S_ISREG
+  #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
+#endif
+#ifndef S_ISDIR
+# define S_ISDIR(mode)  (((mode) & S_IFMT) == S_IFDIR)
+#endif
+
+#ifdef _WIN32
+static int do_delete_ucs2(wchar_t*pathname) {
+    struct stat sb;
+    if (!wstat(pathname, &sb) && (S_ISDIR(sb.st_mode))) {
+            /* a directory */
+        return !(RemoveDirectoryW(pathname));
+    } else {
+            /* probably a file */
+        return !(DeleteFileW(pathname));
+    }
+}
+
+static int sys_stat(const char *pathname, struct stat *statbuf) {
+    uint16_t ucs2buf[MAX_PATH];
+    u8_utf8toucs2(ucs2buf, MAX_PATH, pathname, strlen(pathname));
+    return wstat(ucs2buf, statbuf);
+}
+
+static int sys_rename(const char *oldpath, const char *newpath) {
+    uint16_t src[MAX_PATH], dst[MAX_PATH];
+    u8_utf8toucs2(src, MAX_PATH, oldpath, MAX_PATH);
+    u8_utf8toucs2(dst, MAX_PATH, newpath, MAX_PATH);
+    return _wrename(src, dst);
+}
+static int sys_mkdir(const char *pathname, mode_t mode) {
+    uint16_t ucs2name[MAX_PATH];
+    (void)mode;
+    u8_utf8toucs2(ucs2name, MAX_PATH, pathname, MAX_PATH);
+    return !(CreateDirectoryW(ucs2name, 0));
+}
+static int sys_remove(const char *pathname) {
+    uint16_t ucs2buf[MAXPDSTRING];
+    u8_utf8toucs2(ucs2buf, MAXPDSTRING, pathname, MAXPDSTRING);
+    return do_delete_ucs2(ucs2buf);
+}
+static char* sys_getcwd(char *buf) {
+    uint16_t ucs2buf[MAXPDSTRING];
+    memset(ucs2buf, 0, sizeof(ucs2buf));
+    if (!_wgetcwd(ucs2buf, MAXPDSTRING))
+        return 0;
+
+    u8_ucs2toutf8(buf, MAXPDSTRING-1, ucs2buf, -1);
+    buf[MAXPDSTRING-1] = 0;
+    sys_unbashfilename(buf, buf);
+    return buf;
+}
+static int sys_chdir(const char *path) {
+    uint16_t ucs2buf[MAXPDSTRING];
+    u8_utf8toucs2(ucs2buf, MAXPDSTRING, path, MAXPDSTRING);
+    return _wchdir(ucs2buf);
+}
+#else
+static int sys_stat(const char *pathname, struct stat *statbuf) {
+    return stat(pathname, statbuf);
+}
+
+static int sys_rename(const char *oldpath, const char *newpath) {
+    return rename(oldpath, newpath);
+}
+static int sys_mkdir(const char *pathname, mode_t mode) {
+    return mkdir(pathname, mode);
+}
+static int sys_remove(const char *pathname) {
+    return remove(pathname);
+}
+static char* sys_getcwd(char *buf) {
+    return getcwd(buf, MAXPDSTRING);
+}
+static int sys_chdir(const char *path) {
+    return chdir(path);
+}
+#endif
+
+    /* expand env vars and ~ at the beginning of a path and make a copy to return */
+static char*do_expandpath(const char *from, char *to, int bufsize)
+{
+    if ((strlen(from) == 1 && from[0] == '~') || (strncmp(from,"~/", 2) == 0))
+    {
+#ifdef _WIN32
+        const char *home = getenv("USERPROFILE");
+#else
+        const char *home = getenv("HOME");
+#endif
+        if (home)
+        {
+            strncpy(to, home, bufsize);
+            to[bufsize-1] = 0;
+            strncpy(to + strlen(to), from + 1, bufsize - strlen(to));
+            to[bufsize-1] = 0;
+        }
+        else *to = 0;
+    }
+    else
+    {
+        strncpy(to, from, bufsize);
+        to[bufsize-1] = 0;
+    }
+#ifdef _WIN32
+    {
+        char *buf = alloca(bufsize);
+        ExpandEnvironmentStrings(to, buf, bufsize-1);
+        buf[bufsize-1] = 0;
+        strncpy(to, buf, bufsize);
+        to[bufsize-1] = 0;
+    }
+#endif
+    return to;
+}
+
+    /* unbash '\' to '/', and drop duplicate '/' */
+static char*do_pathnormalize(const char *from, char *to) {
+    const char *rp;
+    char *wp, c;
+    sys_unbashfilename(from, to);
+    rp=wp=to;
+    while((*wp++=c=*rp++)) {
+        if('/' == c) {
+            while('/' == *rp++);
+            rp--;
+        }
+    }
+    return to;
+}
+
+static char*do_expandunbash(const char *from, char *to, int bufsize) {
+    do_expandpath(from, to, bufsize);
+    to[bufsize-1]=0;
+    sys_unbashfilename(to, to);
+    to[bufsize-1]=0;
+    return to;
+}
+
+static int str_endswith(char* str, char* end){
+    size_t strsize = strlen(str), endsize = strlen(end);
+    if(strsize<endsize) return 0;
+    return strcmp(str + strsize - endsize, end) == 0;
+}
+
+static t_symbol*do_splitpath(const char*path, int*argc, t_atom**argv) {
+    t_symbol*slashsym = gensym("/");
+    t_atom*outv;
+    int outc=0, outsize=1;
+    char buffer[MAXPDSTRING], *pathname=buffer;
+    sys_unbashfilename(path, buffer);
+    buffer[MAXPDSTRING-1] = 0;
+
+        /* first count the number of path components */
+    while(*pathname)
+        outsize += ('/'==*pathname++);
+    pathname=buffer;
+    outv = (t_atom*)getbytes(outsize * sizeof(*outv));
+
+    if('/' == *pathname)
+        SETSYMBOL(outv+outc, slashsym), outc++;
+
+    while(*pathname) {
+        char*pathsep;
+        while('/' == *pathname)
+            pathname++;
+        pathsep=strchr(pathname, '/');
+        if(!pathsep) {
+            if(*pathname)
+                SETSYMBOL(outv+outc, gensym(pathname)), outc++;
+            break;
+        }
+        *pathsep=0;
+        SETSYMBOL(outv+outc, gensym(pathname)), outc++;
+        pathname=pathsep+1;
+    }
+
+    if(outc != outsize) {
+        t_atom*a=resizebytes(outv, outsize * sizeof(*outv), outc * sizeof(*outv));
+        if(!a) {
+            freebytes(outv, outsize * sizeof(*outv));
+            outsize = outc = 0;
+        }
+        outv = a;
+    }
+    *argc = outc;
+    *argv = outv;
+    return (*pathname)?0:slashsym;
+}
+
+/* joins up all the path-components (using '/' as the path delimiter)
+ * the (optional) prefix is prepended to the string
+   (to be used for Windows volumes)
+ * the (optional) suffix is present in the result (and appended if required to the string)
+   (to be used for trailing slash)
+ * atoms that are A_NULL are skipped
+ */
+static t_symbol*do_joinpath(t_symbol*prefix, int argc, t_atom*argv, t_symbol*suffix) {
+        /* luckily for us, the path-separator in Pd is always '/' */
+    const char pathseparator = '/';
+    size_t alen, bufsize = 0;
+    char buffer[MAXPDSTRING];
+    t_symbol*result = 0;
+    int needseparator = 0;
+    int i;
+
+    memset(buffer, 0, sizeof(buffer));
+
+    if(prefix) {
+        strcpy(buffer+bufsize, prefix->s_name);
+        bufsize += strlen(prefix->s_name);
+    }
+
+    for(i=0; i<argc; i++) {
+        char sbuf[MAXPDSTRING];
+        const char*abuf=sbuf;
+        t_atom*a = argv+i;
+        switch(a->a_type) {
+        case A_NULL:
+            abuf = 0;
+            break;
+        case A_SYMBOL:
+            abuf = atom_getsymbol(a)->s_name;
+            break;
+        default:
+            atom_string(a, sbuf, MAXPDSTRING);
+            abuf = sbuf;
+            break;
+        }
+        if(!abuf || !(alen = strlen(abuf))) continue;
+        if(pathseparator == abuf[alen-1])
+            needseparator = 0;
+        if(bufsize+alen+needseparator >= sizeof(buffer))
+            break;
+        if(needseparator) {
+            buffer[bufsize]=pathseparator;
+            bufsize++;
+        }
+        needseparator=1;
+
+        strcpy(buffer+bufsize, abuf);
+        bufsize+=alen;
+    }
+
+    if(suffix && (alen=strlen(suffix->s_name)) && (bufsize+alen)<sizeof(buffer)) {
+        strcpy(buffer+bufsize, suffix->s_name);
+        bufsize+=alen;
+    }
+
+    result = gensym(do_pathnormalize(buffer, buffer));
+    return result;
+}
+
+
+#ifdef _WIN32
+static const char*do_errmsg(char*buffer, size_t bufsize) {
+    char errcode[10];
+    char*s;
+    wchar_t wbuf[MAXPDSTRING];
+    DWORD err = GetLastError();
+    DWORD count = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+        0, err, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), wbuf, MAXPDSTRING, NULL);
+    if (!count || !WideCharToMultiByte(CP_UTF8, 0, wbuf, count+1, buffer, bufsize, 0, 0))
+        *buffer = '\0';
+    s=buffer + strlen(buffer)-1;
+    while(('\r' == *s || '\n' == *s) && s>buffer)
+        *s--=0;
+    pd_snprintf(errcode, sizeof(errcode), " [%ld]", err);
+    errcode[sizeof(errcode)-1] = 0;
+    strcat(buffer, errcode);
+    return buffer;
+}
+#else /* !_WIN32 */
+static const char*do_errmsg(char*buffer, size_t bufsize) {
+    (void)buffer; (void)bufsize;
+    return strerror(errno);
+}
+#endif /* !_WIN32 */
+
+typedef struct _file_handler {
+    int fh_fd;
+    int fh_mode; /* 0..read, 1..write */
+} t_file_handler;
+#define x_fd x_fhptr->fh_fd
+#define x_mode x_fhptr->fh_mode
+typedef struct _file_handle {
+    t_object x_obj;
+    t_file_handler x_fh;
+    t_file_handler*x_fhptr;
+    t_symbol*x_fcname; /* multiple [file handle] object can refer to the same [file define] */
+
+    mode_t x_creationmode; /* default: 0666, 0777 */
+    int x_verbose; /* default: 0 */
+
+    t_canvas*x_canvas;
+    t_outlet*x_dataout;
+    t_outlet*x_infoout;
+
+} t_file_handle;
+
+
+static int do_checkpathname(t_file_handle*x, const char*path) {
+    int err_count = 0, warn_count = 0;
+    char buf[4];
+    const char*s;
+
+        /* check for illegal characters */
+    for(s = path; *s; s++) {
+        const char*ill = "illegal";
+        int oops = 0;
+        switch(*s) {
+        case ':': case '\\':
+            ill = "reserved";
+                /* falls through */
+        case '<': case '>':
+        case '|':
+        case '"':
+                //case '/':
+        case '?': case '*':
+            oops++;
+            break;
+        default:
+            if(*s<32)
+                oops++;
+        }
+
+        if(oops) {
+#ifdef _WIN32
+            if(x->x_verbose)
+                pd_error(x, "the path \"%s\" contains the character '%c', which is %s.", path, *s, ill);
+            err_count++;
+#else
+            if(x->x_verbose)
+                logpost(x, 3, "cross-platform issue: the path \"%s\" contains the character '%c', which is %s on MSW.", path, *s, ill);
+            warn_count++;
+#endif
+            goto fail;
+        }
+    }
+
+        /* check for illegal names */
+    strncpy(buf, path, sizeof(buf));
+    buf[3] = 0;
+    if(buf[2]) {
+        const char* forbidden_names0[] = {"AUX", "CON", "NUL", "PRN", 0};
+        const char* forbidden_names1[] = {"COM", "LPT", 0};
+        const char**ref;
+            /* upper-case the string */
+        int i;
+        for(i=0; i<3; i++) {
+            if (buf[i] >= 'a' && buf[i] <= 'z')
+                buf[i] = buf[i] - ('a' - 'A');
+        }
+            /* AUX, CON, NULL, PRN */
+        for(ref = forbidden_names0; *ref; ref++) {
+            if(!strcmp(*ref, buf)
+                && (!path[3] || '.' == path[3])) {
+#ifdef _WIN32
+                if(x->x_verbose)
+                    pd_error(x, "the path \"%s\" contains the reserved name '%s'.", path, *ref);
+                err_count++;
+#else
+                if(x->x_verbose)
+                    logpost(x, 3, "cross-platform issue: the path \"%s\" contains the name '%s' which is reserved on MSW.", path, *ref);
+                warn_count++;
+#endif
+                goto fail;
+            }
+        }
+            /* COM[1-9], LPT[1-9] */
+        for(ref = forbidden_names1; *ref; ref++) {
+            if(!strcmp(*ref, buf)
+                && path[3] > '0' && path[3] <= '9'
+                && (!path[4] || '.' == path[4])) {
+#ifdef _WIN32
+                if(x->x_verbose)
+                    pd_error(x, "the path \"%s\" contains the reserved name '%s[1-9]'.", path, *ref);
+                err_count++;
+#else
+                if(x->x_verbose)
+                    logpost(x, 3, "cross-platform issue: the path \"%s\" contains the name '%s[1-9]' which is reserved on MSW.", path, *ref);
+                warn_count++;
+#endif
+                goto fail;
+            }
+        }
+    }
+    return 0;
+
+ fail:
+    if(err_count>0)
+        return 2;
+    if(warn_count>0)
+        return 1;
+    return 0;
+}
+
+t_canvas*do_getparentcanvas(t_file_handle*x, int parentlevel, int*effectivelevel) {
+    t_canvas *c = x->x_canvas;
+    int i, level = 0;
+    for (i = 0; i < parentlevel; i++)
+    {
+        while (!c->gl_env)  /* back up to containing canvas or abstraction */
+            c = c->gl_owner;
+        if (c->gl_owner)    /* back up one more into an owner if any */
+        {
+            c = c->gl_owner;
+            level++;
+        }
+    }
+    if(effectivelevel)
+        *effectivelevel = level;
+    return c;
+}
+
+
+static int do_parse_creationmode(t_atom*ap) {
+    const char*s;
+    if(A_FLOAT==ap->a_type)
+        return atom_getfloat(ap);
+    if(A_SYMBOL!=ap->a_type)
+        return -1; /* oopsie */
+    s = atom_getsymbol(ap)->s_name;
+    if(!strncmp(s, "0o", 2)) {
+            /* octal mode */
+        char*endptr;
+        long mode = strtol(s+2, &endptr, 8);
+        return (*endptr)?-1:(int)mode;
+    } else if(!strncmp(s, "0x", 2)) {
+            /* hex mode: nobody sane uses this... */
+        char*endptr;
+        long mode = strtol(s+2, &endptr, 16);
+        return (*endptr)?-1:(int)mode;
+    } else {
+            /* free form mode: a+rwx,go-w */
+            /* not supported yet */
+        return -1;
+    }
+    return -1;
+}
+
+static void do_parse_args(t_file_handle*x, int argc, t_atom*argv) {
+        /*
+         * -q: quiet mode
+         * -v: verbose mode
+         * -m <mode>: creation mode
+         */
+    t_symbol*flag_m = gensym("-m");
+    t_symbol*flag_q = gensym("-q");
+    t_symbol*flag_v = gensym("-v");
+    x->x_fcname = 0;
+    while(argc--) {
+        const t_symbol*flag = atom_getsymbol(argv);
+        if (0);
+        else if (flag == flag_q) {
+            x->x_verbose--;
+        } else if (flag == flag_v) {
+            x->x_verbose++;
+        } else if (flag == flag_m) {
+            int mode;
+            if(!argc) {
+                pd_error(x, "'-m' requires an argument");
+                break;
+            }
+            argc--;
+            argv++;
+            mode = do_parse_creationmode(argv);
+            if(mode<0) {
+                char buf[MAXPDSTRING];
+                atom_string(argv, buf, MAXPDSTRING);
+                pd_error(x, "invalid creation mode '%s'", buf);
+                break;
+            } else {
+                x->x_creationmode = mode;
+            }
+        } else {
+            int filearg = (!argc);
+            if(filearg) {
+                x->x_fcname = (t_symbol*)flag;
+            } else {
+                pd_error(x, "unknown flag %s", flag->s_name);
+            }
+            break;
+        }
+        argv++;
+    }
+    x->x_verbose = x->x_verbose > 0;
+}
+
+static t_file_handle* do_file_handle_new(t_class*cls, t_symbol*s, int argc, t_atom*argv, int verbose, mode_t creationmode) {
+    t_file_handle*x = (t_file_handle*)pd_new(cls);
+    (void)s;
+    x->x_fhptr = &x->x_fh;
+    x->x_fd = -1;
+    x->x_canvas = canvas_getcurrent();
+    x->x_creationmode = creationmode;
+    x->x_verbose = verbose;
+
+    x->x_dataout = outlet_new(&x->x_obj, 0);
+    x->x_infoout = outlet_new(&x->x_obj, 0);
+    do_parse_args(x, argc, argv);
+    return x;
+}
+
+static int do_file_open(t_file_handle*x, const char* filename, int mode) {
+    char expandbuf[MAXPDSTRING+1];
+    int fd = sys_open(do_expandpath(filename, expandbuf, MAXPDSTRING), mode, x?x->x_creationmode:0666);
+    if(x) {
+        x->x_fd = fd;
+        if(fd<0) {
+            if(x->x_verbose)
+                pd_error(x, "unable to open '%s': %s", filename, strerror(errno));
+            if(x->x_infoout)
+                outlet_bang(x->x_infoout);
+        }
+    }
+    return fd;
+}
+
+
+static void file_set_verbosity(t_file_handle*x, t_float f) {
+    x->x_verbose = (f>0.5);
+}
+static void file_set_creationmode(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    if(argc!=1) {
+        pd_error(x, "usage: '%s <mode>'", s->s_name);
+        return;
+    }
+    x->x_creationmode = do_parse_creationmode(argv);
+}
+
+    /* ================ [file handle] ====================== */
+t_class *file_define_class;
+static int file_handle_getdefine(t_file_handle*x) {
+    if(x->x_fcname) {
+        t_file_handle *y = (t_file_handle *)pd_findbyclass(x->x_fcname,
+                                                           file_define_class);
+        if(y) {
+            x->x_fhptr=&y->x_fh;
+            return 1;
+        }
+        return 0;
+    }
+    x->x_fhptr = &x->x_fh;
+    return 1;
+}
+
+static void file_handle_close(t_file_handle*x) {
+    if (x->x_fd>=0)
+        sys_close(x->x_fd);
+    x->x_fd = -1;
+}
+static int file_handle_checkopen(t_file_handle*x, const char*cmd) {
+    if(x->x_fcname) {
+        if(!file_handle_getdefine(x)) {
+            pd_error(x, "file handle: couldn't find file-define '%s'", x->x_fcname->s_name);
+            return 0;
+        }
+    }
+    if(x->x_fd<0) {
+        if(!cmd)cmd=(x->x_mode)?"write":"read";
+        pd_error(x, "'%s' without prior 'open'", cmd);
+        outlet_bang(x->x_infoout);
+        return 0;
+    }
+    return 1;
+}
+static void file_handle_do_read(t_file_handle*x, t_float f) {
+    t_atom*outv;
+    unsigned char*buf;
+    ssize_t n, len, outc=f;
+    if(outc<1) {
+        pd_error(x, "cannot read %d bytes", (int)outc);
+        return;
+    }
+    ALLOCA(unsigned char, buf, outc, 100);
+    ALLOCA(t_atom, outv, outc, 100);
+    if(buf && outv) {
+        len = read(x->x_fd, buf, outc);
+        for(n=0; n<len; n++) {
+            SETFLOAT(outv+n, (t_float)buf[n]);
+        }
+        if(len>0) {
+            outlet_list(x->x_dataout, gensym("list"), len, outv);
+        } else if (!len) {
+            file_handle_close(x);
+            outlet_bang(x->x_infoout);
+        } else {
+            if(x->x_verbose)
+                pd_error(x, "read failed: %s", strerror(errno));
+            file_handle_close(x);
+            outlet_bang(x->x_infoout);
+        }
+    } else {
+        pd_error(x, "couldn't allocate buffer for %d bytes", (int)outc);
+    }
+    FREEA(unsigned char, buf, outc, 100);
+    FREEA(t_atom, outv, outc, 100);
+}
+static void file_handle_do_write(t_file_handle*x, int argc, t_atom*argv) {
+    unsigned char*buf;
+    size_t len = (argc>0)?argc:0;
+    ALLOCA(unsigned char, buf, argc, 100);
+    if(buf) {
+        ssize_t n;
+        for(n=0; n<argc; n++) {
+            buf[n] = atom_getfloat(argv+n);
+        }
+        n = write(x->x_fd, buf, len);
+        if(n >= 0 && (size_t)n < len) {
+            n = write(x->x_fd, buf+n, len-n);
+        }
+        if (n<0) {
+            pd_error(x, "write failed: %s", strerror(errno));
+            file_handle_close(x);
+            outlet_bang(x->x_infoout);
+        }
+    } else {
+        pd_error(x, "could not allocate %d bytes for writing", argc);
+    }
+    FREEA(unsigned char, buf, argc, 100);
+}
+static void file_handle_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    (void)s;
+    if(!file_handle_checkopen(x, 0))
+        return;
+    if(x->x_mode) {
+            /* write_mode */
+        file_handle_do_write(x, argc, argv);
+        } else {
+            /* read mode */
+        if(1==argc && A_FLOAT==argv->a_type) {
+            file_handle_do_read(x, atom_getfloat(argv));
+        } else {
+            pd_error(x, "no way to handle 'list' messages while reading file");
+        }
+    }
+}
+static void file_handle_set(t_file_handle*x, t_symbol*s) {
+    if (gensym("") == s)
+        s=0;
+    if (s && x->x_fhptr == &x->x_fh && x->x_fh.fh_fd >= 0) {
+            /* trying to set a name, even though we have an fd open... */
+        pd_error(x, "file handle: shadowing local file descriptor with '%s'", s->s_name);
+    } else if (!s && x->x_fhptr != &x->x_fh && x->x_fh.fh_fd >= 0) {
+        logpost(x, 3, "file handle: unshadowing local file descriptor");
+    }
+    x->x_fcname = s;
+    file_handle_getdefine(x);
+}
+static void file_handle_seek(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    off_t offset=0;
+    int whence = SEEK_SET;
+    t_atom a[1];
+    switch(argc) {
+    case 0:
+            /* just output the current position */
+        whence=SEEK_CUR;
+        break;
+    case 2: {
+        if (A_SYMBOL!=argv[1].a_type)
+            goto usage;
+        s=atom_getsymbol(argv+1);
+        switch(*s->s_name) {
+        case 'S': case 's': case 0:
+            whence = SEEK_SET;
+            break;
+        case 'E': case 'e':
+            whence = SEEK_END;
+            break;
+        case 'C': case 'c': case 'R': case 'r':
+            whence = SEEK_CUR;
+            break;
+        default:
+            pd_error(x, "seek mode must be 'set', 'end' or 'current' (resp. 'relative')");
+            return;
+        }
+    }
+            /* falls through */
+    case 1:
+        if (A_FLOAT!=argv[0].a_type)
+            goto usage;
+        offset = (int)atom_getfloat(argv);
+        break;
+    }
+
+    if(!file_handle_checkopen(x, "seek"))
+        return;
+    offset = lseek(x->x_fd, offset, whence);
+    SETFLOAT(a, offset);
+    outlet_anything(x->x_infoout, gensym("seek"), 1, a);
+    return;
+ usage:
+    pd_error(x, "usage: seek [<int:offset> [<symbol:mode>]]");
+}
+static void file_handle_open(t_file_handle*x, t_symbol*file, t_symbol*smode) {
+    int mode = O_RDONLY;
+    if (x->x_fd>=0) {
+        pd_error(x, "'open' without prior 'close'");
+        return;
+    }
+    if(!file_handle_getdefine(x)) {
+        pd_error(x, "file handle: couldn't find file-define '%s'", x->x_fcname->s_name);
+        return;
+    }
+    if(smode && smode!=&s_) {
+        switch(smode->s_name[0]) {
+        case 'r': /* read */
+            mode = O_RDONLY;
+            break;
+        case 'w': /* write */
+            mode = O_WRONLY;
+            break;
+        case 'a': /* append */
+            mode = O_WRONLY | O_APPEND;
+            break;
+        case 'c': /* create */
+            mode = O_WRONLY | O_TRUNC;
+            break;
+        }
+    }
+    if(mode & O_WRONLY) {
+        mode |= O_CREAT;
+    }
+    if(do_file_open(x, file->s_name, mode)>=0) {
+            /* check if we haven't accidentally opened a directory */
+        struct stat sb;
+        if(fstat(x->x_fd, &sb)) {
+            file_handle_close(x);
+            if(x->x_verbose)
+                pd_error(x, "unable to stat '%s': %s", file->s_name, strerror(errno));
+            outlet_bang(x->x_infoout);
+            return;
+        }
+        if(S_ISDIR(sb.st_mode)) {
+            file_handle_close(x);
+            if(x->x_verbose)
+                pd_error(x, "unable to open directory '%s' as file", file->s_name);
+            outlet_bang(x->x_infoout);
+            return;
+        }
+        x->x_mode = (mode&O_WRONLY)?1:0;
+    }
+}
+
+static void file_handle_free(t_file_handle*x) {
+        /* close our own file handle (if any) */
+    x->x_fhptr = &x->x_fh;
+    file_handle_close(x);
+}
+
+    /* ================ [file stat] ====================== */
+static int do_file_stat(t_file_handle*x, const char*filename, struct stat*sb, int*is_symlink) {
+    int result = -1;
+    int fd = -1;
+    char buf[MAXPDSTRING+1];
+    do_expandpath(filename, buf, MAXPDSTRING);
+
+    if(is_symlink) {
+        *is_symlink=0;
+#ifdef S_IFLNK
+        if(!lstat(buf, sb)) {
+            *is_symlink = !!(S_ISLNK(sb->st_mode));
+        }
+#endif
+    }
+    result = sys_stat(buf, sb);
+    if(!result)
+        return result;
+
+    fd = do_file_open(0, filename, 0);
+
+    if(fd >= 0) {
+        result = fstat(fd, sb);
+        sys_close(fd);
+    } else
+        result = -1;
+
+    if(x) {
+        x->x_fd = -1;
+        if(result && x->x_verbose) {
+            pd_error(x, "could not stat on '%s': %s", filename, strerror(errno));
+        }
+    }
+    return result;
+}
+static void do_dataout_symbol(t_file_handle*x, const char*selector, t_symbol*s) {
+    t_atom ap[1];
+    SETSYMBOL(ap, s);
+    outlet_anything(x->x_dataout, gensym(selector), 1, ap);
+}
+static void do_dataout_float(t_file_handle*x, const char*selector, t_float f) {
+    t_atom ap[1];
+    SETFLOAT(ap, f);
+    outlet_anything(x->x_dataout, gensym(selector), 1, ap);
+}
+static void do_dataout_time(t_file_handle*x, const char*selector, time_t t) {
+    t_atom ap[7];
+    struct tm *ts = localtime(&t);
+    if(!ts) {
+        pd_error(x, "unable to convert timestamp %ld", (long int)t);
+    }
+    SETFLOAT(ap+0, ts->tm_year + 1900);
+    SETFLOAT(ap+1, ts->tm_mon + 1);
+    SETFLOAT(ap+2, ts->tm_mday);
+    SETFLOAT(ap+3, ts->tm_hour);
+    SETFLOAT(ap+4, ts->tm_min);
+    SETFLOAT(ap+5, ts->tm_sec);
+    SETFLOAT(ap+6, ts->tm_isdst);
+    outlet_anything(x->x_dataout, gensym(selector), 7, ap);
+}
+static void file_stat_symbol(t_file_handle*x, t_symbol*filename) {
+        /* get all the info for the given file */
+    struct stat sb;
+    t_symbol*s;
+    int is_symlink=0;
+    int readable=0, writable=0, executable=0, owned=-1;
+    char buf[MAXPDSTRING+1];
+
+    if(do_file_stat(x, filename->s_name, &sb, &is_symlink) < 0) {
+        outlet_bang(x->x_infoout);
+        return;
+    }
+
+        /* this is wrong: readable/writable/executable are supposed to report
+         * on the *current* user, not the *owner*
+         */
+    readable = !!(sb.st_mode & 0400);
+    writable = !!(sb.st_mode & 0200);
+    executable = !!(sb.st_mode & 0100);
+#ifdef HAVE_UNISTD_H
+        /* this is the right way */
+    do_expandpath(filename->s_name, buf, MAXPDSTRING);
+
+    readable = !(access(buf, R_OK));
+    writable = !(access(buf, W_OK));
+    executable = !(access(buf, X_OK));
+#ifndef _WIN32
+    owned = (geteuid() == sb.st_uid);
+#endif
+#endif
+
+    switch (sb.st_mode & S_IFMT) {
+    case S_IFREG:
+#ifdef S_IFLNK
+    case S_IFLNK:
+#endif
+        do_dataout_float(x, "size", (int)(sb.st_size));
+        break;
+    case S_IFDIR:
+        do_dataout_float(x, "size", 0);
+        break;
+    default:
+        do_dataout_float(x, "size", -1);
+        break;
+    }
+    do_dataout_float(x, "readable", readable);
+    do_dataout_float(x, "writable", writable);
+    do_dataout_float(x, "executable", executable);
+    do_dataout_float(x, "owned", owned);
+
+    do_dataout_float(x, "isfile", !!(S_ISREG(sb.st_mode)));
+    do_dataout_float(x, "isdirectory", !!(S_ISDIR(sb.st_mode)));
+    do_dataout_float(x, "issymlink", is_symlink);
+
+    do_dataout_float(x, "uid", (int)(sb.st_uid));
+    do_dataout_float(x, "gid", (int)(sb.st_gid));
+    do_dataout_float(x, "permissions", (int)(sb.st_mode & 0777));
+    switch (sb.st_mode & S_IFMT) {
+    case S_IFREG:  s = gensym("file");            break;
+    case S_IFDIR:  s = gensym("directory");       break;
+#ifdef S_IFBLK
+    case S_IFBLK:  s = gensym("blockdevice");     break;
+#endif
+#ifdef S_IFCHR
+    case S_IFCHR:  s = gensym("characterdevice"); break;
+#endif
+#ifdef S_IFIFO
+    case S_IFIFO:  s = gensym("pipe");            break;
+#endif
+#ifdef S_IFLNK
+    case S_IFLNK:  s = gensym("symlink");         break;
+#endif
+#ifdef S_IFSOCK
+    case S_IFSOCK: s = gensym("socket");          break;
+#endif
+    default:       s = 0;                         break;
+    }
+    if(s)
+        do_dataout_symbol(x, "type", s);
+    else
+        do_dataout_symbol(x, "type", gensym("unknown"));
+
+    do_dataout_time(x, "atime", sb.st_atime);
+    do_dataout_time(x, "mtime", sb.st_mtime);
+}
+
+static void file_size_symbol(t_file_handle*x, t_symbol*filename) {
+    struct stat sb;
+    if(do_file_stat(x, filename->s_name, &sb, 0) < 0) {
+        outlet_bang(x->x_infoout);
+    } else {
+        switch (sb.st_mode & S_IFMT) {
+        case S_IFREG:
+#ifdef S_IFLNK
+        case S_IFLNK:
+#endif
+            outlet_float(x->x_dataout, (int)(sb.st_size));
+            break;
+        case S_IFDIR:
+            outlet_float(x->x_dataout, 0);
+            break;
+        default:
+            outlet_float(x->x_dataout, -1);
+            break;
+        }
+    }
+}
+static void file_isfile_symbol(t_file_handle*x, t_symbol*filename) {
+    struct stat sb;
+    if(do_file_stat(x, filename->s_name, &sb, 0) < 0) {
+        outlet_bang(x->x_infoout);
+    } else {
+        outlet_float(x->x_dataout, !!(S_ISREG(sb.st_mode)));
+    }
+}
+static void file_isdirectory_symbol(t_file_handle*x, t_symbol*filename) {
+    struct stat sb;
+    if(do_file_stat(x, filename->s_name, &sb, 0) < 0) {
+        outlet_bang(x->x_infoout);
+    } else {
+        outlet_float(x->x_dataout, !!(S_ISDIR(sb.st_mode)));
+    }
+}
+
+    /* ================ [file glob] ====================== */
+#ifdef _WIN32
+/* idiosyncrasies:
+ * - cases are ignored ('a*' matches 'A.txt' and 'a.txt'), even with wine on ext4
+ * - only the filename component is returned (must prefix path separately)
+ * - non-ASCII needs special handling
+ * - '*?' seems to be illegal (e.g. 'f*?.txt'); '?*' seems to be fine though
+ * - "*" matches files starting with '.' (including '.', '..', but also .gitignore)
+ * - if the pattern includes '*.', it matches a trailing '~'
+ * - wildcards do not apply to directory-components (e.g. 'foo/ * /' (without the spaces, they are just due to C-comments constraints))
+ *
+ * plan:
+ * - concat the path and the filename
+ * - convert to utf16 (and back again)
+ * - replace '*?' with '*' in the pattern
+ * - manually filter out:
+ *   - matches starting with '.' if the pattern does not start with '.'
+ *   - matches ending in '~' if the pattern does not end with '[*?~]'
+ * - only (officially) support wildcards in the filename component (not in the paths)
+ * - if the pattern ends with '/', strip it, but return only directories
+ */
+static void file_glob_symbol(t_file_handle*x, t_symbol*spattern) {
+    WIN32_FIND_DATAW FindFileData;
+    HANDLE hFind;
+    uint16_t ucs2pattern[MAXPDSTRING];
+    char pattern[MAXPDSTRING];
+    int nostartdot=0, noendtilde=0, onlydirs=0;
+    char *filepattern, *strin, *strout;
+    int pathpatternlength=0;
+    int matchdot=0;
+
+    do_expandunbash(spattern->s_name, pattern, MAXPDSTRING);
+
+        /* '.' and '..' should only match if the pattern exquisitely asked for them */
+    if(!strcmp(".", pattern) || !strcmp("./", pattern)
+        || str_endswith(pattern, "/.") || str_endswith(pattern, "/./"))
+        matchdot=1;
+    else if(!strcmp("..", pattern) || !strcmp("../", pattern)
+        || str_endswith(pattern, "/..") || str_endswith(pattern, "/../"))
+        matchdot=2;
+
+    if (matchdot) {
+            /* windows FindFile would return the actual path rather than '.'
+             * (which would confuse our full-path construction)
+             * so we just return the result directly
+             */
+        struct stat sb;
+        if (!do_file_stat(0, pattern, &sb, 0)) {
+            t_atom outv[2];
+            size_t end = strlen(pattern);
+                /* get rid of trailing slash */
+            if('/' == pattern[end-1])
+                pattern[end-1]=0;
+            SETSYMBOL(outv+0, gensym(pattern));
+            SETFLOAT(outv+1, S_ISDIR(sb.st_mode));
+            outlet_list(x->x_dataout, gensym("list"), 2, outv);
+        } else {
+                // this gets triggered if there is no match...
+            outlet_bang(x->x_infoout);
+        }
+        return;
+    }
+
+
+    filepattern=strrchr(pattern, '/');
+    if(filepattern && !filepattern[1]) {
+            /* patterns ends with slashes: filter for dirs, and bash the trailing slashes */
+        onlydirs=1;
+        while('/' == *filepattern && filepattern>pattern) {
+            *filepattern--=0;
+        }
+        filepattern=strrchr(pattern, '/');
+    }
+    if(!filepattern)
+        filepattern=pattern;
+    else {
+        filepattern++;
+        pathpatternlength=filepattern-pattern;
+    }
+    nostartdot=('.' != *filepattern);
+    strin=filepattern;
+    strout=filepattern;
+    while(*strin) {
+        char c = *strin++;
+        *strout++ = c;
+        if('*' == c) {
+            while('?' == *strin || '*' == *strin)
+                strin++;
+        }
+    }
+    *strout=0;
+    if (strout>pattern) {
+        switch(strout[-1]) {
+        case '~':
+        case '*':
+        case '?':
+            noendtilde=0;
+            break;
+        default:
+            noendtilde=1;
+        }
+    }
+    u8_utf8toucs2(ucs2pattern, MAXPDSTRING, pattern, MAXPDSTRING);
+
+    hFind = FindFirstFileW(ucs2pattern, &FindFileData);
+    if (hFind == INVALID_HANDLE_VALUE) {
+            // this gets triggered if there is no match...
+        outlet_bang(x->x_infoout);
+        return;
+    }
+    do {
+        t_symbol*s;
+        t_atom outv[2];
+        int len = 0;
+        int isdir = !!(FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes);
+        if (matchdot!=1 && !wcscmp(L"." , FindFileData.cFileName))
+            continue;
+        if (matchdot!=2 && !wcscmp(L".." , FindFileData.cFileName))
+            continue;
+        u8_ucs2toutf8(filepattern, MAXPDSTRING-pathpatternlength, FindFileData.cFileName, MAX_PATH);
+        len = strlen(filepattern);
+
+        if(onlydirs && !isdir)
+            continue;
+        if(nostartdot && '.' == filepattern[0])
+            continue;
+        if(noendtilde && '~' == filepattern[len-1])
+            continue;
+
+        s = gensym(pattern);
+        SETSYMBOL(outv+0, s);
+        SETFLOAT(outv+1, isdir);
+        outlet_list(x->x_dataout, gensym("list"), 2, outv);
+    } while (FindNextFileW(hFind, &FindFileData) != 0);
+    FindClose(hFind);
+}
+#else /* !_WIN32 */
+static void file_glob_symbol(t_file_handle*x, t_symbol*spattern) {
+    t_atom outv[2];
+    glob_t gg;
+    int flags = 0;
+    int matchdot=0;
+    char pattern[MAXPDSTRING];
+    size_t patternlen;
+    int onlydirs;
+    do_expandpath(spattern->s_name, pattern, MAXPDSTRING);
+    patternlen=strlen(pattern);
+    onlydirs = ('/' == pattern[patternlen-1]);
+    if(!strcmp(".", pattern) || !strcmp("./", pattern)
+        || str_endswith(pattern, "/.") || str_endswith(pattern, "/./"))
+        matchdot=1;
+    else if(!strcmp("..", pattern) || !strcmp("../", pattern)
+        || str_endswith(pattern, "/..") || str_endswith(pattern, "/../"))
+        matchdot=2;
+    if(glob(pattern, flags, NULL, &gg)) {
+            // this gets triggered if there is no match...
+        outlet_bang(x->x_infoout);
+    } else {
+        size_t i;
+        for(i=0; i<gg.gl_pathc; i++) {
+            t_symbol *s;
+            char*path = gg.gl_pathv[i];
+            int isdir = 0;
+            struct stat sb;
+            int end;
+            if(!do_file_stat(0, path, &sb, 0)) {
+                isdir = S_ISDIR(sb.st_mode);
+            }
+            if(onlydirs && !isdir)
+                continue;
+            end=strlen(path);
+            if('/' == path[end-1]) {
+                path[end-1]=0;
+            }
+            if (matchdot!=1 && (!strcmp(path, ".") || str_endswith(path, "/.")))
+                continue;
+            if (matchdot!=2 && (!strcmp(path, "..") || str_endswith(path, "/..")))
+                continue;
+
+            s = gensym(path);
+            SETSYMBOL(outv+0, s);
+            SETFLOAT(outv+1, isdir);
+            outlet_list(x->x_dataout, gensym("list"), 2, outv);
+        }
+    }
+    globfree(&gg);
+}
+#endif /* _WIN32 */
+
+
+    /* ================ [file which] ====================== */
+
+static void file_which_doit(t_file_handle*x, t_symbol*s, int depth) {
+    char pathname[MAXPDSTRING];
+    t_canvas*c = do_getparentcanvas(x, depth, 0);
+    do_expandunbash(s->s_name, pathname, MAXPDSTRING);
+        /* LATER we might output directories as well,... */
+    int isdir=0;
+    t_atom outv[2];
+    char dirresult[MAXPDSTRING], *nameresult;
+    int fd = canvas_open(c,
+        pathname, "",
+        dirresult, &nameresult, MAXPDSTRING,
+        1);
+    if(fd>=0) {
+        sys_close(fd);
+        if(nameresult>dirresult)
+            nameresult[-1]='/';
+        SETSYMBOL(outv+0, gensym(dirresult));
+        SETFLOAT(outv+1, isdir);
+        outlet_list(x->x_dataout, gensym("list"), 2, outv);
+    } else {
+        outlet_symbol(x->x_infoout, s);
+    }
+}
+
+static void file_which_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    const char*msg = s?s->s_name:"";
+    int parentlevel = 0;
+    t_symbol*path;
+
+    switch(argc) {
+    default: goto fail;
+    case 1:
+        switch(argv->a_type) {
+        case(A_SYMBOL):
+            path = atom_getsymbol(argv);
+            break;
+        default: goto fail;
+        }
+        break;
+    case 2:
+        if(A_SYMBOL == argv[0].a_type && A_FLOAT == argv[1].a_type) {
+            path = atom_getsymbol(argv+0);
+            parentlevel = (int)atom_getfloat(argv+1);
+            break;
+        }
+        goto fail;
+    }
+
+    if(path) {
+        file_which_doit(x, path, parentlevel);
+        return;
+    }
+
+ fail:
+    pd_error(x, "bad arguments for %s%smessage to object 'file which'", msg, *msg?" ":"");
+}
+
+
+    /* ================ [file patchpath] ====================== */
+
+static void file_patchpath_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    const char*msg = s?s->s_name:"";
+    t_canvas *c;
+    const char*path = 0;
+    int parentlevel = 0, effectivelevel = 0;
+
+    switch(argc) {
+    default: goto fail;
+    case 0: break;
+    case 1:
+        switch(argv->a_type) {
+        case(A_SYMBOL):
+            path = atom_getsymbol(argv)->s_name;
+            break;
+        case (A_FLOAT):
+            parentlevel = (int)atom_getfloat(argv);
+            break;
+        default: goto fail;
+        }
+        break;
+    case 2:
+        if(A_SYMBOL == argv[0].a_type && A_FLOAT == argv[1].a_type) {
+            path = atom_getsymbol(argv+0)->s_name;
+            parentlevel = (int)atom_getfloat(argv+1);
+            break;
+        }
+        goto fail;
+    }
+
+    c = do_getparentcanvas(x, parentlevel, &effectivelevel);
+    if (path)
+    {
+        char pathname[MAXPDSTRING-1];
+        do_expandunbash(path, pathname, MAXPDSTRING-1);
+        if(sys_isabsolutepath(pathname))
+        {
+            s = gensym(pathname);
+        } else {
+            char buf[MAXPDSTRING];
+            pd_snprintf(buf, MAXPDSTRING, "%s/%s",
+                     canvas_getdir(c)->s_name, pathname);
+            buf[MAXPDSTRING-1] = 0;
+            s = gensym(buf);
+        }
+    }
+    else
+        s = canvas_getdir(c);
+
+    outlet_float(x->x_infoout, effectivelevel);
+    outlet_symbol(x->x_dataout, s);
+    return;
+
+ fail:
+    pd_error(x, "bad arguments for %s%smessage to object 'file patchpath'", msg, *msg?" ":"");
+}
+
+    /* ================ [file mkdir] ====================== */
+static void file_mkdir_symbol(t_file_handle*x, t_symbol*dir) {
+    char pathname[MAXPDSTRING], *path=pathname, *str;
+    struct stat sb;
+
+    do_expandpath(dir->s_name, pathname, MAXPDSTRING);
+    pathname[MAXPDSTRING-1]=0;
+    do_pathnormalize(pathname, pathname);
+
+    if(sys_isabsolutepath(pathname)) {
+        str=strchr(path, '/');
+        if(str)
+            path=str;
+    }
+    path++;
+    while(*path) {
+            /* get to the next path separator */
+        str=strchr(path, '/');
+        if(str) {
+            *str=0;
+        }
+        if(!sys_stat(pathname, &sb) && (S_ISDIR(sb.st_mode))) {
+                // directory exists, skip...
+        } else {
+            if(sys_mkdir(pathname, x?x->x_creationmode:0777)) {
+                char buf[MAXPDSTRING];
+                pd_error(x, "failed to create '%s': %s", pathname, do_errmsg(buf, MAXPDSTRING));
+                outlet_bang(x->x_infoout);
+                return;
+            }
+        }
+
+        if(str) {
+            *str='/';
+            path=str+1;
+        }
+        else
+            break;
+    }
+    outlet_symbol(x->x_dataout, gensym(pathname));
+}
+
+    /* ================ [file delete] ====================== */
+#ifdef _WIN32
+static int file_do_delete_recursive_ucs2(uint16_t*path) {
+    WIN32_FIND_DATAW FindFileData;
+    HANDLE hFind;
+    uint16_t pattern[MAX_PATH];
+    swprintf(pattern, MAX_PATH, L"%ls/*", path);
+    hFind = FindFirstFileW(pattern, &FindFileData);
+    if (hFind == INVALID_HANDLE_VALUE) {
+        return 1;
+    }
+    do {
+        int isdir = !!(FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes);
+        swprintf(pattern, MAX_PATH, L"%ls/%ls", path, FindFileData.cFileName);
+        if(!isdir) {
+            DeleteFileW(pattern);
+        } else {
+                /* skip self and parent */
+            if(!wcscmp(L".", FindFileData.cFileName))continue;
+            if(!wcscmp(L"..", FindFileData.cFileName))continue;
+            file_do_delete_recursive_ucs2(pattern);
+        }
+    } while (FindNextFileW(hFind, &FindFileData) != 0);
+    FindClose(hFind);
+    return do_delete_ucs2(path);
+}
+
+static int file_do_delete_recursive(const char*path) {
+    uint16_t ucs2path[MAXPDSTRING];
+    u8_utf8toucs2(ucs2path, MAXPDSTRING, path, MAXPDSTRING);
+    return file_do_delete_recursive_ucs2(ucs2path);
+}
+#else /* !_WIN32 */
+static int nftw_cb(const char *path, const struct stat *s, int flag, struct FTW *f) {
+    (void)s;
+    (void)f;
+    (void)flag;
+    return remove(path);
+}
+
+static int file_do_delete_recursive(const char*pathname) {
+    return nftw(pathname, nftw_cb, 128, FTW_MOUNT|FTW_PHYS|FTW_DEPTH);
+}
+#endif /* !_WIN32 */
+
+
+static void file_delete_symbol(t_file_handle*x, t_symbol*path) {
+    char pathname[MAXPDSTRING];
+    do_expandunbash(path->s_name, pathname, MAXPDSTRING);
+
+    if(sys_remove(pathname)) {
+        char buf[MAXPDSTRING];
+        if(x && x->x_verbose)
+            pd_error(x, "unable to delete '%s': %s", pathname, do_errmsg(buf, MAXPDSTRING));
+        outlet_bang(x->x_infoout);
+    } else {
+        outlet_symbol(x->x_dataout, gensym(pathname));
+    }
+}
+
+static void file_delete_recursive(t_file_handle*x, t_symbol*path) {
+    char pathname[MAXPDSTRING];
+    do_expandunbash(path->s_name, pathname, MAXPDSTRING);
+
+    if(file_do_delete_recursive(pathname)) {
+        if(x->x_verbose) {
+            char buf[MAXPDSTRING];
+            pd_error(x, "unable to recursively delete '%s': %s", pathname, do_errmsg(buf, MAXPDSTRING));
+        }
+        outlet_bang(x->x_infoout);
+    } else {
+        outlet_symbol(x->x_dataout, gensym(pathname));
+    }
+}
+
+
+    /* ================ [file copy]/[file move] ====================== */
+static int file_do_copy(const char*source, const char*destination, int mode) {
+    int result = 0;
+    ssize_t len;
+    char buf[1024];
+    int src, dst;
+    int wflags = O_WRONLY | O_CREAT | O_TRUNC;
+#ifdef _WIN32
+    wflags |= O_BINARY;
+#endif
+    src = sys_open(source, O_RDONLY);
+    if(src<0)
+        return 1;
+    dst = sys_open(destination, wflags, mode);
+    if(dst<0) {
+        struct stat sb;
+            /* check if destination is a directory: if so calculate the new filename */
+        if(!do_file_stat(0, destination, &sb, 0) && (S_ISDIR(sb.st_mode))) {
+            char destfile[MAXPDSTRING];
+            const char*filename=strrchr(source, '/');
+            if(!filename)
+                filename=source;
+            else
+                filename++;
+            pd_snprintf(destfile, MAXPDSTRING, "%s/%s", destination, filename);
+            dst = sys_open(destfile, O_WRONLY | O_CREAT | O_TRUNC, mode);
+        }
+    }
+    if(dst<0)
+        return 1;
+
+    while((len=read(src, buf, sizeof(buf)))>0) {
+        ssize_t wlen=write(dst, buf, len);
+            /* TODO: cater for partial writes */
+        if (wlen<1) {
+            result = 1 ;
+        }
+    }
+    sys_close(src);
+    sys_close(dst);
+
+    return (result);
+}
+static int file_do_move(const char*source, const char*destination, int mode) {
+    int olderrno=0;
+    int result = sys_rename(source, destination);
+    (void)mode;
+    if(result) {
+        struct stat sb;
+        int isfile=0;
+        int isdir=0;
+
+        olderrno=errno;
+            /* check whether we are trying to move a file to a directory */
+        if(do_file_stat(0, source, &sb, 0) < 0)
+            goto done; /* source is not statable, that's a serious error */
+        isfile = !(S_ISDIR(sb.st_mode));
+        if(do_file_stat(0, destination, &sb, 0) < 0)
+            goto done;  /* destination is not statable (so it doesn't exist and is not a directory either */
+        isdir = (S_ISDIR(sb.st_mode));
+        if(isfile && isdir) {
+            char destfile[MAXPDSTRING];
+            const char*filename=strrchr(source, '/');
+            if(!filename)
+                filename=source;
+            else
+                filename++;
+            pd_snprintf(destfile, MAXPDSTRING, "%s/%s", destination, filename);
+            result = sys_rename(source, destfile);
+            olderrno = errno;
+        }
+    }
+
+    if(result && EXDEV == errno) {
+            /* need to manually copy the file to the another filesystem */
+        result = file_do_copy(source, destination, mode);
+        if(!result) {
+            olderrno=0;
+                /* copy succeeded, now get rid of the source file */
+            if(sys_remove(source)) {
+                    /* oops, couldn't delete the source-file...
+                     * we still report this as SUCCESS, as the file has been duplicated.
+                     * LATER we might try to unlink() the file first.
+                     * set the olderrno to print some error in verbose-mode
+                     */
+                olderrno=errno;
+            }
+        }
+    }
+ done:
+    errno=olderrno;
+    return result;
+}
+static void file_do_copymove(t_file_handle*x,
+    const char*verb, int (*fun)(const char*,const char*,int),
+    t_symbol*s, int argc, t_atom*argv) {
+    struct stat sb;
+    char src[MAXPDSTRING], dst[MAXPDSTRING];
+    if(argc != 2 || A_SYMBOL != argv[0].a_type || A_SYMBOL != argv[1].a_type) {
+        pd_error(x, "bad arguments for [file %s] - should be 'source:symbol destination:symbol'", verb);
+        return;
+    }
+    do_expandunbash(atom_getsymbol(argv+0)->s_name, src, MAXPDSTRING);
+    do_expandunbash(atom_getsymbol(argv+1)->s_name, dst, MAXPDSTRING);
+
+    if(!sys_stat(src, &sb)) {
+        if(S_ISDIR(sb.st_mode)) {
+            if(x->x_verbose) {
+                pd_error(x, "failed to %s '%s': %s", verb, src, strerror(EISDIR));
+            }
+            outlet_bang(x->x_infoout);
+            return;
+        }
+    }
+
+    errno = 0;
+    if(fun(src, dst, x->x_creationmode?x->x_creationmode:sb.st_mode)) {
+        if(x->x_verbose) {
+            char buf[MAXPDSTRING];
+            pd_error(x, "failed to %s '%s' to '%s': %s", verb, src, dst, do_errmsg(buf, MAXPDSTRING));
+        }
+        outlet_bang(x->x_infoout);
+    } else {
+        if(errno && x->x_verbose) {
+            char buf[MAXPDSTRING];
+            pd_error(x, "troubles (but overall success) to %s '%s' to '%s': %s", verb, src, dst, do_errmsg(buf, MAXPDSTRING));
+        }
+        outlet_list(x->x_dataout, s, argc, argv);
+    }
+}
+
+static void file_copy_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    file_do_copymove(x, "copy", file_do_copy, s, argc, argv);
+}
+static void file_move_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    file_do_copymove(x, "move", file_do_move, s, argc, argv);
+}
+
+/* ================ file cwd  ====================== */
+static void file_cwd_bang(t_file_handle*x) {
+    char buf[MAXPDSTRING];
+    if(sys_getcwd(buf)) {
+        outlet_symbol(x->x_dataout, gensym(buf));
+    } else {
+        if(x->x_verbose)
+            pd_error(x, "could not query current working directory: %s", do_errmsg(buf, MAXPDSTRING));
+        outlet_bang(x->x_infoout);
+    }
+}
+static void file_cwd_symbol(t_file_handle*x, t_symbol*path) {
+    char pathname[MAXPDSTRING];
+    do_expandunbash(path->s_name, pathname, MAXPDSTRING);
+    if(!sys_chdir(pathname)) {
+        file_cwd_bang(x);
+    } else {
+        if(x->x_verbose) {
+            char buf[MAXPDSTRING];
+            pd_error(x, "could not change the working directory to '%s': %s",
+                     pathname, do_errmsg(buf, MAXPDSTRING));
+        }
+        outlet_bang(x->x_infoout);
+    }
+}
+
+
+/* ================ file path operations ====================== */
+static void file_split_symbol(t_file_handle*x, t_symbol*path) {
+    int outc = 0;
+    t_atom*outv = 0;
+    t_symbol*slashsym = do_splitpath(path->s_name, &outc, &outv);
+
+    if (slashsym)
+        outlet_symbol(x->x_infoout, slashsym);
+    else
+        outlet_bang(x->x_infoout);
+
+    outlet_list(x->x_dataout, gensym("list"), outc, outv);
+    freebytes(outv, outc * sizeof(*outv));
+}
+
+static void file_join_list(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+    s = do_joinpath(0, argc, argv, 0);
+    outlet_symbol(x->x_dataout, s);
+}
+
+static void file_splitext_symbol(t_file_handle*x, t_symbol*path) {
+    char pathname[MAXPDSTRING];
+    t_atom outv[2];
+    char*str;
+    sys_unbashfilename(path->s_name, pathname);
+    pathname[MAXPDSTRING-1]=0;
+    str=pathname + strlen(pathname)-1;
+    if(str < pathname || '.' != *str)
+    {
+        while(str>=pathname) {
+            char c = *str;
+            switch(c) {
+            case '.':
+                str[0]=0;
+                SETSYMBOL(outv+0, gensym(pathname));
+                SETSYMBOL(outv+1, gensym(str+1));
+                outlet_list(x->x_dataout, gensym("list"), 2, outv);
+                return;
+            case '/':
+                str = pathname;
+                break;
+            default:
+                break;
+            }
+            str--;
+        }
+    }
+    outlet_symbol(x->x_infoout, gensym(pathname));
+}
+static void file_splitname_symbol(t_file_handle*x, t_symbol*path) {
+    char pathname[MAXPDSTRING];
+    char*str;
+    sys_unbashfilename(path->s_name, pathname);
+    pathname[MAXPDSTRING-1]=0;
+    str=strrchr(pathname, '/');
+    if(str>pathname) {
+        t_symbol*s;
+        *str++=0;
+        s = gensym(pathname);
+        if(*str) {
+            t_atom outv[2];
+            SETSYMBOL(outv+0, s);
+            SETSYMBOL(outv+1, gensym(str));
+            outlet_list(x->x_dataout, gensym("list"), 2, outv);
+        } else {
+            outlet_symbol(x->x_dataout, s);
+        }
+    } else {
+        outlet_symbol(x->x_infoout, gensym(pathname));
+    }
+}
+
+static void file_normalize_symbol(t_file_handle*x, t_symbol*path) {
+    char _path[MAXPDSTRING];
+    char*xpath=do_expandunbash(path->s_name, _path, sizeof(_path));
+    int argc=0;
+    t_atom*argv=0;
+    t_symbol*dot=gensym("."), *dotdot=gensym("..");
+    t_symbol*slash=gensym("/"), *dotslash=gensym("./");
+    const int isabs=sys_isabsolutepath(xpath);
+    t_symbol*suffix=0,*volume=0, *result=0;
+    int check, changed, i;
+
+#ifdef _WIN32
+    if(xpath[0] && ':' == xpath[1]) {
+        char vol[3];
+        vol[0] = xpath[0];
+        vol[1] = xpath[1];
+        vol[2] = 0;
+        volume = gensym(vol);
+        xpath+=2;
+    }
+#endif
+    suffix=do_splitpath(xpath, &argc, &argv);
+
+        /* drop 'empty' elements ('.') */
+    for(i=isabs; i<argc; i++)
+        if(atom_getsymbol(argv+i) == dot) {
+            argv[i].a_type = A_NULL;
+            continue;
+        }
+
+        /* drop any non-'..' followed by a '..' (recursively) */
+    changed=1;
+    while(changed) {
+        t_atom*last=0;
+        t_symbol*s=0;
+        changed = 0;
+        for(i=isabs; i<argc; i++) {
+            if(A_NULL == argv[i].a_type) continue;
+            s = atom_getsymbol(argv+i);
+            if(s == dotdot) {
+                if (last) {
+                        /* we just encountered a 'xxx/..' portion, that cancels itself out */
+                    last->a_type = argv[i].a_type = A_NULL;
+                    changed = 1;
+                }
+                last = 0;
+            } else {
+                last = argv+i;
+            }
+        }
+    }
+
+        /* drop any leading '..' components in absolute mode... */
+    if(isabs) {
+        for(i=isabs; i<argc; i++) {
+            t_atom*a=argv+i;
+            if(A_NULL == a->a_type) continue;
+            if (dotdot == atom_getsymbol(a)) {
+                a->a_type = A_NULL;
+            } else break;
+        }
+    }
+
+        /* an join the leftovers */
+    result = do_joinpath(volume, argc, argv, suffix);
+
+
+        /* find problematic components */
+    check = 0;
+    for(i=0; i<argc; i++) {
+        t_atom*a=argv+i;
+        int chk;
+        if(A_NULL == a->a_type) continue;
+        chk = do_checkpathname(x, atom_getsymbol(a)->s_name);
+        if(chk>check)check=chk;
+    }
+
+        /* free dynamically allocated memory before outputting data */
+    freebytes(argv, argc * sizeof(*argv));
+
+        /* output the error/warning/ok */
+    outlet_float(x->x_infoout, check);
+
+        /* output the normalized path */
+    if(result && *result->s_name) {
+        if(!isabs && slash == result)
+            outlet_symbol(x->x_dataout, dotslash);
+        else
+            outlet_symbol(x->x_dataout, result);
+    } else {
+        if(suffix)
+            outlet_symbol(x->x_dataout, dotslash);
+        else
+            outlet_symbol(x->x_dataout, dot);
+    }
+}
+
+
+static void file_isabsolute_symbol(t_file_handle*x, t_symbol*path) {
+    char xpath[MAXPDSTRING];
+    outlet_float(x->x_dataout, sys_isabsolutepath(do_expandunbash(path->s_name, xpath, MAXPDSTRING)));
+}
+
+
+    /* overall creator for "file" objects - dispatch to "file handle" etc */
+t_class *file_handle_class, *file_which_class, *file_glob_class, *file_patchpath_class;
+t_class *file_stat_class, *file_size_class, *file_isfile_class, *file_isdirectory_class;
+t_class *file_mkdir_class, *file_delete_class, *file_copy_class, *file_move_class;
+t_class *file_split_class,*file_join_class,*file_splitext_class, *file_splitname_class;
+t_class *file_normalize_class, *file_isabsolute_class, *file_cwd_class;
+
+#define FILE_PD_NEW(verb, verbose, creationmode) static t_file_handle* file_##verb##_new(t_symbol*s, int argc, t_atom*argv) \
+    {                                                                   \
+        return do_file_handle_new(file_##verb##_class, s, argc, argv, verbose, creationmode); \
+    }
+
+static void file_define_ignore(t_file_handle*x, t_symbol*s, int argc, t_atom*argv) {
+        /* this is a noop (so the object does not bail out if somebody 'send's something to its label) */
+    (void)x;
+    (void)s;
+    (void)argc;
+    (void)argv;
+}
+static t_file_handle*file_define_new(t_symbol*s, int argc, t_atom*argv) {
+   t_file_handle*x = (t_file_handle*)pd_new(file_define_class);
+   x->x_fhptr = &x->x_fh;
+   x->x_fh.fh_fd = -1;
+   x->x_canvas = canvas_getcurrent();
+   x->x_creationmode = 0666;
+   x->x_verbose = 0;
+   if(1 == argc && A_SYMBOL == argv->a_type) {
+       x->x_fcname = atom_getsymbol(argv);
+       pd_bind(&x->x_obj.ob_pd, x->x_fcname);
+   } else {
+       pd_error(x, "%s requires an argument: handle name", s->s_name);
+   }
+   return x;
+}
+static void file_define_free(t_file_handle*x) {
+    file_handle_close(x);
+    if(x->x_fcname)
+        pd_unbind(&x->x_obj.ob_pd, x->x_fcname);
+}
+
+static t_file_handle*file_handle_new(t_symbol*s, int argc, t_atom*argv) {
+    t_file_handle*x=do_file_handle_new(file_handle_class, s, argc, argv, 1, 0666);
+    inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("symbol"), gensym("set"));
+    return x;
+}
+
+
+FILE_PD_NEW(which, 0, 0);
+FILE_PD_NEW(patchpath, 0, 0);
+FILE_PD_NEW(glob, 0, 0);
+
+FILE_PD_NEW(stat, 0, 0);
+FILE_PD_NEW(size, 0, 0);
+FILE_PD_NEW(isfile, 0, 0);
+FILE_PD_NEW(isdirectory, 0, 0);
+
+FILE_PD_NEW(mkdir, 0, 0777);
+FILE_PD_NEW(delete, 0, 0);
+FILE_PD_NEW(copy, 0, 0);
+FILE_PD_NEW(move, 0, 0);
+
+FILE_PD_NEW(cwd, 1, 0);
+
+FILE_PD_NEW(split, 0, 0);
+FILE_PD_NEW(join, 0, 0);
+FILE_PD_NEW(splitext, 0, 0);
+FILE_PD_NEW(splitname, 0, 0);
+FILE_PD_NEW(isabsolute, 0, 0);
+FILE_PD_NEW(normalize, 1, 0);
+
+static t_pd *fileobj_new(t_symbol *s, int argc, t_atom*argv)
+{
+    t_file_handle*x = 0;
+    const char*verb=0;
+    if(gensym("file") == s) {
+        if (argc && A_SYMBOL == argv->a_type) {
+            verb = atom_getsymbol(argv)->s_name;
+            argc--;
+            argv++;
+        } else {
+            verb = "handle";
+        }
+    } else if (strlen(s->s_name)>5) {
+        verb = s->s_name + 5;
+    }
+    if (!verb || !*verb)
+        x = do_file_handle_new(file_handle_class, gensym("file handle"), argc, argv, 1, 0666);
+    else {
+#define ELIF_FILE_PD_NEW(name, verbose, creationmode) else              \
+            if (!strcmp(verb, #name))                                   \
+                x = do_file_handle_new(file_##name##_class, gensym("file "#name), argc, argv, verbose, creationmode)
+
+        if (!strcmp(verb, "define"))
+            x = file_define_new(gensym("file define"), argc, argv);
+        else if (!strcmp(verb, "handle"))
+            x = file_handle_new(gensym("file handle"), argc, argv);
+        ELIF_FILE_PD_NEW(handle, 1, 0666);
+        ELIF_FILE_PD_NEW(which, 0, 0);
+        ELIF_FILE_PD_NEW(patchpath, 0, 0);
+        ELIF_FILE_PD_NEW(glob, 0, 0);
+        ELIF_FILE_PD_NEW(stat, 0, 0);
+        ELIF_FILE_PD_NEW(size, 0, 0);
+        ELIF_FILE_PD_NEW(isfile, 0, 0);
+        ELIF_FILE_PD_NEW(isdirectory, 0, 0);
+        ELIF_FILE_PD_NEW(mkdir, 0, 0777);
+        ELIF_FILE_PD_NEW(delete, 0, 0);
+        ELIF_FILE_PD_NEW(copy, 0, 0);
+        ELIF_FILE_PD_NEW(move, 0, 0);
+        ELIF_FILE_PD_NEW(cwd, 1, 0);
+        ELIF_FILE_PD_NEW(split, 0, 0);
+        ELIF_FILE_PD_NEW(join, 0, 0);
+        ELIF_FILE_PD_NEW(splitext, 0, 0);
+        ELIF_FILE_PD_NEW(splitname, 0, 0);
+        ELIF_FILE_PD_NEW(isabsolute, 0, 0);
+        ELIF_FILE_PD_NEW(normalize, 1, 0);
+        else {
+            pd_error(0, "file %s: unknown function", verb);
+        }
+    }
+    return (t_pd*)x;
+}
+
+typedef enum _filenew_flag {
+    DEFAULT = 0,
+    VERBOSE = 1<<0,
+    MODE = 1<<1
+} t_filenew_flag;
+
+static t_class*file_class_new(const char*name
+    , t_file_handle* (*ctor)(t_symbol*,int,t_atom*), void (*dtor)(t_file_handle*)
+    , void (*symfun)(t_file_handle*, t_symbol*)
+    , t_filenew_flag flag
+    ) {
+    t_class*cls = class_new(gensym(name),
+        (t_newmethod)ctor, (t_method)dtor,
+        sizeof(t_file_handle), 0, A_GIMME, 0);
+    if (flag & VERBOSE)
+        class_addmethod(cls, (t_method)file_set_verbosity, gensym("verbose"), A_FLOAT, 0);
+    if (flag & MODE)
+        class_addmethod(cls, (t_method)file_set_creationmode, gensym("creationmode"), A_GIMME, 0);
+    if(symfun)
+        class_addsymbol(cls, (t_method)symfun);
+
+    class_sethelpsymbol(cls, gensym("file"));
+    return cls;
+}
+
+    /* ---------------- global setup function -------------------- */
+void x_file_setup(void)
+{
+    class_addcreator((t_newmethod)fileobj_new, gensym("file"), A_GIMME, 0);
+
+        /* [file define] */
+    file_define_class = class_new(gensym("file define"),
+                                  (t_newmethod)file_define_new, (t_method)file_define_free,
+                                  sizeof(t_file_handle), CLASS_NOINLET, A_GIMME, 0);
+    class_addanything(file_define_class, file_define_ignore);
+    class_sethelpsymbol(file_define_class, gensym("file"));
+
+        /* [file handle] */
+    file_handle_class = file_class_new("file handle", file_handle_new, file_handle_free, 0, MODE|VERBOSE);
+    class_addmethod(file_handle_class, (t_method)file_handle_open,
+        gensym("open"), A_SYMBOL, A_DEFSYM, 0);
+    class_addmethod(file_handle_class, (t_method)file_handle_close,
+        gensym("close"), 0);
+    class_addmethod(file_handle_class, (t_method)file_handle_seek,
+        gensym("seek"), A_GIMME, 0);
+    class_addmethod(file_handle_class, (t_method)file_handle_set,
+        gensym("set"), A_DEFSYMBOL, 0);
+    class_addlist(file_handle_class, file_handle_list);
+
+        /* [file which] */
+    file_which_class = file_class_new("file which", file_which_new, 0, 0, VERBOSE);
+    class_addlist(file_which_class, file_which_list);
+
+        /* [file patchpath] */
+    file_patchpath_class = file_class_new("file patchpath", file_patchpath_new, 0, 0, VERBOSE);
+    class_addlist(file_patchpath_class, file_patchpath_list);
+
+        /* [file glob] */
+    file_glob_class = file_class_new("file glob", file_glob_new, 0, file_glob_symbol, VERBOSE);
+
+        /* [file stat] */
+    file_stat_class = file_class_new("file stat", file_stat_new, 0, file_stat_symbol, VERBOSE);
+    file_size_class = file_class_new("file size", file_size_new, 0, file_size_symbol, VERBOSE);
+    file_isfile_class = file_class_new("file isfile", file_isfile_new, 0, file_isfile_symbol, VERBOSE);
+    file_isdirectory_class = file_class_new("file isdirectory", file_isdirectory_new, 0, file_isdirectory_symbol, VERBOSE);
+
+        /* [file mkdir] */
+    file_mkdir_class = file_class_new("file mkdir", file_mkdir_new, 0, file_mkdir_symbol, MODE|VERBOSE);
+
+        /* [file delete] */
+    file_delete_class = file_class_new("file delete", file_delete_new, 0, file_delete_symbol, VERBOSE);
+    class_addmethod(file_delete_class, (t_method)file_delete_recursive,
+        gensym("recursive"), A_SYMBOL, 0);
+
+        /* [file copy] */
+    file_copy_class = file_class_new("file copy", file_copy_new, 0, 0, MODE|VERBOSE);
+    class_addlist(file_copy_class, (t_method)file_copy_list);
+
+        /* [file move] */
+    file_move_class = file_class_new("file move", file_move_new, 0, 0, MODE|VERBOSE);
+    class_addlist(file_move_class, (t_method)file_move_list);
+
+        /* [file cwd] */
+    file_cwd_class = file_class_new("file cwd", file_cwd_new, 0, file_cwd_symbol, VERBOSE);
+    class_addbang(file_cwd_class, (t_method)file_cwd_bang);
+
+        /* file path objects */
+    file_split_class = file_class_new("file split", file_split_new, 0, file_split_symbol, DEFAULT);
+    file_join_class = file_class_new("file join", file_join_new, 0, 0, DEFAULT);
+    class_addlist(file_join_class, (t_method)file_join_list);
+    file_splitext_class = file_class_new("file splitext", file_splitext_new, 0, file_splitext_symbol, DEFAULT);
+    file_splitname_class = file_class_new("file splitname", file_splitname_new, 0, file_splitname_symbol, DEFAULT);
+    file_isabsolute_class = file_class_new("file isabsolute", file_isabsolute_new, 0, file_isabsolute_symbol, DEFAULT);
+    file_normalize_class = file_class_new("file normalize", file_normalize_new, 0, file_normalize_symbol, VERBOSE);
+}
diff --git a/pd/src/x_interface.c b/pd/src/x_interface.c
index 5cc4ab26ae71613f3401ae87057385b7844a1271..7a58cd6a2829d718e25f63fd7983b1bbb9239c2c 100644
--- a/pd/src/x_interface.c
+++ b/pd/src/x_interface.c
@@ -1738,6 +1738,56 @@ void debuginfo_setup(void)
         gensym("print"), A_GIMME, 0);
 }
 
+/* ------------------- trace - see message passing traces ------------- */
+int backtracer_settracing(void *x, int tracing);
+extern int backtracer_cantrace;
+
+static t_class *trace_class;
+
+typedef struct _trace
+{
+    t_object x_obj;
+    t_symbol *x_sym;
+    t_float x_f;
+} t_trace;
+
+static void *trace_new(t_symbol *s)
+{
+    t_trace *x = (t_trace *)pd_new(trace_class);
+    x->x_sym = s;
+    x->x_f = 0;
+    floatinlet_new(&x->x_obj, &x->x_f);
+    outlet_new(&x->x_obj, &s_anything);
+    return (x);
+}
+
+static void trace_anything(t_trace *x, t_symbol *s, int argc, t_atom *argv)
+{
+    int nturns = x->x_f;
+    if (nturns > 0)
+    {
+        if (!backtracer_cantrace)
+        {
+            pd_error(x, "trace requested but tracing is not enabled");
+            x->x_f = 0;
+        }
+        else if (backtracer_settracing(x, 1))
+        {
+            outlet_anything(x->x_obj.ob_outlet, s, argc, argv);
+            x->x_f = nturns-1;
+            (void)backtracer_settracing(x, 0);
+        }
+    }
+    else outlet_anything(x->x_obj.ob_outlet, s, argc, argv);
+}
+
+static void trace_setup(void)
+{
+    trace_class = class_new(gensym("trace"), (t_newmethod)trace_new, 0,
+        sizeof(t_trace), 0, A_DEFSYM, 0);
+    class_addanything(trace_class, trace_anything);
+}
+
 void x_interface_setup(void)
 {
     canvasinfo_setup();
@@ -1747,5 +1797,6 @@ void x_interface_setup(void)
     abinfo_setup();
     pdinfo_setup();
     print_setup();
+    trace_setup();
     unpost_setup();
 }