Primitive loading and saving.
authorSoeren Sandmann <sandmann@redhat.com>
Wed, 23 Mar 2005 05:05:57 +0000 (05:05 +0000)
committerSøren Sandmann Pedersen <ssp@src.gnome.org>
Wed, 23 Mar 2005 05:05:57 +0000 (05:05 +0000)
Wed Mar 23 00:04:07 2005  Soeren Sandmann  <sandmann@redhat.com>

Primitive loading and saving.

* sysprof.c (on_open_clicked): Hook up loading.

* sfile.c: Add a copy of g_file_replace() from glib CVS HEAD.

* sfile.c (add_string): Escape and quote the string

* sfile.c (sfile_load): Initialize current_instruction and
instructions_by_location

* sfile.c (post_process_instructions_recurse): Handle NULL
pointers properly.

* sfile.c (handle_begin_element, handle_end_element, handle_text):
Move error handling here from state_transition_begin/text/end.

* sfile.c (handle_text): Discard whitespace-only strings

* sfile.c (sfile_get_pointer, sfile_get_integer,
sfile_get_string): expect both begin, value, and end transitions.

* sfile.c (hook_up_pointers): Only treat instructions as pointer
values when they are. Handle NULL targets properly.

* sfile.c (get_number): Fix a few read-freed-data bugs

* profile.c (profile_load): Call sfile_end_get() for the profile;
build the nodes_by_objects hash table. Build the call tree.

* profile.c (create_format): Don't store next pointer, but do
store total, self and toplevel.

* profile.c (make_hash_table): New function to build
nodes_by_object hashtable from loaded data

ChangeLog
profile.c
sfile.c
sysprof.c

index f1be485..60b857a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,41 @@
+Wed Mar 23 00:04:07 2005  Soeren Sandmann  <sandmann@redhat.com>
+
+       Primitive loading and saving.
+       
+       * sysprof.c (on_open_clicked): Hook up loading.
+
+       * sfile.c: Add a copy of g_file_replace() from glib CVS HEAD.
+
+       * sfile.c (add_string): Escape and quote the string
+
+       * sfile.c (sfile_load): Initialize current_instruction and
+       instructions_by_location
+
+       * sfile.c (post_process_instructions_recurse): Handle NULL
+       pointers properly.
+
+       * sfile.c (handle_begin_element, handle_end_element, handle_text):
+       Move error handling here from state_transition_begin/text/end.
+
+       * sfile.c (handle_text): Discard whitespace-only strings
+
+       * sfile.c (sfile_get_pointer, sfile_get_integer,
+       sfile_get_string): expect both begin, value, and end transitions.
+
+       * sfile.c (hook_up_pointers): Only treat instructions as pointer
+       values when they are. Handle NULL targets properly. 
+
+       * sfile.c (get_number): Fix a few read-freed-data bugs
+
+       * profile.c (profile_load): Call sfile_end_get() for the profile;
+       build the nodes_by_objects hash table. Build the call tree.
+
+       * profile.c (create_format): Don't store next pointer, but do
+       store total, self and toplevel.
+
+       * profile.c (make_hash_table): New function to build
+       nodes_by_object hashtable from loaded data
+
 Sat Mar 12 11:05:19 2005  Soeren Sandmann  <sandmann@redhat.com>
 
        * sysprof-module.c: Fix small bug in add_timeout()
index 315319d..6d5584b 100644 (file)
--- a/profile.c
+++ b/profile.c
@@ -22,7 +22,7 @@ update()
 static guint
 direct_hash_no_null (gconstpointer v)
 {
-    g_assert (v);
+    g_assert (v != NULL);
     return GPOINTER_TO_UINT (v);
 }
 
@@ -78,7 +78,9 @@ create_format (void)
                    sformat_new_pointer ("siblings", &node_type),
                    sformat_new_pointer ("children", &node_type),
                    sformat_new_pointer ("parent", &node_type),
-                   sformat_new_pointer ("next", &node_type),
+                   sformat_new_integer ("total"),
+                   sformat_new_integer ("self"),
+                   sformat_new_integer ("toplevel"),
                    NULL)),
            NULL));
 }
@@ -109,7 +111,9 @@ serialize_call_tree (Node *node, SFileOutput *output)
     sfile_add_pointer (output, "siblings", node->siblings);
     sfile_add_pointer (output, "children", node->children);
     sfile_add_pointer (output, "parent", node->parent);
-    sfile_add_pointer (output, "next", node->next);
+    sfile_add_integer (output, "total", node->total);
+    sfile_add_integer (output, "self", node->self);
+    sfile_add_integer (output, "toplevel", node->toplevel);
     sfile_end_add (output, "node", node);
 
     serialize_call_tree (node->siblings, output);
@@ -148,6 +152,25 @@ profile_save (Profile               *profile,
     return result;
 }
 
+static void
+make_hash_table (Node *node, GHashTable *table)
+{
+    if (!node)
+       return;
+
+    g_assert (node->object);
+    
+    node->next = g_hash_table_lookup (table, node->object);
+    g_hash_table_insert (table, node->object, node);
+
+    g_print ("added %s\n", node->object->name);
+    
+    g_assert (node->siblings != 0x11);
+    
+    make_hash_table (node->siblings, table);
+    make_hash_table (node->children, table);
+}
+
 Profile *
 profile_load (const char *filename, GError **err)
 {
@@ -164,6 +187,9 @@ profile_load (const char *filename, GError **err)
     
     profile = g_new (Profile, 1);
 
+    profile->nodes_by_object =
+       g_hash_table_new (direct_hash_no_null, g_direct_equal);
+    
     sfile_begin_get_record (input, "profile");
 
     sfile_get_integer (input, "size", &profile->size);
@@ -178,11 +204,12 @@ profile_load (const char *filename, GError **err)
        sfile_get_string (input, "name", &obj->name);
        sfile_get_integer (input, "total", &obj->total);
        sfile_get_integer (input, "self", &obj->self);
-
+       
        sfile_end_get (input, "object", obj);
     }
     sfile_end_get (input, "objects", NULL);
 
+    profile->call_tree = NULL;
     n = sfile_begin_get_list (input, "nodes");
     for (i = 0; i < n; ++i)
     {
@@ -194,17 +221,28 @@ profile_load (const char *filename, GError **err)
        sfile_get_pointer (input, "siblings", (gpointer *)&node->siblings);
        sfile_get_pointer (input, "children", (gpointer *)&node->children);
        sfile_get_pointer (input, "parent", (gpointer *)&node->parent);
-       sfile_get_pointer (input, "next", (gpointer *)&node->next);
+       sfile_get_integer (input, "total", &node->total);
+       sfile_get_integer (input, "self", &node->self);
+       sfile_get_integer (input, "toplevel", &node->toplevel);
        
        sfile_end_get (input, "node", node);
 
-       if (!node->parent)
+       if (!profile->call_tree)
            profile->call_tree = node;
+       
+       g_assert (node->siblings != 0x11);
     }
     sfile_end_get (input, "nodes", NULL);
+    sfile_end_get (input, "profile", NULL);
     
     sformat_free (format);
 
+    /* FIXME: why don't we just store the root node? */
+    while (profile->call_tree && profile->call_tree->parent)
+       profile->call_tree = profile->call_tree->parent;
+    
+    make_hash_table (profile->call_tree, profile->nodes_by_object);
+
     return profile;
 }
 
diff --git a/sfile.c b/sfile.c
index f75b400..39e6090 100644 (file)
--- a/sfile.c
+++ b/sfile.c
@@ -210,6 +210,7 @@ fragment_queue (Fragment *fragment1, va_list args)
  * floating point values. How do we store those portably
  *     without losing precision? Gnumeric may know.
  * enums, stored as strings
+ * booleans
  */
 gpointer
 sformat_new_union (const char *name,
@@ -400,11 +401,10 @@ static const State *
 state_transition_check (const State *state,
                         const char *element,
                         TransitionKind kind,
-                        SType *type,
-                        GError **err)
+                        SType *type)
 {
     GList *list;
-    
+
     for (list = state->transitions->head; list; list = list->next)
     {
         Transition *transition = list->data;
@@ -417,27 +417,23 @@ state_transition_check (const State *state,
         }
     }
     
-    set_unknown_element_error (err, "<%s> or </%s> unexpected here", element, element);
-    
     return NULL;
 }
 
 static const State *
-state_transition_begin (const State *state, const char *element,
-                        SType *type, GError **err)
+state_transition_begin (const State *state, const char *element, SType *type)
 {
-    return state_transition_check (state, element, BEGIN, type, err);
+    return state_transition_check (state, element, BEGIN, type);
 }
 
 static const State *
-state_transition_end (const State *state, const char *element,
-                      SType *type, GError **err)
+state_transition_end (const State *state, const char *element, SType *type)
 {
-    return state_transition_check (state, element, END, type, err);
+    return state_transition_check (state, element, END, type);
 }
 
 static const State *
-state_transition_text (const State *state, SType *type, GError **err)
+state_transition_text (const State *state, SType *type)
 {
     GList *list;
     
@@ -454,9 +450,12 @@ state_transition_text (const State *state, SType *type, GError **err)
              */
             return transition->to;
         }
+#if 0
+        else
+            g_print ("transition: %d (%s)\n", transition->kind, transition->element);
+#endif
     }
     
-    set_invalid_content_error (err, "Unexpected text data");
     return NULL;
 }
 
@@ -518,18 +517,19 @@ get_number (const char *text, int *number)
     char *end;
     int result;
     char *stripped;
-    
+    gboolean retval;
+
     stripped = g_strstrip (g_strdup (text));
     result = strtol (stripped, &end, 10);
-    g_free (stripped);
-    
-    if (*end != '\0')
-       return FALSE;
+
+    retval = (*end == '\0');
+
+    if (retval && number)
+        *number = result;
     
-    if (number)
-       *number = result;
+    g_free (stripped);
     
-    return TRUE;
+    return retval;
 }
 
 struct SFileInput
@@ -582,10 +582,15 @@ sfile_get_pointer (SFileInput  *file,
                    const char *name,
                    gpointer    *location)
 {
-    Instruction *instruction = file->current_instruction++;
+    Instruction *instruction;
+
+    instruction = file->current_instruction++;
     g_return_if_fail (instruction->type == TYPE_POINTER &&
                       strcmp (instruction->name, name) == 0);
     
+    instruction = file->current_instruction++;
+    g_return_if_fail (instruction->type == TYPE_POINTER);
+    
     instruction->u.pointer.location = location;
 
     *location = (gpointer) 0xFedeAbe;
@@ -597,6 +602,11 @@ sfile_get_pointer (SFileInput  *file,
         
         g_hash_table_insert (file->instructions_by_location, location, instruction);
     }
+
+    instruction = file->current_instruction++;
+    g_return_if_fail (instruction->type == TYPE_POINTER &&
+                      strcmp (instruction->name, name) == 0);
+    
 }
 
 void
@@ -604,12 +614,21 @@ sfile_get_integer      (SFileInput  *file,
                         const char  *name,
                         int         *integer)
 {
-    Instruction *instruction = file->current_instruction++;
+    Instruction *instruction;
+
+    instruction = file->current_instruction++;
     g_return_if_fail (instruction->type == TYPE_INTEGER &&
                       strcmp (instruction->name, name) == 0);
     
+    instruction = file->current_instruction++;
+    g_return_if_fail (instruction->type == TYPE_INTEGER);
+    
     if (integer)
         *integer = instruction->u.integer.value;
+
+    instruction = file->current_instruction++;
+    g_return_if_fail (instruction->type == TYPE_INTEGER &&
+                      strcmp (instruction->name, name) == 0);
 }
 
 void
@@ -617,28 +636,52 @@ sfile_get_string       (SFileInput  *file,
                         const char  *name,
                         char       **string)
 {
-    Instruction *instruction = file->current_instruction++;
+    Instruction *instruction;
+
+    instruction = file->current_instruction++;
     g_return_if_fail (instruction->type == TYPE_STRING &&
                       strcmp (instruction->name, name) == 0);
     
+    instruction = file->current_instruction++;
+    g_return_if_fail (instruction->type == TYPE_STRING);
+    
     if (string)
         *string = g_strdup (instruction->u.string.value);
+
+    instruction = file->current_instruction++;
+    g_return_if_fail (instruction->type == TYPE_STRING &&
+                      strcmp (instruction->name, name) == 0);
 }
 
 static void
 hook_up_pointers (SFileInput *file)
 {
     int i;
-
+#if 0
+    g_print ("emfle\n");
+#endif
     for (i = 0; i < file->n_instructions; ++i)
     {
         Instruction *instruction = &(file->instructions[i]);
 
-        if (instruction->type == TYPE_POINTER)
+        if (instruction->kind == VALUE &&
+            instruction->type == TYPE_POINTER)
         {
-            gpointer target_object =
-                instruction->u.pointer.target_instruction->u.begin.end_instruction->u.end.object;
+            gpointer target_object;
+            Instruction *target_instruction;
+
+            target_instruction = instruction->u.pointer.target_instruction;
 
+            if (target_instruction)
+                target_object = target_instruction->u.begin.end_instruction->u.end.object;
+            else
+                target_object = NULL;
+
+#if 0
+            g_print ("target object: %p\n", target_object);
+#endif
+            
             *(instruction->u.pointer.location) = target_object;
         }
     }
@@ -705,16 +748,22 @@ handle_begin_element (GMarkupParseContext *parse_context,
 {
     BuildContext *build = user_data;
     Instruction instruction;
-    
+
     instruction.u.begin.id = get_id (attribute_names, attribute_values, err);
 
     if (instruction.u.begin.id == -1)
         return;
 
-    build->state = state_transition_begin (build->state, element_name, &instruction.type, err);
+    build->state = state_transition_begin (build->state, element_name, &instruction.type);
+    if (!build->state)
+    {
+        set_unknown_element_error (err, "<%s> unexpected here", element_name);
+        return;
+    }
+
+    /* FIXME: is there really a reason to add begin/end instructions for values? */
     instruction.name = g_strdup (element_name);
     instruction.kind = BEGIN;
-    
     g_array_append_val (build->instructions, instruction);
 }
 
@@ -727,7 +776,12 @@ handle_end_element (GMarkupParseContext *context,
     BuildContext *build = user_data;
     Instruction instruction;
     
-    build->state = state_transition_end (build->state, element_name, &instruction.type, err);
+    build->state = state_transition_end (build->state, element_name, &instruction.type);
+    if (!build->state)
+    {
+        set_unknown_element_error (err, "</%s> unexpected here", element_name);
+        return;
+    }
 
     instruction.name = g_strdup (element_name);
     instruction.kind = END;
@@ -739,7 +793,7 @@ static gboolean
 decode_text (const char *text, char **decoded)
 {
     int length = strlen (text);
-    
+
     if (length < 2)
         return FALSE;
 
@@ -761,11 +815,28 @@ handle_text (GMarkupParseContext *context,
 {
     BuildContext *build = user_data;
     Instruction instruction;
-    
-    build->state = state_transition_text (build->state, &instruction.type, err);
+    char *free_me;
 
+    text = free_me = g_strstrip (g_strdup (text));
+
+    if (strlen (text) == 0)
+        goto out;
+        
+    build->state = state_transition_text (build->state, &instruction.type);
+    if (!build->state)
+    {
+        int line, ch;
+        g_markup_parse_context_get_position (context, &line, &ch);
+#if 0
+        g_print ("line: %d char: %d\n", line, ch);
+#endif
+        set_invalid_content_error (err, "Unexpected text data");
+        goto out;
+    }
+        
     instruction.name = NULL;
     instruction.kind = VALUE;
+    instruction.u.string.value = 0x01;
     
     switch (instruction.type)
     {
@@ -773,47 +844,50 @@ handle_text (GMarkupParseContext *context,
         if (!get_number (text, &instruction.u.pointer.target_id))
         {
             set_invalid_content_error (err, "Contents '%s' of pointer element is not a number", text);
-            return;
+            goto out;
         }
         break;
-
+        
     case TYPE_INTEGER:
         if (!get_number (text, &instruction.u.integer.value))
         {
             set_invalid_content_error (err, "Contents '%s' of integer element not a number", text);
-            return;
+            goto out;
         }
         break;
-
+        
     case TYPE_STRING:
         if (!decode_text (text, &instruction.u.string.value))
         {
             set_invalid_content_error (err, "Contents '%s' of text element is illformed", text);
-            return;
+            goto out;
         }
         break;
-
+        
     default:
         g_assert_not_reached();
         break;
     }
-
+    
     g_array_append_val (build->instructions, instruction);
+
+ out:
+    g_free (free_me);
 }
 
 static void
 free_instructions (Instruction *instructions, int n_instructions)
 {
     int i;
-    
+
     for (i = 0; i < n_instructions; ++i)
     {
         Instruction *instruction = &(instructions[i]);
         
         if (instruction->name)
             g_free (instruction->name);
-        
-        if (instruction->type == TYPE_STRING)
+
+        if (instruction->kind == VALUE && instruction->type == TYPE_STRING)
             g_free (instruction->u.string.value);
     }
     
@@ -928,19 +1002,31 @@ post_process_read_instructions (Instruction *instructions, int n_instructions, G
     {
         Instruction *instruction = &(instructions[i]);
 
-        if (instruction->type == TYPE_POINTER)
+        if (instruction->kind == VALUE &&
+            instruction->type == TYPE_POINTER)
         {
             int target_id = instruction->u.pointer.target_id;
-            
-            Instruction *target = g_hash_table_lookup (instructions_by_id,
-                                                       GINT_TO_POINTER (target_id));
 
-            if (!target)
+            if (target_id)
             {
-                set_invalid_content_error (err, "Id %d doesn't reference any record or list\n",
-                                           instruction->u.pointer.target_id);
-                retval = FALSE;
-                break;
+                Instruction *target = g_hash_table_lookup (instructions_by_id,
+                                                           GINT_TO_POINTER (target_id));
+                
+                if (target)
+                {
+                    instruction->u.pointer.target_instruction = target;
+                }
+                else
+                {
+                    set_invalid_content_error (err, "Id %d doesn't reference any record or list\n",
+                                               instruction->u.pointer.target_id);
+                    retval = FALSE;
+                    break;
+                }
+            }
+            else
+            {
+                instruction->u.pointer.target_instruction = NULL;
             }
         }
     }
@@ -1018,6 +1104,9 @@ sfile_load (const char  *filename,
     }
     
     g_free (contents);
+
+    input->current_instruction = input->instructions;
+    input->instructions_by_location = g_hash_table_new (g_direct_hash, g_direct_equal);
     
     return input;
 }
@@ -1050,8 +1139,7 @@ sfile_begin_add_record (SFileOutput       *file,
 {
     Instruction instruction;
     
-    file->state = state_transition_begin (
-        file->state, name, &instruction.type, NULL);
+    file->state = state_transition_begin (file->state, name, &instruction.type);
 
     g_return_if_fail (file->state);
     g_return_if_fail (is_record_type (instruction.type));
@@ -1068,8 +1156,7 @@ sfile_begin_add_list   (SFileOutput *file,
 {
     Instruction instruction;
 
-    file->state = state_transition_begin (
-        file->state, name, &instruction.type, NULL);
+    file->state = state_transition_begin (file->state, name, &instruction.type);
 
     g_return_if_fail (file->state);
     g_return_if_fail (is_list_type (instruction.type));
@@ -1093,8 +1180,7 @@ sfile_end_add (SFileOutput       *file,
         return;
     }
     
-    file->state = state_transition_end (
-        file->state, name, &instruction.type, NULL);
+    file->state = state_transition_end (file->state, name, &instruction.type);
 
     if (!file->state)
     {
@@ -1119,13 +1205,13 @@ sfile_check_value (SFileOutput *file,
 {
     SType tmp_type;
     
-    file->state = state_transition_begin (file->state, name, &tmp_type, NULL);
+    file->state = state_transition_begin (file->state, name, &tmp_type);
     g_return_if_fail (file->state && tmp_type == type);
 
-    file->state = state_transition_text (file->state, &type, NULL);
+    file->state = state_transition_text (file->state, &type);
     g_return_if_fail (file->state && tmp_type == type);
     
-    file->state = state_transition_end (file->state, name, &type, NULL);
+    file->state = state_transition_end (file->state, name, &type);
     g_return_if_fail (file->state && tmp_type == type);
 }
 
@@ -1238,7 +1324,7 @@ post_process_write_instructions (SFileOutput *sfile)
                 
                 if (target->u.end.begin_instruction->u.begin.id == -1)
                     target->u.end.begin_instruction->u.begin.id = id++;
-                
+
                 instruction->u.pointer.target_id = 
                     target->u.end.begin_instruction->u.begin.id;
             }
@@ -1269,7 +1355,27 @@ add_integer (GString *output, int value)
 static void
 add_string (GString *output, const char *str)
 {
+#if 0
+    /* FIXME: strings need to be encoded so that special
+     * xml characters can be added, and so they don't get
+     * confused with the deletion of whitespace-only
+     * text entries
+     */
     g_string_append_printf (output, "%s", str);
+#endif
+
+    int i;
+    g_string_append_c (output, '\"');
+    for (i = 0; str[i] != '\0'; ++i)
+    {
+        if (str[i] == '\"')
+            g_string_append_printf (output, "%s", "\\\"");
+        else if (str[i] == '\\')
+            g_string_append_printf (output, "\\\\");
+        else
+            g_string_append_c (output, str[i]);
+    }
+    g_string_append_c (output, '\"');
 }
 
 static void
@@ -1296,6 +1402,12 @@ add_nl (GString *output)
     g_string_append_c (output, '\n');
 }
 
+static gboolean
+file_replace (const gchar *filename,
+              const gchar *contents,
+              gssize        length,
+              GError      **error);
+
 gboolean
 sfile_output_save (SFileOutput  *sfile,
                    const char   *filename,
@@ -1359,13 +1471,13 @@ sfile_output_save (SFileOutput  *sfile,
     /* FIXME: don't dump this to stdout */
     g_print (output->str);
     
-#if 0
     /* FIXME, cut-and-paste the g_file_write() implementation
      * as long as it isn't in glib
      */
-    retval = g_file_write (filename, output->str, - 1, err);
-#endif
+    retval = file_replace (filename, output->str, - 1, err);
+#if 0
     retval = TRUE;
+#endif
     
     g_string_free (output, TRUE);
 
@@ -1378,3 +1490,333 @@ sfile_output_free (SFileOutput *sfile)
 {
     /* FIXME */
 }
+
+
+
+
+
+
+
+
+
+
+/* A copy of g_file_replace() because I don't want to depend on
+ * GLib HEAD
+ */
+#include <errno.h>
+#include <sys/wait.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+
+static gboolean
+rename_file (const char *old_name,
+            const char *new_name,
+            GError **err)
+{
+  errno = 0;
+  if (g_rename (old_name, new_name) == -1)
+    {
+      int save_errno = errno;
+      gchar *display_old_name = g_filename_display_name (old_name);
+      gchar *display_new_name = g_filename_display_name (new_name);
+
+      g_set_error (err,
+                  G_FILE_ERROR,
+                  g_file_error_from_errno (save_errno),
+                  "Failed to rename file '%s' to '%s': g_rename() failed: %s",
+                  display_old_name,
+                  display_new_name,
+                  g_strerror (save_errno));
+
+      g_free (display_old_name);
+      g_free (display_new_name);
+      
+      return FALSE;
+    }
+  
+  return TRUE;
+}
+
+static gboolean
+set_umask_permissions (int          fd,
+                      GError      **err)
+{
+#ifdef G_OS_WIN32
+
+  return TRUE;
+
+#else
+
+  /* All of this function is just to work around the fact that
+   * there is no way to get the umask without changing it.
+   *
+   * We can't just change-and-reset the umask because that would
+   * lead to a race condition if another thread tried to change
+   * the umask in between the getting and the setting of the umask.
+   * So we have to do the whole thing in a child process.
+   */
+  
+  pid_t pid = fork ();
+
+  if (pid == -1)
+    {
+      g_set_error (err,
+                  G_FILE_ERROR,
+                  g_file_error_from_errno (errno),
+                  "Could not change file mode: fork() failed: %s",
+                  g_strerror (errno));
+      
+      return FALSE;
+    }
+  else if (pid == 0)
+    {
+      /* child */
+      mode_t mask = umask (0666);
+
+      errno = 0;
+      if (fchmod (fd, 0666 & ~mask) == -1)
+       _exit (errno);
+      else
+       _exit (0);
+
+      return TRUE; /* To quiet gcc */
+    }
+  else
+    { 
+      /* parent */
+      int status;
+      
+      waitpid (pid, &status, 0);
+
+      if (WIFEXITED (status))
+       {
+         int chmod_errno = WEXITSTATUS (status);
+
+         if (chmod_errno == 0)
+           {
+             return TRUE;
+           }
+         else
+           {
+             g_set_error (err,
+                          G_FILE_ERROR,
+                          g_file_error_from_errno (chmod_errno),
+                          "Could not change file mode: chmod() failed: %s",
+                          g_strerror (chmod_errno));
+      
+             return FALSE;
+           }
+       }
+      else if (WIFSIGNALED (status))
+       {
+         g_set_error (err,
+                      G_FILE_ERROR,
+                      G_FILE_ERROR_FAILED,
+                      "Could not change file mode: Child terminated by signal: %s",
+                      g_strsignal (WTERMSIG (status)));
+                      
+         return FALSE;
+       }
+      else
+       {
+         /* This shouldn't happen */
+         g_set_error (err,
+                      G_FILE_ERROR,
+                      G_FILE_ERROR_FAILED,
+                      "Could not change file mode: Child terminated abnormally");
+         return FALSE;
+       }
+    }
+#endif
+}
+
+static gchar *
+write_to_temp_file (const gchar *contents,
+                   gssize length,
+                   const gchar *template,
+                   GError **err)
+{
+  gchar *tmp_name;
+  gchar *display_name;
+  gchar *retval;
+  FILE *file;
+  gint fd;
+  int save_errno;
+
+  retval = NULL;
+  
+  tmp_name = g_strdup_printf ("%s.XXXXXX", template);
+
+  errno = 0;
+  fd = g_mkstemp (tmp_name);
+  save_errno = errno;
+  display_name = g_filename_display_name (tmp_name);
+      
+  if (fd == -1)
+    {
+      g_set_error (err,
+                  G_FILE_ERROR,
+                  g_file_error_from_errno (save_errno),
+                  "Failed to create file '%s': %s",
+                  display_name, g_strerror (save_errno));
+      
+      goto out;
+    }
+
+  if (!set_umask_permissions (fd, err))
+    {
+      close (fd);
+      g_unlink (tmp_name);
+
+      goto out;
+    }
+  
+  errno = 0;
+  file = fdopen (fd, "wb");
+  if (!file)
+    {
+      g_set_error (err,
+                  G_FILE_ERROR,
+                  g_file_error_from_errno (errno),
+                  "Failed to open file '%s' for writing: fdopen() failed: %s",
+                  display_name,
+                  g_strerror (errno));
+
+      close (fd);
+      g_unlink (tmp_name);
+      
+      goto out;
+    }
+
+  if (length > 0)
+    {
+      size_t n_written;
+      
+      errno = 0;
+
+      n_written = fwrite (contents, 1, length, file);
+      
+      if (n_written < length)
+       {
+         g_set_error (err,
+                      G_FILE_ERROR,
+                      g_file_error_from_errno (errno),
+                      "Failed to write file '%s': fwrite() failed: %s",
+                      display_name,
+                      g_strerror (errno));
+
+         fclose (file);
+         g_unlink (tmp_name);
+         
+         goto out;
+       }
+    }
+   
+  errno = 0;
+  if (fclose (file) == EOF)
+    {
+      g_set_error (err,
+                  G_FILE_ERROR,
+                  g_file_error_from_errno (errno),
+                  "Failed to close file '%s': fclose() failed: %s",
+                  display_name, 
+                  g_strerror (errno));
+
+      g_unlink (tmp_name);
+      
+      goto out;
+    }
+
+  retval = g_strdup (tmp_name);
+  
+ out:
+  g_free (tmp_name);
+  g_free (display_name);
+  
+  return retval;
+}
+
+static gboolean
+file_replace (const gchar *filename,
+              const gchar *contents,
+              gssize        length,
+              GError      **error)
+{
+  gchar *tmp_filename;
+  gboolean retval;
+  GError *rename_error = NULL;
+  
+  g_return_val_if_fail (filename != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (contents != NULL || length == 0, FALSE);
+  g_return_val_if_fail (length >= -1, FALSE);
+  
+  if (length == -1)
+    length = strlen (contents);
+
+  tmp_filename = write_to_temp_file (contents, length, filename, error);
+  
+  if (!tmp_filename)
+    {
+      retval = FALSE;
+      goto out;
+    }
+
+  if (!rename_file (tmp_filename, filename, &rename_error))
+    {
+#ifndef G_OS_WIN32
+
+      g_unlink (tmp_filename);
+      g_propagate_error (error, rename_error);
+      retval = FALSE;
+      goto out;
+
+#else /* G_OS_WIN32 */
+      
+      /* Renaming failed, but on Windows this may just mean
+       * the file already exists. So if the target file
+       * exists, try deleting it and do the rename again.
+       */
+      if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+       {
+         g_unlink (tmp_filename);
+         g_propagate_error (error, rename_error);
+         retval = FALSE;
+         goto out;
+       }
+
+      g_error_free (rename_error);
+      
+      if (g_unlink (filename) == -1)
+       {
+          gchar *display_filename = g_filename_display_name (filename);
+
+         g_set_error (error,
+                      G_FILE_ERROR,
+                      g_file_error_from_errno (errno),
+                      "Existing file '%s' could not be removed: g_unlink() failed: %s",
+                      display_filename,
+                      g_strerror (errno));
+
+         g_free (display_filename);
+         g_unlink (tmp_filename);
+         retval = FALSE;
+         goto out;
+       }
+      
+      if (!rename_file (tmp_filename, filename, error))
+       {
+         g_unlink (tmp_filename);
+         retval = FALSE;
+         goto out;
+       }
+
+#endif
+    }
+
+  retval = TRUE;
+  
+ out:
+  g_free (tmp_filename);
+  return retval;
+}
index 3b21579..dc9e238 100644 (file)
--- a/sysprof.c
+++ b/sysprof.c
@@ -56,6 +56,22 @@ struct Application
     
     int                        timeout_id;
     int                        generating_profile;
+
+    gboolean           profile_from_file; /* FIXME: This is a kludge. Figure out how
+                                           * to maintain the application model properly
+                                           *
+                                           * The fundamental issue is that the state of
+                                           * widgets is controlled by two different
+                                           * entities:
+                                           *
+                                           *   The user clicks on them, changing their
+                                           *   state.
+                                           *
+                                           *   The application model changes, changing their
+                                           *   state.
+                                           *
+                                           * Model/View/Controller is a possibility.
+                                           */
 };
 
 static void
@@ -290,6 +306,7 @@ delete_data (Application *app)
     process_flush_caches ();
     app->n_samples = 0;
     queue_show_samples (app);
+    app->profile_from_file = FALSE;
 }
 
 static void
@@ -349,6 +366,7 @@ fill_main_list (Application *app)
                                         G_TYPE_POINTER);
 
        objects = profile_get_objects (profile);
+       g_print ("got %d objects\n", g_list_length (objects));
        for (list = objects; list != NULL; list = list->next)
        {
            ProfileObject *object = list->data;
@@ -414,12 +432,12 @@ on_profile_toggled (gpointer widget, gpointer data)
     
     if (gtk_toggle_tool_button_get_active (widget))
     {
-       if (app->profile)
+       if (app->profile && !app->profile_from_file)
        {
            profile_free (app->profile);
            app->profile = NULL;
        }
-       
+
        ensure_profile (app);
     }
 }
@@ -486,7 +504,27 @@ on_save_as_clicked (gpointer widget, gpointer data)
 static void
 on_open_clicked (gpointer widget, gpointer data)
 {
+#if 0
     sorry (NULL, "Open is not implemented yet. (Fortunately, neither is saving),");
+#endif
+    Application *app = data;
+    GError *err = NULL;
+    Profile *profile = profile_load ("name.profile", &err);
+    if (!profile)
+       sorry (NULL, "Could not open: %s\n", err->message);
+    else
+    {
+       delete_data (app);
+    
+       app->state = DISPLAYING;
+       
+       app->profile = profile;
+       app->profile_from_file = TRUE;
+    
+       fill_main_list (app);
+       
+       update_sensitivity (app);
+    }
 }
 
 static void