callbacks_handle_log_messages(const gchar *log_domain, GLogLevelFlags log_level,
		      const gchar *message, gpointer user_data)
{
	GtkTextBuffer *textbuffer = NULL;
	GtkTextIter iter;
	GtkTextTag *tag;
	GtkTextMark *StartMark = NULL, *StopMark = NULL;
	GtkTextIter StartIter, StopIter;
	GtkWidget *dialog, *label;

	if (!screen.win.messageTextView)
		return;

	textbuffer = gtk_text_view_get_buffer((GtkTextView*)screen.win.messageTextView);

	/* create a mark for the end of the text. */
	gtk_text_buffer_get_end_iter(textbuffer, &iter);

	/* get the current end position of the text (it will be the
	      start of the new text. */
	StartMark = gtk_text_buffer_create_mark(textbuffer,
					    "NewTextStart", &iter, TRUE);

	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
	                              "blue_foreground");
	/* the tag does not exist: create it and let them exist in the tag table.*/
	if (tag == NULL)    {
		tag = gtk_text_buffer_create_tag(textbuffer, "black_foreground",
		                              "foreground", "black", NULL);
		tag = gtk_text_buffer_create_tag(textbuffer, "blue_foreground",
		                              "foreground", "blue", NULL);
		tag = gtk_text_buffer_create_tag(textbuffer, "red_foreground",
		                              "foreground", "red", NULL);
		tag = gtk_text_buffer_create_tag(textbuffer, "darkred_foreground",
		                              "foreground", "darkred", NULL);
		tag = gtk_text_buffer_create_tag(textbuffer, "darkblue_foreground",
		                              "foreground", "darkblue", NULL);
		tag = gtk_text_buffer_create_tag (textbuffer, "darkgreen_foreground",
		                              "foreground", "darkgreen", NULL);
		tag = gtk_text_buffer_create_tag (textbuffer,
		                              "saddlebrown_foreground",
		                              "foreground", "saddlebrown", NULL);
	}

	/*
	* See rgb.txt for the color names definition
	* (on my PC it is on /usr/X11R6/lib/X11/rgb.txt)
	*/
	switch (log_level & G_LOG_LEVEL_MASK) {
	case G_LOG_LEVEL_ERROR:
	/* a message of this kind aborts the application calling abort() */
	      tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
	                                    "red_foreground");
	      gtk_notebook_set_current_page(GTK_NOTEBOOK(screen.win.sidepane_notebook), 1);
	      gtk_widget_show(screen.win.sidepane_notebook);
		break;
	case G_LOG_LEVEL_CRITICAL:
	      tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
	                                    "red_foreground");
	      gtk_notebook_set_current_page(GTK_NOTEBOOK(screen.win.sidepane_notebook), 1);
	      gtk_widget_show(screen.win.sidepane_notebook);
		break;
	case G_LOG_LEVEL_WARNING:
	      tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
	                                    "darkred_foreground");
	      gtk_notebook_set_current_page(GTK_NOTEBOOK(screen.win.sidepane_notebook), 1);
	      gtk_widget_show(screen.win.sidepane_notebook);
		break;
	case G_LOG_LEVEL_MESSAGE:
	      tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
	                                    "darkblue_foreground");
	      gtk_notebook_set_current_page(GTK_NOTEBOOK(screen.win.sidepane_notebook), 1);
	      gtk_widget_show(screen.win.sidepane_notebook);
		break;
	case G_LOG_LEVEL_INFO:
	      tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
	                                    "darkgreen_foreground");
		break;
	case G_LOG_LEVEL_DEBUG:
		tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(textbuffer),
					"saddlebrown_foreground");
		break;
	default:
	      tag = gtk_text_tag_table_lookup (gtk_text_buffer_get_tag_table(textbuffer),
	                                    "black_foreground");
		break;
	}

	/*
	* Fatal aborts application. We will try to get the message out anyhow.
	*/
	if (log_level & G_LOG_FLAG_FATAL) {
		fprintf(stderr, _("Fatal error: %s\n"), message);

		/* Try to show dialog box with error message */
		dialog = gtk_dialog_new_with_buttons(_("Fatal Error"),
			NULL, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);

		label = gtk_label_new(g_strdup_printf(_("Fatal error: %s"), message));
		gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
				label);
		gtk_label_set_selectable(GTK_LABEL(label), TRUE);
		gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
				gtk_label_new(_("\nGerbv will be closed now!")));

		gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);

		gtk_widget_show_all(dialog);
		gtk_dialog_run(GTK_DIALOG(dialog));
	}

	gtk_text_buffer_insert(textbuffer, &iter, message, -1);
	gtk_text_buffer_insert(textbuffer, &iter, "\n", -1);

	/* Scroll view to inserted text */
	g_signal_emit_by_name(textbuffer, "paste-done", NULL);

	gtk_text_buffer_get_end_iter(textbuffer, &iter);

	StopMark = gtk_text_buffer_create_mark(textbuffer,
					   "NewTextStop", &iter, TRUE);

	gtk_text_buffer_get_iter_at_mark(textbuffer, &StartIter, StartMark);
	gtk_text_buffer_get_iter_at_mark(textbuffer, &StopIter, StopMark);

	gtk_text_buffer_apply_tag(textbuffer, tag, &StartIter, &StopIter);
}


Remove local alias to parameter array

Normalizing access to `gerbv_simplified_amacro_t::parameter` as a step to fix CVE-2021-40402

static double arc_length(double dia, double angle)
{
	return M_PI*dia*(angle/360.0);
}


callbacks_drawingarea_button_release_event (GtkWidget *widget, GdkEventButton *event)
{
	gint index;

	if (event->type != GDK_BUTTON_RELEASE)
		return TRUE;

	switch (screen.state) {
	case IN_MOVE:
		screen.off_x = 0;
		screen.off_y = 0;
		render_refresh_rendered_image_on_screen ();
		callbacks_switch_to_normal_tool_cursor (screen.tool);
		break;

	case IN_ZOOM_OUTLINE:
		if ((event->state & GDK_SHIFT_MASK) != 0) {
			render_zoom_display (ZOOM_OUT_CMOUSE, 0,
					event->x, event->y);
		}
		/* if the user just clicks without dragging, then simply
		   zoom in a preset amount */
		else if ((abs(screen.start_x - event->x) < 4) &&
				(abs(screen.start_y - event->y) < 4)) {
			render_zoom_display (ZOOM_IN_CMOUSE, 0,
					event->x, event->y);
		} else {
			render_calculate_zoom_from_outline (widget, event);
		}
		callbacks_switch_to_normal_tool_cursor (screen.tool);
		break;

	case IN_SELECTION_DRAG:
		/* selection will only work with cairo, so do nothing if it's
		   not compiled */
		index = callbacks_get_selected_row_index ();

		if ((index >= 0) && mainProject->file[index]->isVisible) {
			enum selection_action sel_action = SELECTION_REPLACE;

			if (event->state & GDK_SHIFT_MASK)
				sel_action = SELECTION_ADD;
			else if (event->state & GDK_CONTROL_MASK)
				sel_action = SELECTION_TOGGLE;

			/* determine if this was just a click or a box drag */
			if ((fabs((double)(screen.last_x - screen.start_x)) < 5)
			 && (fabs((double)(screen.last_y - screen.start_y)) < 5)) {
				render_fill_selection_buffer_from_mouse_click (
						event->x, event->y, index,
						sel_action);
			} else {
				render_fill_selection_buffer_from_mouse_drag (
						event->x, event->y,
						screen.start_x, screen.start_y,
						index, sel_action);
			}

			/* Check if anything was selected */
			update_selected_object_message (TRUE);

			check_align_files_possibility (&screen.selectionInfo);
		} else {
			render_refresh_rendered_image_on_screen ();
		}
		break;
	default:
		break;
	}

	screen.state = NORMAL;

	return TRUE;
} /* button_release_event */


void callbacks_vadjustment_value_changed (GtkAdjustment *adjustment, gpointer user_data){
	/* make sure we're actually using the scrollbar to make sure we don't reset
	   lowerLeftY during a scrollbar redraw during something else */
	if (screen.state == SCROLLBAR) {
		screenRenderInfo.lowerLeftY = adjustment->upper -
			(gtk_adjustment_get_value (adjustment) + adjustment->page_size) + adjustment->lower;
	}
}


callbacks_support_benchmark (gerbv_render_info_t *renderInfo) {
	int i;
	time_t start, now;
	GdkPixmap *renderedPixmap = gdk_pixmap_new (NULL, renderInfo->displayWidth,
								renderInfo->displayHeight, 24);

	// start by running the GDK (fast) rendering test
	i = 0;
	start = time(NULL);
	now = start;
	while( now - 30 < start) {
		i++;
		dprintf("Benchmark():  Starting redraw #%d\n", i);
		gerbv_render_to_pixmap_using_gdk (mainProject, renderedPixmap, renderInfo, NULL, NULL);
		now = time(NULL);
		dprintf("Elapsed time = %ld seconds\n", (long int) (now - start));
	}
	g_message(_("FAST (=GDK) mode benchmark: %d redraws "
				"in %ld seconds (%g redraws/second)"),
		      i, (long int) (now - start), (double) i / (double)(now - start));
	gdk_pixmap_unref(renderedPixmap);

	// run the cairo (normal) render mode
	i = 0;
	start = time(NULL);
	now = start;
	renderInfo->renderType = GERBV_RENDER_TYPE_CAIRO_NORMAL;
	while( now - 30 < start) {
		i++;
		dprintf("Benchmark():  Starting redraw #%d\n", i);
		cairo_surface_t *cSurface = cairo_image_surface_create  (CAIRO_FORMAT_ARGB32,
	                              renderInfo->displayWidth, renderInfo->displayHeight);
		cairo_t *cairoTarget = cairo_create (cSurface);
		gerbv_render_all_layers_to_cairo_target (mainProject, cairoTarget, renderInfo);
		cairo_destroy (cairoTarget);
		cairo_surface_destroy (cSurface);
		now = time(NULL);
		dprintf("Elapsed time = %ld seconds\n", (long int) (now - start));
	}
	g_message(_("NORMAL (=Cairo) mode benchmark: %d redraws "
				"in %ld seconds (%g redraws/second)"),
		      i, (long int) (now - start), (double) i / (double)(now - start));
}


callbacks_window_key_press_event (GtkWidget *widget, GdkEventKey *event)
{
	switch (event->keyval) {
	case GDK_Escape:
		if (screen.tool == POINTER) {
			selection_clear (&screen.selectionInfo);
			update_selected_object_message (FALSE);
		}

		/* Escape may be used to abort outline zoom and just plain
		 * repaint */
		screen.state = NORMAL;
		render_refresh_rendered_image_on_screen();

		break;
	default:
		break;
	}

	return TRUE;
} /* key_press_event */


callbacks_new_project_activate (GtkMenuItem *menuitem, gpointer user_data)
{
	if (mainProject->last_loaded >= 0) {
		if (!interface_get_alert_dialog_response (
			_("Do you want to close any open layers "
			"and start a new project?"),
			_("Starting a new project will cause all currently "
			"open layers to be closed. Any unsaved changes "
			"will be lost."),
			FALSE, NULL, GTK_STOCK_CLOSE, GTK_STOCK_CANCEL))
			return;
	}
	/* Unload all layers and then clear layer window */
	gerbv_unload_all_layers (mainProject);
	callbacks_update_layer_tree ();
	selection_clear (&screen.selectionInfo);
	update_selected_object_message (FALSE);

	/* Destroy project info */
	if (mainProject->project) {
	    g_free(mainProject->project);
	    mainProject->project = NULL;
	}
	render_refresh_rendered_image_on_screen();
}


callbacks_render_type_changed () {
	static gboolean isChanging = FALSE;
	if (isChanging)
		return;

	isChanging = TRUE;
	gerbv_render_types_t type = screenRenderInfo.renderType;
	GtkCheckMenuItem *check_item = screen.win.menu_view_render_group[type];
	dprintf ("%s():  type = %d, check_item = %p\n", __FUNCTION__, type, check_item);
	gtk_check_menu_item_set_active (check_item, TRUE);
	gtk_combo_box_set_active (screen.win.sidepaneRenderComboBox, type);

	render_refresh_rendered_image_on_screen();
	isChanging = FALSE;
}


callbacks_benchmark_clicked (GtkButton *button, gpointer   user_data)
{
	// prepare render size and options (canvas size width and height are last 2 variables)
	gerbv_render_info_t renderInfo = {1.0, 1.0, 0, 0,
					GERBV_RENDER_TYPE_GDK, 640, 480};

	if (!interface_get_alert_dialog_response(_("Performance benchmark"),
			_("Application will be unresponsive for 1 minute! "
			"Run performance benchmark?"),
			FALSE, NULL, GTK_STOCK_OK, GTK_STOCK_CANCEL))
		return;

	GERB_COMPILE_WARNING(_("Running full zoom benchmark..."));
	while (g_main_context_iteration(NULL, FALSE)) {} // update log widget

	// autoscale the image for now...maybe we don't want to do this in order to
	//   allow benchmarking of different zoom levels?
	gerbv_render_zoom_to_fit_display (mainProject, &renderInfo);
	callbacks_support_benchmark (&renderInfo);

	GERB_COMPILE_WARNING(_("Running x5 zoom benchmark..."));
	while (g_main_context_iteration(NULL, FALSE)) {} // update log widget

	renderInfo.lowerLeftX += (screenRenderInfo.displayWidth /
				screenRenderInfo.scaleFactorX) / 3.0;
	renderInfo.lowerLeftY += (screenRenderInfo.displayHeight /
				screenRenderInfo.scaleFactorY) / 3.0;
	renderInfo.scaleFactorX *= 5;
	renderInfo.scaleFactorY *= 5;
	callbacks_support_benchmark (&renderInfo);
}


callbacks_move_layer_up_button_clicked  (GtkButton *button, gpointer   user_data) {
	gint index=callbacks_get_selected_row_index();
	if (index < 0) {
		show_no_layers_warning ();
		return;
	}
	if (index > 0) {
		gerbv_change_layer_order (mainProject, index, index - 1);
		callbacks_update_layer_tree ();
		callbacks_select_layer_row (index - 1);
		if (screenRenderInfo.renderType <= GERBV_RENDER_TYPE_GDK_XOR) {
			render_refresh_rendered_image_on_screen();
		}
		else {
			render_recreate_composite_surface (screen.drawing_area);
			callbacks_force_expose_event_for_screen ();
		}
	}
}


callbacks_about_activate                     (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	GtkWidget *aboutdialog1;
	/* TRANSLATORS: Replace this string with your names, one name per line. */
	gchar *translators = _("translator-credits");

	gchar *string = g_strdup_printf(_(
		"Gerbv — a Gerber (RS-274/X) viewer\n"
		"\n"
		"Version %s\n"
		"Compiled on %s at %s\n"
		"\n"
		"Gerbv was originally developed as part of the gEDA Project "
		"but is now separately maintained.\n"
		"\n"
		"For more information see:\n"
		"  gerbv homepage: https://gerbv.github.io/\n"
		"  gerbv repository: https://github.com/gerbv/gerbv"),
		VERSION, __DATE__, __TIME__);

#if GTK_CHECK_VERSION(2,6,0)
	gchar *license = g_strdup_printf(_(
		"Gerbv — a Gerber (RS-274/X) viewer\n"
		"\n"
		"Copyright (C) 2000—2007 Stefan Petersen\n"
		"\n"
		"This program is free software: you can redistribute it and/or modify\n"
		"it under the terms of the GNU General Public License as published by\n"
		"the Free Software Foundation, either version 2 of the License, or\n"
		"(at your option) any later version.\n"
		"\n"
		"This program is distributed in the hope that it will be useful,\n"
		"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
		"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
		"GNU General Public License for more details.\n\n"
		"You should have received a copy of the GNU General Public License\n"
		"along with this program.  If not, see <http://www.gnu.org/licenses/>."));

	#include "authors.c"

	int a_size, i;
	gchar **a;

	aboutdialog1 = gtk_about_dialog_new ();
	gtk_container_set_border_width (GTK_CONTAINER (aboutdialog1), 5);
	gtk_about_dialog_set_version (GTK_ABOUT_DIALOG (aboutdialog1), VERSION);
	gtk_about_dialog_set_name (GTK_ABOUT_DIALOG (aboutdialog1), _("Gerbv"));

	gtk_about_dialog_set_translator_credits (GTK_ABOUT_DIALOG (aboutdialog1), translators);
	gtk_about_dialog_set_comments (GTK_ABOUT_DIALOG (aboutdialog1), string);
	gtk_about_dialog_set_license(GTK_ABOUT_DIALOG (aboutdialog1), license);

	/* The authors.c file is autogenerated at build time */
	a_size = sizeof(authors_string_array)/sizeof(authors_string_array[0]);
	a = g_new(gchar *, a_size);
	for (i = 0; i < a_size; i++)
		a[i] = _(authors_string_array[i]);

	gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG (aboutdialog1), (const gchar **)a);
	gtk_about_dialog_set_website(GTK_ABOUT_DIALOG (aboutdialog1), "https://gerbv.github.io/");
	g_free(a);

	g_signal_connect (G_OBJECT(aboutdialog1),"response",
		      G_CALLBACK (gtk_widget_destroy), GTK_WIDGET(aboutdialog1));

	g_free (string);
	g_free (license);
#else
	aboutdialog1 = gtk_message_dialog_new (	GTK_WINDOW (screen.win.topLevelWindow),
					       GTK_DIALOG_DESTROY_WITH_PARENT,
					       GTK_MESSAGE_INFO,
					       GTK_BUTTONS_CLOSE,
					       string
					       );

	gtk_window_set_title ( GTK_WINDOW (aboutdialog1), _("About Gerbv"));

	/* Destroy the dialog when the user responds to it (e.g. clicks a button) */
	g_signal_connect_swapped (aboutdialog1, "response",
				  G_CALLBACK (gtk_widget_destroy),
				  aboutdialog1);
	g_free (string);
#endif

	gtk_widget_show_all(GTK_WIDGET(aboutdialog1));

}


gdouble callbacks_calculate_actual_distance (gdouble inputDimension) {
	return screen_units(inputDimension);
}


gchar *utf8_strncpy(gchar *dst, const gchar *src, gsize byte_len)
{
	/* -1 for '\0' in buffer */
	glong char_len = g_utf8_strlen(src, byte_len - 1);
	return g_utf8_strncpy(dst, src, char_len);
}


static void parea_report (gerbv_net_t *net,
		gerbv_image_t *img, gerbv_project_t *prj)
{
	gerbv_net_t *n;
	unsigned int c = 0;
	gerbv_interpolation_t inter_prev;
	double x, y;

	if (net->interpolation != GERBV_INTERPOLATION_PAREA_START)
		return;

	/* Count vertices */
	for (gerbv_net_t *n = net->next; n != NULL; n = n->next) {
		if (n->interpolation == GERBV_INTERPOLATION_PAREA_END)
			break;
		c++;
	}

	g_message (_("    Number of vertices: %u"), c - 1);

	for (n = net->next, inter_prev = net->interpolation;
			n != NULL
			&& n->interpolation != GERBV_INTERPOLATION_PAREA_END;
			n = n->next) {

		switch (n->interpolation) {

		case GERBV_INTERPOLATION_LINEARx1:

			if (inter_prev != n->interpolation) {
				x = n->start_x;
				y = n->start_y;
				gerbv_transform_coord_for_image(&x, &y,
						img, prj);
				g_message (_("    Line from: (%g, %g) %s"),
						screen_units(x),
						screen_units(y),
						screen_units_str());
			}

			x = n->stop_x;
			y = n->stop_y;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("        Line to: (%g, %g) %s"),
					screen_units(x), screen_units(y),
					screen_units_str());
			break;

		case GERBV_INTERPOLATION_CW_CIRCULAR:
		case GERBV_INTERPOLATION_CCW_CIRCULAR:

			x = n->start_x;
			y = n->start_y;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Arc from: (%g, %g) %s"),
					screen_units(x), screen_units(y),
					screen_units_str());

			x = n->stop_x;
			y = n->stop_y;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("        Arc to: (%g, %g) %s"),
					screen_units(x), screen_units(y),
					screen_units_str());

			x = n->cirseg->cp_x;
			y = n->cirseg->cp_y;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("        Center: (%g, %g) %s"),
					screen_units(x), screen_units(y),
					screen_units_str());

			x = n->cirseg->width;
			y = n->cirseg->height;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("        Radius: %g %s"),
					screen_units(x)/2, screen_units_str());

			g_message (_("        Angle: %g deg"),
				fabs(n->cirseg->angle1 - n->cirseg->angle2));
			g_message (_("        Angles: (%g, %g) deg"),
					n->cirseg->angle1, n->cirseg->angle2);
			g_message (_("        Direction: %s"),
					(n->interpolation ==
					 GERBV_INTERPOLATION_CW_CIRCULAR)?
						_("CW"): _("CCW"));
			break;

		default:
			g_message("       Skipping interpolation: %s",
				_(gerbv_interpolation_name(n->interpolation)));
		}

		inter_prev = n->interpolation;
	}
}


callbacks_file_drop_event(GtkWidget *widget, GdkDragContext *dc,
		gint x, gint y, GtkSelectionData *data,
		guint info, guint time, gpointer p)
{
	gchar **uris, **uri;
	GSList *fns = NULL;

	uris = gtk_selection_data_get_uris(data);
	if (!uris)
		return FALSE;

	for (uri = uris; *uri; uri++) {
		const char *prefix_str =
#ifdef WIN32
			"file:///";
#else
			"file://";
#endif
		if (g_strrstr(*uri, prefix_str) == *uri)
			fns = g_slist_append(fns, g_uri_unescape_string(
					*uri + strlen(prefix_str), NULL));
	}

	open_files(fns);
	g_slist_free_full(fns, g_free);
	g_strfreev(uris);

	return TRUE;
}


callbacks_fullscreen_toggled (GtkMenuItem *menuitem, gpointer user_data)
{
	//struct GtkWindow *win = (struct GtkWindow *)(screen.win.topLevelWindow);
	GdkWindowState state = gdk_window_get_state (gtk_widget_get_window(screen.win.topLevelWindow));
	if(state & GDK_WINDOW_STATE_FULLSCREEN)
		gtk_window_unfullscreen (GTK_WINDOW(screen.win.topLevelWindow));
	else
		gtk_window_fullscreen (GTK_WINDOW(screen.win.topLevelWindow));
}


callbacks_add_layer_button_clicked (GtkButton *button, gpointer user_data)
{
	callbacks_open_activate (NULL, NULL);
}


callbacks_switch_to_normal_tool_cursor (gint toolNumber)
{
	GdkWindow *drawing_area_window = screen.drawing_area->window;
	GdkCursor *cursor;

	switch (toolNumber) {
	case POINTER:
		gdk_window_set_cursor(drawing_area_window, GERBV_DEF_CURSOR);
		break;
	case PAN:
		cursor = gdk_cursor_new(GDK_FLEUR);
		gdk_window_set_cursor(drawing_area_window, cursor);
		gdk_cursor_destroy(cursor);
		break;
	case ZOOM:
		cursor = gdk_cursor_new(GDK_SIZING);
		gdk_window_set_cursor(drawing_area_window, cursor);
		gdk_cursor_destroy(cursor);
		break;
	case MEASURE:
		cursor = gdk_cursor_new(GDK_CROSSHAIR);
		gdk_window_set_cursor(drawing_area_window, cursor);
		gdk_cursor_destroy(cursor);
		break;
	default:
		break;
	}
}


callbacks_layer_tree_button_press (GtkWidget *widget, GdkEventButton *event,
					gpointer user_data)
{
	GtkTreePath *path;
	GtkTreeIter iter;
	GtkTreeViewColumn *col;
	gint x,y;
	gint *indices;

	GtkListStore *list_store = (GtkListStore *) gtk_tree_view_get_model (
			(GtkTreeView *) screen.win.layerTree);

	if (event->button == 1) {
		if (gtk_tree_view_get_path_at_pos ((GtkTreeView *) widget,
				event->x, event->y, &path, &col, &x, &y)
		&& gtk_tree_model_get_iter ((GtkTreeModel *)list_store,
				&iter, path)) {
			indices = gtk_tree_path_get_indices (path);
			if (indices && (indices[0] <= mainProject->last_loaded)) {
				switch (callbacks_get_col_num_from_tree_view_col (col)) {
				case 0:
					callbacks_select_layer_row (indices[0]);
					callbacks_layer_tree_visibility_toggled (indices[0]);
					return TRUE;
				case 1:
					callbacks_show_color_picker_dialog (indices[0]);
					/* don't propagate the signal, since drag and drop can
					   sometimes activated during color selection */
					return TRUE;
				}
			}
		}
	}
	/* don't pop up the menu if we don't have any loaded files */
	else if ((event->button == 3)&&(mainProject->last_loaded >= 0)) {
		gtk_menu_popup (GTK_MENU (screen.win.layerTreePopupMenu),
				NULL, NULL, NULL, NULL,
				event->button, event->time);
	}

	/* always allow the click to propagate and make sure the line is activated */
	return FALSE;
}


callbacks_zoom_out_activate                   (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	render_zoom_display (ZOOM_OUT, 0, 0, 0);
}


callbacks_sidepane_render_type_combo_box_changed (GtkComboBox *widget, gpointer user_data) {
	gerbv_render_types_t type = gtk_combo_box_get_active (widget);

	dprintf ("%s():  type = %d\n", __FUNCTION__, type);

	if (type < 0 || type == screenRenderInfo.renderType)
		return;

	screenRenderInfo.renderType = type;
	callbacks_render_type_changed ();
}


void callbacks_hadjustment_value_changed (GtkAdjustment *adjustment, gpointer user_data){
	/* make sure we're actually using the scrollbar to make sure we don't reset
	   lowerLeftX during a scrollbar redraw during something else */
	if (screen.state == SCROLLBAR) {
		screenRenderInfo.lowerLeftX = gtk_adjustment_get_value (adjustment);
	}
}


callbacks_generic_save_activate (GtkMenuItem     *menuitem,
				 gpointer         user_data)
{
	gchar *new_file_name = NULL;
	gchar *file_name = NULL;
	gchar *dir_name = NULL;
	gboolean error_visible_layers = FALSE;
	gchar *windowTitle = NULL;
	gerbv_fileinfo_t *act_file;
	gint file_index;
	gint processType = GPOINTER_TO_INT (user_data);
	GtkFileFilter *filter;
	GtkSpinButton *spin_but;
	GtkTooltips *tooltips;
	GtkWidget *label;
	GtkWidget *hbox;
	static gint dpi = 0;

	file_index = callbacks_get_selected_row_index ();
	if (file_index < 0) {
		interface_show_alert_dialog (_("No layer is currently active"),
			_("Please select a layer and try again."),
			FALSE,
			NULL);
		return;
	}

	act_file = mainProject->file[file_index];

	screen.win.gerber = gtk_file_chooser_dialog_new ("", NULL,
			GTK_FILE_CHOOSER_ACTION_SAVE, NULL, NULL, NULL);
	GtkFileChooser *file_chooser_p =
			GTK_FILE_CHOOSER(screen.win.gerber);
	gtk_file_chooser_set_do_overwrite_confirmation (file_chooser_p, TRUE);

	hbox = gtk_hbox_new (0, 0);
	spin_but = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range (0, 0, 1));
	label = gtk_label_new ("");
	tooltips = gtk_tooltips_new ();
	gtk_box_pack_end (GTK_BOX(hbox), GTK_WIDGET(spin_but), 0, 0, 1);
	gtk_box_pack_end (GTK_BOX(hbox), label, 0, 0, 5);
	gtk_box_pack_end (GTK_BOX(GTK_DIALOG(screen.win.gerber)->vbox),
			hbox, 0, 0, 2);

	switch (processType) {
	case CALLBACKS_SAVE_PROJECT_AS:
		windowTitle = g_strdup (_("Save project as..."));
		if (mainProject->project) {
			file_name = g_path_get_basename (mainProject->project);

			dir_name = g_path_get_dirname (mainProject->project);
		} else {
			file_name = g_strdup_printf("%s%s",
					pgettext("file name", "untitled"),
					GERBV_PROJECT_FILE_EXT);
			dir_name= g_path_get_dirname (act_file->fullPathname);
		}

		filter = gtk_file_filter_new ();
		gtk_file_filter_set_name (filter, _(gerbv_project_file_name));
		gtk_file_filter_add_pattern (filter, gerbv_project_file_pat);
		gtk_file_chooser_add_filter (file_chooser_p, filter);

		filter = gtk_file_filter_new ();
		gtk_file_filter_set_name (filter, _("All"));
		gtk_file_filter_add_pattern (filter, "*");
		gtk_file_chooser_add_filter (file_chooser_p, filter);

		break;
	case CALLBACKS_SAVE_FILE_PS:
		windowTitle = g_strdup_printf (
				_("Export visible layers to %s file as..."),
				_("PS"));
		if (0 == visible_file_name(&file_name, &dir_name, -1,
							".ps", ".ps")) {
			error_visible_layers = TRUE;
			break;
		}

		break;
	case CALLBACKS_SAVE_FILE_PDF:
		windowTitle = g_strdup_printf (
				_("Export visible layers to %s file as..."),
				_("PDF"));
		if (0 == visible_file_name(&file_name, &dir_name, -1,
							".pdf", ".pdf")) {
			error_visible_layers = TRUE;
			break;
		}

		break;
	case CALLBACKS_SAVE_FILE_SVG:
		windowTitle = g_strdup_printf (
				_("Export visible layers to %s file as..."),
				_("SVG"));
		if (0 == visible_file_name(&file_name, &dir_name, -1,
							".svg", ".svg")) {
			error_visible_layers = TRUE;
			break;
		}

		break;
	case CALLBACKS_SAVE_FILE_DXF:
#if HAVE_LIBDXFLIB
		windowTitle = g_strdup_printf (
			_("Export \"%s\" layer #%d to DXF file as..."),
			act_file->name, file_index + 1);
		file_name = g_strconcat (act_file->name, ".dxf", NULL);
		dir_name =  g_path_get_dirname (act_file->fullPathname);
#endif
		break;
	case CALLBACKS_SAVE_FILE_PNG:
		windowTitle = g_strdup_printf (
				_("Export visible layers to %s file as..."),
				_("PNG"));
		if (0 == visible_file_name(&file_name, &dir_name, -1,
							".png", ".png")) {
			error_visible_layers = TRUE;
			break;
		}

		gtk_label_set_text (GTK_LABEL(label), _("DPI:"));
		gtk_spin_button_set_range (spin_but, 0, 6000);
		gtk_spin_button_set_increments (spin_but, 10, 100);
		gtk_tooltips_set_tip (tooltips, GTK_WIDGET(label),
				_("DPI value, autoscaling if 0"), NULL);
		gtk_tooltips_set_tip (tooltips, GTK_WIDGET(spin_but),
				_("DPI value, autoscaling if 0"), NULL);
		gtk_spin_button_set_value (spin_but, dpi);
		gtk_widget_show_all (hbox);

		break;
	case CALLBACKS_SAVE_FILE_RS274X:
		windowTitle = g_strdup_printf(
			_("Export \"%s\" layer #%d to "
				"RS-274X file as..."),
			act_file->name, file_index+1);

		if (GERBV_LAYERTYPE_RS274X != act_file->image->layertype)
			file_name = g_strconcat (act_file->name, ".gbr", NULL);
		else
			file_name = g_strdup (act_file->name);

		dir_name =  g_path_get_dirname (act_file->fullPathname);
		break;
	case CALLBACKS_SAVE_FILE_RS274XM:
		windowTitle = g_strdup (_("Export merged visible layers to "
					"RS-274X file as..."));
		if (2 > visible_file_name(&file_name, &dir_name,
					GERBV_LAYERTYPE_RS274X, "", ".gbr")) {
			error_visible_layers = TRUE;
			break;
		}
	case CALLBACKS_SAVE_FILE_DRILL:
		windowTitle = g_strdup_printf(
			_("Export \"%s\" layer #%d to "
				"Excellon drill file as..."),
			act_file->name, file_index+1);

		if (GERBV_LAYERTYPE_DRILL != act_file->image->layertype)
			file_name = g_strconcat (act_file->name, ".drl", NULL);
		else
			file_name = g_strdup (act_file->name);

		dir_name =  g_path_get_dirname (act_file->fullPathname);
		break;
	case CALLBACKS_SAVE_FILE_DRILLM:
		windowTitle = g_strdup (_("Export merged visible layers to "
					"Excellon drill file as..."));
		if (2 > visible_file_name(&file_name, &dir_name,
				GERBV_LAYERTYPE_DRILL, "", ".drl")) {
			error_visible_layers = TRUE;
		}
		break;
	case CALLBACKS_SAVE_FILE_IDRILL:
		windowTitle = g_strdup_printf(
			_("Export \"%s\" layer #%d to ISEL NCP drill file as..."),
			act_file->name, file_index+1);
		file_name = g_strconcat (act_file->name, ".ncp", NULL);
		dir_name =  g_path_get_dirname (act_file->fullPathname);

		break;
	case CALLBACKS_SAVE_FILE_GEDA_PCB:
		windowTitle = g_strdup_printf (
			_("Export \"%s\" layer #%d to gEDA PCB file as..."),
			act_file->name, file_index + 1);
		file_name = g_strconcat (act_file->name, ".pcb", NULL);
		dir_name =  g_path_get_dirname (act_file->fullPathname);
		break;
	case CALLBACKS_SAVE_LAYER_AS:
		windowTitle = g_strdup_printf (_("Save \"%s\" layer #%d as..."),
				act_file->name, file_index+1);
		file_name = g_strdup (act_file->name);
		dir_name =  g_path_get_dirname (act_file->fullPathname);
		break;
	}

	if (file_name != NULL) {
		gtk_file_chooser_set_current_name (file_chooser_p, file_name);
		g_free (file_name);
	}
	if (dir_name != NULL) {
		gtk_file_chooser_set_current_folder (file_chooser_p, dir_name);
		g_free (dir_name);
	}

	gtk_dialog_add_buttons (GTK_DIALOG(screen.win.gerber),
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
			NULL);

	gtk_window_set_title (GTK_WINDOW(screen.win.gerber), windowTitle);
	g_free (windowTitle);

	if (error_visible_layers) {
		switch (processType) {
		case CALLBACKS_SAVE_FILE_RS274XM:
			interface_get_alert_dialog_response (
				_("Not enough Gerber layers are visible"),
				_("Two or more Gerber layers must be visible "
				"for export with merge."),
				FALSE, NULL, NULL, GTK_STOCK_CANCEL);
			break;
		case CALLBACKS_SAVE_FILE_DRILLM:
			interface_get_alert_dialog_response (
				_("Not enough Excellon layers are visible"),
				_("Two or more Excellon layers must be visible "
				"for export with merge."),
				FALSE, NULL, NULL, GTK_STOCK_CANCEL);
			break;
		default:
			interface_get_alert_dialog_response (
				_("No layers are visible"), _("One or more "
				"layers must be visible for export."),
				FALSE, NULL, NULL, GTK_STOCK_CANCEL);
		}

		gtk_widget_destroy (screen.win.gerber);
		callbacks_update_layer_tree ();

		return;
	}

	gtk_widget_show (screen.win.gerber);
	if (gtk_dialog_run (GTK_DIALOG(screen.win.gerber)) == GTK_RESPONSE_ACCEPT) {
		new_file_name = gtk_file_chooser_get_filename (file_chooser_p);
		dpi = gtk_spin_button_get_value_as_int (spin_but);
	}
	gtk_widget_destroy (screen.win.gerber);

	if (!new_file_name) {
		callbacks_update_layer_tree ();

		return;
	}

	switch (processType) {
	case CALLBACKS_SAVE_PROJECT_AS:
		main_save_as_project_from_filename (mainProject, new_file_name);
		rename_main_window(new_file_name, NULL);
		break;
	case CALLBACKS_SAVE_FILE_PS:
		gerbv_export_postscript_file_from_project_autoscaled (
				mainProject, new_file_name);
		break;
	case CALLBACKS_SAVE_FILE_PDF:
		gerbv_export_pdf_file_from_project_autoscaled (
				mainProject, new_file_name);
		break;
	case CALLBACKS_SAVE_FILE_SVG:
		gerbv_export_svg_file_from_project_autoscaled (
				mainProject, new_file_name);
		break;
	case CALLBACKS_SAVE_FILE_DXF:
#if HAVE_LIBDXFLIB
		if (gerbv_export_dxf_file_from_image(new_file_name,
				act_file->image, &act_file->transform)) {
			GERB_MESSAGE (
				_("\"%s\" layer #%d saved as DXF in \"%s\""),
				act_file->name, file_index + 1,
				new_file_name);
		}
#endif
		break;
	case CALLBACKS_SAVE_FILE_PNG:
		if (dpi == 0) {
			gerbv_export_png_file_from_project_autoscaled (
					mainProject,
					screenRenderInfo.displayWidth,
					screenRenderInfo.displayHeight,
					new_file_name);
		} else {	/* Non zero DPI */
			gerbv_render_size_t bb;
			gerbv_render_get_boundingbox (mainProject, &bb);
			gfloat w = bb.right - bb.left;
			gfloat h = bb.bottom - bb.top;
			gerbv_render_info_t renderInfo = {
				dpi, dpi,
				bb.left - (w*GERBV_DEFAULT_BORDER_COEFF)/2.0,
				bb.top - (h*GERBV_DEFAULT_BORDER_COEFF)/2.0,
				GERBV_RENDER_TYPE_CAIRO_HIGH_QUALITY,
				w*dpi*(1 + GERBV_DEFAULT_BORDER_COEFF),
				h*dpi*(1 + GERBV_DEFAULT_BORDER_COEFF),
			};
			gerbv_export_png_file_from_project (mainProject,
					&renderInfo, new_file_name);
		}

		break;
	case  CALLBACKS_SAVE_LAYER_AS:
		gerbv_save_layer_from_index (mainProject,
				file_index, new_file_name);

		/* Rename the file path in the index, so future saves will
		 * reference the new file path */
		g_free (act_file->fullPathname);
		act_file->fullPathname = g_strdup (new_file_name);
		g_free (act_file->name);
		act_file->name = g_path_get_basename (new_file_name);

		break;
	case CALLBACKS_SAVE_FILE_RS274X:
		if (gerbv_export_rs274x_file_from_image (new_file_name,
					act_file->image,
					&act_file->transform)) {
			GERB_MESSAGE (
				_("\"%s\" layer #%d saved as Gerber in \"%s\""),
				act_file->name, file_index + 1,
				new_file_name);
		}
		break;
	case CALLBACKS_SAVE_FILE_DRILL:
		if (gerbv_export_drill_file_from_image (new_file_name,
					act_file->image,
					&act_file->transform)) {
			GERB_MESSAGE (
				_("\"%s\" layer #%d saved as drill in \"%s\""),
				act_file->name, file_index + 1,
				new_file_name);
		}
		break;
	case CALLBACKS_SAVE_FILE_IDRILL:
		if (gerbv_export_isel_drill_file_from_image (new_file_name,
				act_file->image, &act_file->transform)) {
			GERB_MESSAGE (
				_("\"%s\" layer #%d saved as ISEL NCP drill "
				"in \"%s\""), act_file->name, file_index + 1,
				new_file_name);
		}
		break;
	case CALLBACKS_SAVE_FILE_GEDA_PCB:
		if (gerbv_export_geda_pcb_file_from_image(new_file_name,
				act_file->image, &act_file->transform)) {
			GERB_MESSAGE (
				_("\"%s\" layer #%d saved as gEDA PCB "
				"in \"%s\""), act_file->name, file_index + 1,
				new_file_name);
		}
		break;
	case CALLBACKS_SAVE_FILE_RS274XM: {
		gerbv_image_t *image;
		gerbv_user_transformation_t t = {0,0,1,1,0,FALSE,FALSE,FALSE};
		if (NULL != (image = merge_images (processType))) {
			if (gerbv_export_rs274x_file_from_image (
						new_file_name, image, &t)) {
				GERB_MESSAGE (_("Merged visible Gerber layers "
						"and saved in \"%s\""),
						new_file_name);
			}
			gerbv_destroy_image (image);
		}
		break;
	}
	case CALLBACKS_SAVE_FILE_DRILLM: {
		gerbv_image_t *image;
		gerbv_user_transformation_t t = {0,0,1,1,0,FALSE,FALSE,FALSE};
		if (NULL != (image = merge_images (processType))) {
			gerbv_export_drill_file_from_image (
					new_file_name, image, &t);
			gerbv_destroy_image (image);
			GERB_MESSAGE (_("Merged visible drill layers "
					"and saved in \"%s\""),
					new_file_name);
		}
		break;
	}
	}

	g_free (new_file_name);
	callbacks_update_layer_tree ();

	return;
}


callbacks_open_activate(GtkMenuItem *menuitem, gpointer user_data)
{
	GSList *fns = NULL;
	screen.win.gerber =
		gtk_file_chooser_dialog_new (
				_("Open Gerbv project, Gerber, drill, "
				"or pick&place files"),
			NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN,   GTK_RESPONSE_ACCEPT,
			NULL);

	gtk_file_chooser_set_select_multiple(
			(GtkFileChooser *)screen.win.gerber, TRUE);
	gtk_file_chooser_set_current_folder(
			(GtkFileChooser *)screen.win.gerber, mainProject->path);
	gtk_widget_show (screen.win.gerber);
	if (gtk_dialog_run ((GtkDialog*)screen.win.gerber) ==
			GTK_RESPONSE_ACCEPT) {
		fns = gtk_file_chooser_get_filenames(
				GTK_FILE_CHOOSER (screen.win.gerber));
		/* Update the last folder */
		g_free (mainProject->path);
		mainProject->path = gtk_file_chooser_get_current_folder(
				(GtkFileChooser *)screen.win.gerber);
	}
	gtk_widget_destroy (screen.win.gerber);

	open_files (fns);
	g_slist_free_full (fns, g_free);
}


callbacks_statusbar_unit_combo_box_changed (GtkComboBox *widget, gpointer user_data) {
	gerbv_gui_unit_t unit = gtk_combo_box_get_active (widget);
	int force_change = GPOINTER_TO_INT (user_data);

	if (!force_change && (unit < 0 || unit == screen.unit))
		return;

	callbacks_units_changed (unit);
}


Avoided allocating and reading an attribute beyond its full size

Before reading a full attribute value for internal use, its expected
length has been checked to be < 0x40000. However the allocated size
in the runlist may be much bigger as a consequence of a bug or malice.
To prevent malloc'ing excessive size, restrict the size of the last
run to read to the needed length.

s64 ntfs_get_attribute_value_length(const ATTR_RECORD *a)
{
	if (!a) {
		errno = EINVAL;
		return 0;
	}
	errno = 0;
	if (a->non_resident)
		return sle64_to_cpu(a->data_size);

	return (s64)le32_to_cpu(a->value_length);
}


int ntfs_attr_map_whole_runlist(ntfs_attr *na)
{
	VCN next_vcn, last_vcn, highest_vcn;
	ntfs_attr_search_ctx *ctx;
	ntfs_volume *vol = na->ni->vol;
	ATTR_RECORD *a;
	int ret = -1;
	int not_mapped;

	ntfs_log_enter("Entering for inode %llu, attr 0x%x.\n",
		       (unsigned long long)na->ni->mft_no, le32_to_cpu(na->type));

		/* avoid multiple full runlist mappings */
	if (NAttrFullyMapped(na)) {
		ret = 0;
		goto out;
	}
	ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
	if (!ctx)
		goto out;

	/* Map all attribute extents one by one. */
	next_vcn = last_vcn = highest_vcn = 0;
	a = NULL;
	while (1) {
		runlist_element *rl;

		not_mapped = 0;
		if (ntfs_rl_vcn_to_lcn(na->rl, next_vcn) == LCN_RL_NOT_MAPPED)
			not_mapped = 1;

		if (ntfs_attr_lookup(na->type, na->name, na->name_len,
				CASE_SENSITIVE, next_vcn, NULL, 0, ctx))
			break;

		a = ctx->attr;

		if (not_mapped) {
			/* Decode the runlist. */
			rl = ntfs_mapping_pairs_decompress(na->ni->vol,
								a, na->rl);
			if (!rl)
				goto err_out;
			na->rl = rl;
		}

		/* Are we in the first extent? */
		if (!next_vcn) {
			 if (a->lowest_vcn) {
				 errno = EIO;
				 ntfs_log_perror("First extent of inode %llu "
					"attribute has non-zero lowest_vcn",
					(unsigned long long)na->ni->mft_no);
				 goto err_out;
			}
			/* Get the last vcn in the attribute. */
			last_vcn = sle64_to_cpu(a->allocated_size) >>
					vol->cluster_size_bits;
		}

		/* Get the lowest vcn for the next extent. */
		highest_vcn = sle64_to_cpu(a->highest_vcn);
		next_vcn = highest_vcn + 1;

		/* Only one extent or error, which we catch below. */
		if (next_vcn <= 0) {
			errno = ENOENT;
			break;
		}

		/* Avoid endless loops due to corruption. */
		if (next_vcn < sle64_to_cpu(a->lowest_vcn)) {
			errno = EIO;
			ntfs_log_perror("Inode %llu has corrupt attribute list",
					(unsigned long long)na->ni->mft_no);
			goto err_out;
		}
	}
	if (!a) {
		ntfs_log_perror("Couldn't find attribute for runlist mapping");
		goto err_out;
	}
		/*
		 * Cannot check highest_vcn when the last runlist has
		 * been modified earlier, as runlists and sizes may be
		 * updated without highest_vcn being in sync, when
		 * HOLES_DELAY is used
		 */
	if (not_mapped && highest_vcn && highest_vcn != last_vcn - 1) {
		errno = EIO;
		ntfs_log_perror("Failed to load full runlist: inode: %llu "
				"highest_vcn: 0x%llx last_vcn: 0x%llx",
				(unsigned long long)na->ni->mft_no,
				(long long)highest_vcn, (long long)last_vcn);
		goto err_out;
	}
	if (errno == ENOENT) {
		NAttrSetFullyMapped(na);
		ret = 0;
	}
err_out:
	ntfs_attr_put_search_ctx(ctx);
out:
	ntfs_log_leave("\n");
	return ret;
}


s64 ntfs_attr_pread(ntfs_attr *na, const s64 pos, s64 count, void *b)
{
	s64 ret;

	if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) {
		errno = EINVAL;
		ntfs_log_perror("%s: na=%p  b=%p  pos=%lld  count=%lld",
				__FUNCTION__, na, b, (long long)pos,
				(long long)count);
		return -1;
	}

	ntfs_log_enter("Entering for inode %lld attr 0x%x pos %lld count "
		       "%lld\n", (unsigned long long)na->ni->mft_no,
		       le32_to_cpu(na->type), (long long)pos, (long long)count);

	ret = ntfs_attr_pread_i(na, pos, count, b);

	ntfs_log_leave("\n");
	return ret;
}


static int ntfs_attr_map_partial_runlist(ntfs_attr *na, VCN vcn)
{
	VCN last_vcn;
	VCN highest_vcn;
	VCN needed;
	runlist_element *rl;
	ATTR_RECORD *a;
	BOOL startseen;
	ntfs_attr_search_ctx *ctx;
	BOOL done;
	BOOL newrunlist;

	if (NAttrFullyMapped(na))
		return 0;

	ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
	if (!ctx)
		return -1;

	/* Get the last vcn in the attribute. */
	last_vcn = na->allocated_size >> na->ni->vol->cluster_size_bits;

	needed = vcn;
	highest_vcn = 0;
	startseen = FALSE;
	done = FALSE;
	rl = (runlist_element*)NULL;
	do {
		newrunlist = FALSE;
		/* Find the attribute in the mft record. */
		if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
				needed, NULL, 0, ctx)) {

			a = ctx->attr;
				/* Decode and merge the runlist. */
			if (ntfs_rl_vcn_to_lcn(na->rl, needed)
						== LCN_RL_NOT_MAPPED) {
				rl = ntfs_mapping_pairs_decompress(na->ni->vol,
					a, na->rl);
				newrunlist = TRUE;
			} else
				rl = na->rl;
			if (rl) {
				na->rl = rl;
				highest_vcn = sle64_to_cpu(a->highest_vcn);
				if (highest_vcn < needed) {
				/* corruption detection on unchanged runlists */
					if (newrunlist
					    && ((highest_vcn + 1) < last_vcn)) {
						ntfs_log_error("Corrupt attribute list\n");
						rl = (runlist_element*)NULL;
						errno = EIO;
					}
					done = TRUE;
				}
				needed = highest_vcn + 1;
				if (!a->lowest_vcn)
					startseen = TRUE;
			}
		} else {
			done = TRUE;
		}
	} while (rl && !done && (needed < last_vcn));
	ntfs_attr_put_search_ctx(ctx);
		/*
		 * Make sure we reached the end, unless the last
		 * runlist was modified earlier (using HOLES_DELAY
		 * leads to have a visibility over attributes which
		 * have not yet been fully updated)
		 */
	if (done && newrunlist && (needed < last_vcn)) {
		ntfs_log_error("End of runlist not reached\n");
		rl = (runlist_element*)NULL;
		errno = EIO;
	}
		/* mark fully mapped if we did so */
	if (rl && startseen)
		NAttrSetFullyMapped(na);
	return (rl ? 0 : -1);
}


static void __ntfs_attr_init(ntfs_attr *na, ntfs_inode *ni,
		const ATTR_TYPES type, ntfschar *name, const u32 name_len)
{
	na->rl = NULL;
	na->ni = ni;
	na->type = type;
	na->name = name;
	if (name)
		na->name_len = name_len;
	else
		na->name_len = 0;
}


ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type,
		ntfschar *name, u32 name_len)
{
	ntfs_attr_search_ctx *ctx;
	ntfs_attr *na = NULL;
	ntfschar *newname = NULL;
	ATTR_RECORD *a;
	le16 cs;

	ntfs_log_enter("Entering for inode %lld, attr 0x%x.\n",
		       (unsigned long long)ni->mft_no, le32_to_cpu(type));

	if (!ni || !ni->vol || !ni->mrec) {
		errno = EINVAL;
		goto out;
	}
	na = ntfs_calloc(sizeof(ntfs_attr));
	if (!na)
		goto out;
	if (!name_len)
		name = (ntfschar*)NULL;
	if (name && name != AT_UNNAMED && name != NTFS_INDEX_I30) {
		/* A null char leads to a short name and unallocated bytes */
		if (ntfs_ucsnlen(name, name_len) != name_len) {
			ntfs_log_error("Null character in attribute name"
				" of inode %lld\n",(long long)ni->mft_no);
			goto err_out;
		}
		name = ntfs_ucsndup(name, name_len);
		if (!name)
			goto err_out;
		newname = name;
	}

	ctx = ntfs_attr_get_search_ctx(ni, NULL);
	if (!ctx)
		goto err_out;

	if (ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx))
		goto put_err_out;

	a = ctx->attr;

	if (!name) {
		if (a->name_length) {
			name = ntfs_ucsndup((ntfschar*)((u8*)a + le16_to_cpu(
					a->name_offset)), a->name_length);
			if (!name)
				goto put_err_out;
			newname = name;
			name_len = a->name_length;
		} else {
			name = AT_UNNAMED;
			name_len = 0;
		}
	}

	__ntfs_attr_init(na, ni, type, name, name_len);

	/*
	 * Wipe the flags in case they are not zero for an attribute list
	 * attribute.  Windows does not complain about invalid flags and chkdsk
	 * does not detect or fix them so we need to cope with it, too.
	 */
	if (type == AT_ATTRIBUTE_LIST)
		a->flags = const_cpu_to_le16(0);

	if ((type == AT_DATA)
	   && (a->non_resident ? !a->initialized_size : !a->value_length)) {
		/*
		 * Define/redefine the compression state if stream is
		 * empty, based on the compression mark on parent
		 * directory (for unnamed data streams) or on current
		 * inode (for named data streams). The compression mark
		 * may change any time, the compression state can only
		 * change when stream is wiped out.
		 *
		 * Also prevent compression on NTFS version < 3.0
		 * or cluster size > 4K or compression is disabled
		 */
		a->flags &= ~ATTR_COMPRESSION_MASK;
		if ((ni->flags & FILE_ATTR_COMPRESSED)
		    && (ni->vol->major_ver >= 3)
		    && NVolCompression(ni->vol)
		    && (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE))
			a->flags |= ATTR_IS_COMPRESSED;
	}

	cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE);

	/* a file may be sparse though its unnamed data is not (cf $UsnJrnl) */
	if (na->type == AT_DATA && na->name == AT_UNNAMED &&
	    (((a->flags & ATTR_IS_SPARSE)     && !NAttrSparse(na)) ||
	     (!(a->flags & ATTR_IS_ENCRYPTED)  != !NAttrEncrypted(na)))) {
		errno = EIO;
		ntfs_log_perror("Inode %lld has corrupt attribute flags "
				"(0x%x <> 0x%x)",(unsigned long long)ni->mft_no,
				le16_to_cpu(a->flags), le32_to_cpu(na->ni->flags));
		goto put_err_out;
	}

	if (a->non_resident) {
		if (((a->flags & ATTR_COMPRESSION_MASK)
			|| a->compression_unit)
		    && (ni->vol->major_ver < 3)) {
			errno = EIO;
			ntfs_log_perror("Compressed inode %lld not allowed"
					" on NTFS %d.%d",
					(unsigned long long)ni->mft_no,
					ni->vol->major_ver,
					ni->vol->major_ver);
			goto put_err_out;
		}
		if ((a->flags & ATTR_COMPRESSION_MASK)
				 && !a->compression_unit) {
			errno = EIO;
			ntfs_log_perror("Compressed inode %lld attr 0x%x has "
					"no compression unit",
					(unsigned long long)ni->mft_no, le32_to_cpu(type));
			goto put_err_out;
		}
		if ((a->flags & ATTR_COMPRESSION_MASK)
				 && (a->compression_unit
					!= STANDARD_COMPRESSION_UNIT)) {
			errno = EIO;
			ntfs_log_perror("Compressed inode %lld attr 0x%lx has "
					"an unsupported compression unit %d",
					(unsigned long long)ni->mft_no,
					(long)le32_to_cpu(type),
					(int)a->compression_unit);
			goto put_err_out;
		}
		ntfs_attr_init(na, TRUE, a->flags,
				a->flags & ATTR_IS_ENCRYPTED,
				a->flags & ATTR_IS_SPARSE,
				sle64_to_cpu(a->allocated_size),
				sle64_to_cpu(a->data_size),
				sle64_to_cpu(a->initialized_size),
				cs ? sle64_to_cpu(a->compressed_size) : 0,
				cs ? a->compression_unit : 0);
	} else {
		s64 l = le32_to_cpu(a->value_length);
		ntfs_attr_init(na, FALSE, a->flags,
				a->flags & ATTR_IS_ENCRYPTED,
				a->flags & ATTR_IS_SPARSE, (l + 7) & ~7, l, l,
				cs ? (l + 7) & ~7 : 0, 0);
	}
	ntfs_attr_put_search_ctx(ctx);
out:
	ntfs_log_leave("\n");
	return na;

put_err_out:
	ntfs_attr_put_search_ctx(ctx);
err_out:
	free(newname);
	free(na);
	na = NULL;
	goto out;
}


void ntfs_attr_close(ntfs_attr *na)
{
	if (!na)
		return;
	if (NAttrNonResident(na) && na->rl)
		free(na->rl);
	/* Don't release if using an internal constant. */
	if (na->name != AT_UNNAMED && na->name != NTFS_INDEX_I30
				&& na->name != STREAM_SDS)
		free(na->name);
	free(na);
}


int ntfs_attr_map_runlist(ntfs_attr *na, VCN vcn)
{
	LCN lcn;
	ntfs_attr_search_ctx *ctx;

	ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn 0x%llx.\n",
		(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type), (long long)vcn);

	lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
	if (lcn >= 0 || lcn == LCN_HOLE || lcn == LCN_ENOENT)
		return 0;

	ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
	if (!ctx)
		return -1;

	/* Find the attribute in the mft record. */
	if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
			vcn, NULL, 0, ctx)) {
		runlist_element *rl;

		/* Decode the runlist. */
		rl = ntfs_mapping_pairs_decompress(na->ni->vol, ctx->attr,
				na->rl);
		if (rl) {
			na->rl = rl;
			ntfs_attr_put_search_ctx(ctx);
			return 0;
		}
	}

	ntfs_attr_put_search_ctx(ctx);
	return -1;
}


void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident,
		const ATTR_FLAGS data_flags,
		const BOOL encrypted, const BOOL sparse,
		const s64 allocated_size, const s64 data_size,
		const s64 initialized_size, const s64 compressed_size,
		const u8 compression_unit)
{
	if (!NAttrInitialized(na)) {
		na->data_flags = data_flags;
		if (non_resident)
			NAttrSetNonResident(na);
		if (data_flags & ATTR_COMPRESSION_MASK)
			NAttrSetCompressed(na);
		if (encrypted)
			NAttrSetEncrypted(na);
		if (sparse)
			NAttrSetSparse(na);
		na->allocated_size = allocated_size;
		na->data_size = data_size;
		na->initialized_size = initialized_size;
		if ((data_flags & ATTR_COMPRESSION_MASK) || sparse) {
			ntfs_volume *vol = na->ni->vol;

			na->compressed_size = compressed_size;
			na->compression_block_clusters = 1 << compression_unit;
			na->compression_block_size = 1 << (compression_unit +
					vol->cluster_size_bits);
			na->compression_block_size_bits = ffs(
					na->compression_block_size) - 1;
		}
		NAttrSetInitialized(na);
	}
}


runlist_element *ntfs_attr_find_vcn(ntfs_attr *na, const VCN vcn)
{
	runlist_element *rl;
	BOOL is_retry = FALSE;

	if (!na || !NAttrNonResident(na) || vcn < 0) {
		errno = EINVAL;
		return NULL;
	}

	ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn %llx\n",
		       (unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
		       (long long)vcn);
retry:
	rl = na->rl;
	if (!rl)
		goto map_rl;
	if (vcn < rl[0].vcn)
		goto map_rl;
	while (rl->length) {
		if (vcn < rl[1].vcn) {
			if (rl->lcn >= (LCN)LCN_HOLE)
				return rl;
			break;
		}
		rl++;
	}
	switch (rl->lcn) {
	case (LCN)LCN_RL_NOT_MAPPED:
		goto map_rl;
	case (LCN)LCN_ENOENT:
		errno = ENOENT;
		break;
	case (LCN)LCN_EINVAL:
		errno = EINVAL;
		break;
	default:
		errno = EIO;
		break;
	}
	return NULL;
map_rl:
	/* The @vcn is in an unmapped region, map the runlist and retry. */
	if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) {
		is_retry = TRUE;
		goto retry;
	}
	/*
	 * If we already retried or the mapping attempt failed something has
	 * gone badly wrong. EINVAL and ENOENT coming from a failed mapping
	 * attempt are equivalent to errors for us as they should not happen
	 * in our code paths.
	 */
	if (is_retry || errno == EINVAL || errno == ENOENT)
		errno = EIO;
	return NULL;
}


static void NAttrSetFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
{
	if (na->type == AT_DATA && na->name == AT_UNNAMED)
		na->ni->flags |= flag;
	else
		ntfs_log_trace("Denied setting flag %d for not unnamed data "
			       "attribute\n", le32_to_cpu(flag));
}


s64 ntfs_get_attribute_value(const ntfs_volume *vol,
		const ATTR_RECORD *a, u8 *b)
{
	runlist *rl;
	s64 total, r;
	int i;

	/* Sanity checks. */
	if (!vol || !a || !b) {
		errno = EINVAL;
		return 0;
	}
	/* Complex attribute? */
	/*
	 * Ignore the flags in case they are not zero for an attribute list
	 * attribute.  Windows does not complain about invalid flags and chkdsk
	 * does not detect or fix them so we need to cope with it, too.
	 */
	if (a->type != AT_ATTRIBUTE_LIST && a->flags) {
		ntfs_log_error("Non-zero (%04x) attribute flags. Cannot handle "
			       "this yet.\n", le16_to_cpu(a->flags));
		errno = EOPNOTSUPP;
		return 0;
	}
	if (!a->non_resident) {
		/* Attribute is resident. */

		/* Sanity check. */
		if (le32_to_cpu(a->value_length) + le16_to_cpu(a->value_offset)
				> le32_to_cpu(a->length)) {
			return 0;
		}

		memcpy(b, (const char*)a + le16_to_cpu(a->value_offset),
				le32_to_cpu(a->value_length));
		errno = 0;
		return (s64)le32_to_cpu(a->value_length);
	}

	/* Attribute is not resident. */

	/* If no data, return 0. */
	if (!(a->data_size)) {
		errno = 0;
		return 0;
	}
	/*
	 * FIXME: What about attribute lists?!? (AIA)
	 */
	/* Decompress the mapping pairs array into a runlist. */
	rl = ntfs_mapping_pairs_decompress(vol, a, NULL);
	if (!rl) {
		errno = EINVAL;
		return 0;
	}
	/*
	 * FIXED: We were overflowing here in a nasty fashion when we
	 * reach the last cluster in the runlist as the buffer will
	 * only be big enough to hold data_size bytes while we are
	 * reading in allocated_size bytes which is usually larger
	 * than data_size, since the actual data is unlikely to have a
	 * size equal to a multiple of the cluster size!
	 * FIXED2:  We were also overflowing here in the same fashion
	 * when the data_size was more than one run smaller than the
	 * allocated size which happens with Windows XP sometimes.
	 */
	/* Now load all clusters in the runlist into b. */
	for (i = 0, total = 0; rl[i].length; i++) {
		if (total + (rl[i].length << vol->cluster_size_bits) >=
				sle64_to_cpu(a->data_size)) {
			unsigned char *intbuf = NULL;
			s64 intlth;
			/*
			 * We have reached the last run so we were going to
			 * overflow when executing the ntfs_pread() which is
			 * BAAAAAAAD!
			 * Temporary fix:
			 *	Allocate a new buffer with size:
			 *	rl[i].length << vol->cluster_size_bits, do the
			 *	read into our buffer, then memcpy the correct
			 *	amount of data into the caller supplied buffer,
			 *	free our buffer, and continue.
			 * We have reached the end of data size so we were
			 * going to overflow in the same fashion.
			 * Temporary fix:  same as above.
			 *
			 * For safety, limit the amount to read to the
			 * needed size, knowing that the whole attribute
			 * size has been checked to be <= 0x40000.
			 */
			intlth = (sle64_to_cpu(a->data_size) - total
					+ vol->cluster_size - 1)
					>> vol->cluster_size_bits;
			if (rl[i].length < intlth)
				intlth = rl[i].length;
			intbuf = (u8*)ntfs_malloc(intlth
						<< vol->cluster_size_bits);
			if (!intbuf) {
				free(rl);
				return 0;
			}
			/*
			 * FIXME: If compressed file: Only read if lcn != -1.
			 * Otherwise, we are dealing with a sparse run and we
			 * just memset the user buffer to 0 for the length of
			 * the run, which should be 16 (= compression unit
			 * size).
			 * FIXME: Really only when file is compressed, or can
			 * we have sparse runs in uncompressed files as well?
			 * - Yes we can, in sparse files! But not necessarily
			 * size of 16, just run length.
			 */
			r = ntfs_pread(vol->dev,
					rl[i].lcn << vol->cluster_size_bits,
					intlth << vol->cluster_size_bits,
					intbuf);
			if (r != intlth << vol->cluster_size_bits) {
#define ESTR "Error reading attribute value"
				if (r == -1)
					ntfs_log_perror(ESTR);
				else if (r < intlth <<
						vol->cluster_size_bits) {
					ntfs_log_debug(ESTR ": Ran out of input data.\n");
					errno = EIO;
				} else {
					ntfs_log_debug(ESTR ": unknown error\n");
					errno = EIO;
				}
#undef ESTR
				free(rl);
				free(intbuf);
				return 0;
			}
			memcpy(b + total, intbuf, sle64_to_cpu(a->data_size) -
					total);
			free(intbuf);
			total = sle64_to_cpu(a->data_size);
			break;
		}
		/*
		 * FIXME: If compressed file: Only read if lcn != -1.
		 * Otherwise, we are dealing with a sparse run and we just
		 * memset the user buffer to 0 for the length of the run, which
		 * should be 16 (= compression unit size).
		 * FIXME: Really only when file is compressed, or can
		 * we have sparse runs in uncompressed files as well?
		 * - Yes we can, in sparse files! But not necessarily size of
		 * 16, just run length.
		 */
		r = ntfs_pread(vol->dev, rl[i].lcn << vol->cluster_size_bits,
				rl[i].length << vol->cluster_size_bits,
				b + total);
		if (r != rl[i].length << vol->cluster_size_bits) {
#define ESTR "Error reading attribute value"
			if (r == -1)
				ntfs_log_perror(ESTR);
			else if (r < rl[i].length << vol->cluster_size_bits) {
				ntfs_log_debug(ESTR ": Ran out of input data.\n");
				errno = EIO;
			} else {
				ntfs_log_debug(ESTR ": unknown error\n");
				errno = EIO;
			}
#undef ESTR
			free(rl);
			return 0;
		}
		total += r;
	}
	free(rl);
	return total;
}


static int split_compressed_hole(ntfs_attr *na, runlist_element **prl,
    		s64 pos, s64 count, VCN *update_from)
{
	int compressed_part;
	int cluster_size_bits = na->ni->vol->cluster_size_bits;
	runlist_element *rl = *prl;

	compressed_part
		= na->compression_block_clusters;
		/* reserve entries in runlist if we have to split */
	if (rl->length > na->compression_block_clusters) {
		*prl = ntfs_rl_extend(na,*prl,2);
		if (!*prl) {
			compressed_part = -1;
		} else {
			rl = *prl;
			na->unused_runs = 2;
		}
	}
	if (*prl && (rl->length > na->compression_block_clusters)) {
		/*
		 * Locate the update part relative to beginning of
		 * current run
		 */
		int beginwrite = (pos >> cluster_size_bits) - rl->vcn;
		s32 endblock = (((pos + count - 1) >> cluster_size_bits)
			| (na->compression_block_clusters - 1)) + 1 - rl->vcn;

		compressed_part = na->compression_block_clusters
			- (rl->length & (na->compression_block_clusters - 1));
		if ((beginwrite + compressed_part) >= na->compression_block_clusters)
			compressed_part = na->compression_block_clusters;
			/*
			 * if the run ends beyond end of needed block
			 * we have to split the run
			 */
		if (endblock < rl[0].length) {
			runlist_element *xrl;
			int n;

			/*
			 * we have to split into three parts if the run
			 * does not end within the first compression block.
			 * This means the hole begins before the
			 * compression block.
			 */
			if (endblock > na->compression_block_clusters) {
				if (na->unused_runs < 2) {
ntfs_log_error("No free run, case 1\n");
				}
				na->unused_runs -= 2;
				xrl = rl;
				n = 0;
				while (xrl->length) {
					xrl++;
					n++;
				}
				do {
					xrl[2] = *xrl;
					xrl--;
				} while (xrl != rl);
				rl[1].length = na->compression_block_clusters;
				rl[2].length = rl[0].length - endblock;
				rl[0].length = endblock
					- na->compression_block_clusters;
				rl[1].lcn = LCN_HOLE;
				rl[2].lcn = LCN_HOLE;
				rl[1].vcn = rl[0].vcn + rl[0].length;
				rl[2].vcn = rl[1].vcn
					+ na->compression_block_clusters;
				rl = ++(*prl);
			} else {
				/*
				 * split into two parts and use the
				 * first one
				 */
				if (!na->unused_runs) {
ntfs_log_error("No free run, case 2\n");
				}
				na->unused_runs--;
				xrl = rl;
				n = 0;
				while (xrl->length) {
					xrl++;
					n++;
				}
				do {
					xrl[1] = *xrl;
					xrl--;
				} while (xrl != rl);
				if (beginwrite < endblock) {
					/* we will write into the first part of hole */
					rl[1].length = rl[0].length - endblock;
					rl[0].length = endblock;
					rl[1].vcn = rl[0].vcn + rl[0].length;
					rl[1].lcn = LCN_HOLE;
				} else {
					/* we will write into the second part of hole */
// impossible ?
					rl[1].length = rl[0].length - endblock;
					rl[0].length = endblock;
					rl[1].vcn = rl[0].vcn + rl[0].length;
					rl[1].lcn = LCN_HOLE;
					rl = ++(*prl);
				}
			}
		} else {
			if (rl[1].length) {
				runlist_element *xrl;
				int n;

				/*
				 * split into two parts and use the
				 * last one
				 */
				if (!na->unused_runs) {
ntfs_log_error("No free run, case 4\n");
				}
				na->unused_runs--;
				xrl = rl;
				n = 0;
				while (xrl->length) {
					xrl++;
					n++;
				}
				do {
					xrl[1] = *xrl;
					xrl--;
				} while (xrl != rl);
			} else {
				rl[2].lcn = rl[1].lcn;
				rl[2].vcn = rl[1].vcn;
				rl[2].length = rl[1].length;
			}
			rl[1].vcn -= na->compression_block_clusters;
			rl[1].lcn = LCN_HOLE;
			rl[1].length = na->compression_block_clusters;
			rl[0].length -= na->compression_block_clusters;
			if (pos >= (rl[1].vcn << cluster_size_bits)) {
				rl = ++(*prl);
			}
		}
	NAttrSetRunlistDirty(na);
	if ((*update_from == -1) || ((*prl)->vcn < *update_from))
		*update_from = (*prl)->vcn;
	}
	return (compressed_part);
}


static s64 ntfs_attr_pread_i(ntfs_attr *na, const s64 pos, s64 count, void *b)
{
	s64 br, to_read, ofs, total, total2, max_read, max_init;
	ntfs_volume *vol;
	runlist_element *rl;
	u16 efs_padding_length;

	/* Sanity checking arguments is done in ntfs_attr_pread(). */

	if ((na->data_flags & ATTR_COMPRESSION_MASK) && NAttrNonResident(na)) {
		if ((na->data_flags & ATTR_COMPRESSION_MASK)
		    == ATTR_IS_COMPRESSED)
			return ntfs_compressed_attr_pread(na, pos, count, b);
		else {
				/* compression mode not supported */
			errno = EOPNOTSUPP;
			return -1;
		}
	}
	/*
	 * Encrypted non-resident attributes are not supported.  We return
	 * access denied, which is what Windows NT4 does, too.
	 * However, allow if mounted with efs_raw option
	 */
	vol = na->ni->vol;
	if (!vol->efs_raw && NAttrEncrypted(na) && NAttrNonResident(na)) {
		errno = EACCES;
		return -1;
	}

	if (!count)
		return 0;
		/*
		 * Truncate reads beyond end of attribute,
		 * but round to next 512 byte boundary for encrypted
		 * attributes with efs_raw mount option
		 */
	max_read = na->data_size;
	max_init = na->initialized_size;
	if (na->ni->vol->efs_raw
	    && (na->data_flags & ATTR_IS_ENCRYPTED)
	    && NAttrNonResident(na)) {
		if (na->data_size != na->initialized_size) {
			ntfs_log_error("uninitialized encrypted file not supported\n");
			errno = EINVAL;
			return -1;
		}
		max_init = max_read = ((na->data_size + 511) & ~511) + 2;
	}
	if (pos + count > max_read) {
		if (pos >= max_read)
			return 0;
		count = max_read - pos;
	}
	/* If it is a resident attribute, get the value from the mft record. */
	if (!NAttrNonResident(na)) {
		ntfs_attr_search_ctx *ctx;
		char *val;

		ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
		if (!ctx)
			return -1;
		if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
				0, NULL, 0, ctx)) {
res_err_out:
			ntfs_attr_put_search_ctx(ctx);
			return -1;
		}
		val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset);
		if (val < (char*)ctx->attr || val +
				le32_to_cpu(ctx->attr->value_length) >
				(char*)ctx->mrec + vol->mft_record_size) {
			errno = EIO;
			ntfs_log_perror("%s: Sanity check failed", __FUNCTION__);
			goto res_err_out;
		}
		memcpy(b, val + pos, count);
		ntfs_attr_put_search_ctx(ctx);
		return count;
	}
	total = total2 = 0;
	/* Zero out reads beyond initialized size. */
	if (pos + count > max_init) {
		if (pos >= max_init) {
			memset(b, 0, count);
			return count;
		}
		total2 = pos + count - max_init;
		count -= total2;
		memset((u8*)b + count, 0, total2);
	}
		/*
		 * for encrypted non-resident attributes with efs_raw set
		 * the last two bytes aren't read from disk but contain
		 * the number of padding bytes so original size can be
		 * restored
		 */
	if (na->ni->vol->efs_raw &&
			(na->data_flags & ATTR_IS_ENCRYPTED) &&
			((pos + count) > max_init-2)) {
		efs_padding_length = 511 - ((na->data_size - 1) & 511);
		if (pos+count == max_init) {
			if (count == 1) {
				*((u8*)b+count-1) = (u8)(efs_padding_length >> 8);
				count--;
				total2++;
			} else {
				*(le16*)((u8*)b+count-2) = cpu_to_le16(efs_padding_length);
				count -= 2;
				total2 +=2;
			}
		} else {
			*((u8*)b+count-1) = (u8)(efs_padding_length & 0xff);
			count--;
			total2++;
		}
	}

	/* Find the runlist element containing the vcn. */
	rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits);
	if (!rl) {
		/*
		 * If the vcn is not present it is an out of bounds read.
		 * However, we already truncated the read to the data_size,
		 * so getting this here is an error.
		 */
		if (errno == ENOENT) {
			errno = EIO;
			ntfs_log_perror("%s: Failed to find VCN #1", __FUNCTION__);
		}
		return -1;
	}
	/*
	 * Gather the requested data into the linear destination buffer. Note,
	 * a partial final vcn is taken care of by the @count capping of read
	 * length.
	 */
	ofs = pos - (rl->vcn << vol->cluster_size_bits);
	for (; count; rl++, ofs = 0) {
		if (rl->lcn == LCN_RL_NOT_MAPPED) {
			rl = ntfs_attr_find_vcn(na, rl->vcn);
			if (!rl) {
				if (errno == ENOENT) {
					errno = EIO;
					ntfs_log_perror("%s: Failed to find VCN #2",
							__FUNCTION__);
				}
				goto rl_err_out;
			}
			/* Needed for case when runs merged. */
			ofs = pos + total - (rl->vcn << vol->cluster_size_bits);
		}
		if (!rl->length) {
			errno = EIO;
			ntfs_log_perror("%s: Zero run length", __FUNCTION__);
			goto rl_err_out;
		}
		if (rl->lcn < (LCN)0) {
			if (rl->lcn != (LCN)LCN_HOLE) {
				ntfs_log_perror("%s: Bad run (%lld)",
						__FUNCTION__,
						(long long)rl->lcn);
				goto rl_err_out;
			}
			/* It is a hole, just zero the matching @b range. */
			to_read = min(count, (rl->length <<
					vol->cluster_size_bits) - ofs);
			memset(b, 0, to_read);
			/* Update progress counters. */
			total += to_read;
			count -= to_read;
			b = (u8*)b + to_read;
			continue;
		}
		/* It is a real lcn, read it into @dst. */
		to_read = min(count, (rl->length << vol->cluster_size_bits) -
				ofs);
retry:
		ntfs_log_trace("Reading %lld bytes from vcn %lld, lcn %lld, ofs"
				" %lld.\n", (long long)to_read, (long long)rl->vcn,
			       (long long )rl->lcn, (long long)ofs);
		br = ntfs_pread(vol->dev, (rl->lcn << vol->cluster_size_bits) +
				ofs, to_read, b);
		/* If everything ok, update progress counters and continue. */
		if (br > 0) {
			total += br;
			count -= br;
			b = (u8*)b + br;
		}
		if (br == to_read)
			continue;
		/* If the syscall was interrupted, try again. */
		if (br == (s64)-1 && errno == EINTR)
			goto retry;
		if (total)
			return total;
		if (!br)
			errno = EIO;
		ntfs_log_perror("%s: ntfs_pread failed", __FUNCTION__);
		return -1;
	}
	/* Finally, return the number of bytes read. */
	return total + total2;
rl_err_out:
	if (total)
		return total;
	errno = EIO;
	return -1;
}


static int NAttrFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
{
	if (na->type == AT_DATA && na->name == AT_UNNAMED)
		return (na->ni->flags & flag);
	return 0;
}


static int ntfs_attr_fill_zero(ntfs_attr *na, s64 pos, s64 count)
{
	char *buf;
	s64 written, size, end = pos + count;
	s64 ofsi;
	const runlist_element *rli;
	ntfs_volume *vol;
	int ret = -1;

	ntfs_log_trace("pos %lld, count %lld\n", (long long)pos,
		       (long long)count);

	if (!na || pos < 0 || count < 0) {
		errno = EINVAL;
		goto err_out;
	}

	buf = ntfs_calloc(NTFS_BUF_SIZE);
	if (!buf)
		goto err_out;

	rli = na->rl;
	ofsi = 0;
	vol = na->ni->vol;
	while (pos < end) {
		while (rli->length && (ofsi + (rli->length <<
	                        vol->cluster_size_bits) <= pos)) {
	                ofsi += (rli->length << vol->cluster_size_bits);
			rli++;
		}
		size = min(end - pos, NTFS_BUF_SIZE);
			/*
			 * If the zeroed block is fully within a hole,
			 * we need not write anything, so advance as far
			 * as possible within the hole.
			 */
		if ((rli->lcn == (LCN)LCN_HOLE)
		    && (ofsi <= pos)
		    && (ofsi + (rli->length << vol->cluster_size_bits)
				>= (pos + size))) {
			size = min(end - pos, ofsi - pos
				+ (rli->length << vol->cluster_size_bits));
			pos += size;
		} else {
			written = ntfs_rl_pwrite(vol, rli, ofsi, pos,
							size, buf);
			if (written <= 0) {
				ntfs_log_perror("Failed to zero space");
				goto err_free;
			}
			pos += written;
		}
	}

	ret = 0;
err_free:
	free(buf);
err_out:
	return ret;
}


static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs,
			       runlist_element **rl, VCN *update_from)
{
	s64 to_write;
	s64 need;
	ntfs_volume *vol = na->ni->vol;
	int eo, ret = -1;
	runlist *rlc;
	LCN lcn_seek_from = -1;
	VCN cur_vcn, from_vcn;

	if (na->ni->mft_no == FILE_Bitmap) {
		/*
		 * Filling a hole in the main bitmap implies allocating
		 * clusters, which is likely to imply updating the
		 * bitmap in a cluster being allocated.
		 * Not supported now, could lead to endless recursions.
		 */
		ntfs_log_error("Corrupt $BitMap not fully allocated\n");
		errno = EIO;
		goto err_out;
	}
	to_write = min(count, ((*rl)->length << vol->cluster_size_bits) - *ofs);

	cur_vcn = (*rl)->vcn;
	from_vcn = (*rl)->vcn + (*ofs >> vol->cluster_size_bits);

	ntfs_log_trace("count: %lld, cur_vcn: %lld, from: %lld, to: %lld, ofs: "
		       "%lld\n", (long long)count, (long long)cur_vcn,
		       (long long)from_vcn, (long long)to_write, (long long)*ofs);

	/* Map the runlist to be able to update mapping pairs later. */
#if PARTIAL_RUNLIST_UPDATING
	if (!na->rl) {
		if (ntfs_attr_map_whole_runlist(na))
			goto err_out;
	} else {
		/* make sure the run ahead of hole is mapped */
		if ((*rl)->lcn == LCN_HOLE) {
			if (ntfs_attr_map_partial_runlist(na,
				(cur_vcn ? cur_vcn - 1 : cur_vcn)))
					goto err_out;
		}
	}
#else
	if (ntfs_attr_map_whole_runlist(na))
		goto err_out;
#endif

	/* Restore @*rl, it probably get lost during runlist mapping. */
	*rl = ntfs_attr_find_vcn(na, cur_vcn);
	if (!*rl) {
		ntfs_log_error("Failed to find run after mapping runlist. "
			       "Please report to %s.\n", NTFS_DEV_LIST);
		errno = EIO;
		goto err_out;
	}

	/* Search backwards to find the best lcn to start seek from. */
	rlc = *rl;
	while (rlc->vcn) {
		rlc--;
		if (rlc->lcn >= 0) {
				/*
				 * avoid fragmenting a compressed file
				 * Windows does not do that, and that may
				 * not be desirable for files which can
				 * be updated
				 */
			if (na->data_flags & ATTR_COMPRESSION_MASK)
				lcn_seek_from = rlc->lcn + rlc->length;
			else
				lcn_seek_from = rlc->lcn + (from_vcn - rlc->vcn);
			break;
		}
	}
	if (lcn_seek_from == -1) {
		/* Backwards search failed, search forwards. */
		rlc = *rl;
		while (rlc->length) {
			rlc++;
			if (rlc->lcn >= 0) {
				lcn_seek_from = rlc->lcn - (rlc->vcn - from_vcn);
				if (lcn_seek_from < -1)
					lcn_seek_from = -1;
				break;
			}
		}
	}

	need = ((*ofs + to_write - 1) >> vol->cluster_size_bits)
			 + 1 + (*rl)->vcn - from_vcn;
	if ((na->data_flags & ATTR_COMPRESSION_MASK)
	    && (need < na->compression_block_clusters)) {
		/*
		 * for a compressed file, be sure to allocate the full
		 * compression block, as we may need space to decompress
		 * existing compressed data.
		 * So allocate the space common to compression block
		 * and existing hole.
		 */
		VCN alloc_vcn;

		if ((from_vcn & -na->compression_block_clusters) <= (*rl)->vcn)
			alloc_vcn = (*rl)->vcn;
		else
			alloc_vcn = from_vcn & -na->compression_block_clusters;
		need = (alloc_vcn | (na->compression_block_clusters - 1))
			+ 1 - alloc_vcn;
		if (need > (*rl)->length) {
			ntfs_log_error("Cannot allocate %lld clusters"
					" within a hole of %lld\n",
					(long long)need,
					(long long)(*rl)->length);
			errno = EIO;
			goto err_out;
		}
		rlc = ntfs_cluster_alloc(vol, alloc_vcn, need,
				 lcn_seek_from, DATA_ZONE);
	} else
		rlc = ntfs_cluster_alloc(vol, from_vcn, need,
				 lcn_seek_from, DATA_ZONE);
	if (!rlc)
		goto err_out;
	if (na->data_flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE))
		na->compressed_size += need << vol->cluster_size_bits;

	*rl = ntfs_runlists_merge(na->rl, rlc);
	NAttrSetRunlistDirty(na);
		/*
		 * For a compressed attribute, we must be sure there are two
		 * available entries, so reserve them before it gets too late.
		 */
	if (*rl && (na->data_flags & ATTR_COMPRESSION_MASK)) {
		runlist_element *oldrl = na->rl;
		na->rl = *rl;
		*rl = ntfs_rl_extend(na,*rl,2);
		if (!*rl) na->rl = oldrl; /* restore to original if failed */
	}
	if (!*rl) {
		eo = errno;
		ntfs_log_perror("Failed to merge runlists");
		if (ntfs_cluster_free_from_rl(vol, rlc)) {
			ntfs_log_perror("Failed to free hot clusters. "
					"Please run chkdsk /f");
		}
		errno = eo;
		goto err_out;
	}
	na->unused_runs = 2;
	na->rl = *rl;
	if ((*update_from == -1) || (from_vcn < *update_from))
		*update_from = from_vcn;
	*rl = ntfs_attr_find_vcn(na, cur_vcn);
	if (!*rl) {
		/*
		 * It's definitely a BUG, if we failed to find @cur_vcn, because
		 * we missed it during instantiating of the hole.
		 */
		ntfs_log_error("Failed to find run after hole instantiation. "
			       "Please report to %s.\n", NTFS_DEV_LIST);
		errno = EIO;
		goto err_out;
	}
	/* If leaved part of the hole go to the next run. */
	if ((*rl)->lcn < 0)
		(*rl)++;
	/* Now LCN shoudn't be less than 0. */
	if ((*rl)->lcn < 0) {
		ntfs_log_error("BUG! LCN is lesser than 0. "
			       "Please report to the %s.\n", NTFS_DEV_LIST);
		errno = EIO;
		goto err_out;
	}
	if (*ofs) {
		/* Clear non-sparse region from @cur_vcn to @*ofs. */
		if (ntfs_attr_fill_zero(na, cur_vcn << vol->cluster_size_bits,
					*ofs))
			goto err_out;
	}
	if ((*rl)->vcn < cur_vcn) {
		/*
		 * Clusters that replaced hole are merged with
		 * previous run, so we need to update offset.
		 */
		*ofs += (cur_vcn - (*rl)->vcn) << vol->cluster_size_bits;
	}
	if ((*rl)->vcn > cur_vcn) {
		/*
		 * We left part of the hole, so we need to update offset
		 */
		*ofs -= ((*rl)->vcn - cur_vcn) << vol->cluster_size_bits;
	}

	ret = 0;
err_out:
	return ret;
}


LCN ntfs_attr_vcn_to_lcn(ntfs_attr *na, const VCN vcn)
{
	LCN lcn;
	BOOL is_retry = FALSE;

	if (!na || !NAttrNonResident(na) || vcn < 0)
		return (LCN)LCN_EINVAL;

	ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
			long)na->ni->mft_no, le32_to_cpu(na->type));
retry:
	/* Convert vcn to lcn. If that fails map the runlist and retry once. */
	lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
	if (lcn >= 0)
		return lcn;
	if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) {
		is_retry = TRUE;
		goto retry;
	}
	/*
	 * If the attempt to map the runlist failed, or we are getting
	 * LCN_RL_NOT_MAPPED despite having mapped the attribute extent
	 * successfully, something is really badly wrong...
	 */
	if (!is_retry || lcn == (LCN)LCN_RL_NOT_MAPPED)
		return (LCN)LCN_EIO;
	/* lcn contains the appropriate error code. */
	return lcn;
}


static void NAttrClearFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
{
	if (na->type == AT_DATA && na->name == AT_UNNAMED)
		na->ni->flags &= ~flag;
}