diff --git a/.gitattributes b/.gitattributes
index d38f610584384e83a33b0132ecfc06fc328c3ef1..0ee787125c421364f9a8f980c6afa09d0f53b133 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -799,7 +799,6 @@ testfont binary
 /src/test_co.pike foreign_ident
 /src/time_stuff.h foreign_ident
 /src/tmodule.c foreign_ident
-/src/treeopt.in foreign_ident
 /src/uncompressor.c foreign_ident
 /src/version.c foreign_ident
 /tools/pike.el foreign_ident
diff --git a/src/sscanf.c b/src/sscanf.c
index 4479aa49fe88867a6d201a384634096e1c8c1d13..4e10ee8f79dcad1e4232d540f2e8d8d187a5931b 100644
--- a/src/sscanf.c
+++ b/src/sscanf.c
@@ -611,8 +611,8 @@ static INT32 PIKE_CONCAT4(very_low_sscanf_,INPUT_SHIFT,_,MATCH_SHIFT)(	 \
 {									 \
   struct svalue sval;							 \
   INT32 matches, arg;							 \
-  ptrdiff_t cnt, eye, e, field_length = 0;				 \
-  int no_assign = 0, minus_flag = 0, plus_flag = 0;			 \
+  ptrdiff_t cnt, eye, start_eye, e, field_length = 0, truncated = 0;	 \
+  int no_assign = 0, minus_flag = 0, plus_flag = 0, truncate = 0;	 \
   struct sscanf_set set;						 \
 									 \
 									 \
@@ -658,6 +658,8 @@ static INT32 PIKE_CONCAT4(very_low_sscanf_,INPUT_SHIFT,_,MATCH_SHIFT)(	 \
     field_length=-1;							 \
     minus_flag=0;							 \
     plus_flag=0;							 \
+    truncate=0;								 \
+    start_eye = eye;							 \
 									 \
     cnt++;								 \
     if(cnt>=match_len)							 \
@@ -694,6 +696,11 @@ static INT32 PIKE_CONCAT4(very_low_sscanf_,INPUT_SHIFT,_,MATCH_SHIFT)(	 \
 	  cnt++;							 \
 	  continue;							 \
 									 \
+        case '!':							 \
+	  truncate=1;							 \
+	  cnt++;							 \
+	  continue;							 \
+									 \
 	case '{':							 \
 	{								 \
 	  ONERROR err;							 \
@@ -1270,7 +1277,7 @@ INPUT_IS_WIDE(								 \
 	case 'n':							 \
 	  sval.type=T_INT;						 \
 	  sval.subtype=NUMBER_NUMBER;					 \
-	  sval.u.integer=TO_INT32(eye);					 \
+	  sval.u.integer=TO_INT32(eye - truncated);			 \
 	  break;							 \
 									 \
 	default:							 \
@@ -1280,6 +1287,9 @@ INPUT_IS_WIDE(								 \
       break;								 \
     }									 \
     matches++;								 \
+    if (truncate) {							 \
+      truncated += eye - start_eye;					 \
+    }									 \
 									 \
     if(no_assign)							 \
     {									 \
@@ -1456,6 +1466,8 @@ INT32 low_sscanf(struct pike_string *data, struct pike_string *format, INT32 fla
  *!     @expr{-28@}.
  *!   @value "%n"
  *!     Returns the current character offset in @[data].
+ *!     Note that any characters matching fields scanned with the
+ *!     @expr{"!"@}-modifier are removed from the count (see below).
  *!   @value "%f"
  *!     Reads a float ("0101" makes 101.0).
  *!   @value "%F"
@@ -1530,6 +1542,9 @@ INT32 low_sscanf(struct pike_string *data, struct pike_string *format, INT32 fla
  *!     Interpret the data as a signed entity. In other words,
  *!     @expr{"%+1c"@} will read @expr{"\xFF"@} as @expr{-1@} instead
  *!     of @expr{255@}, as @expr{"%1c"@} would have.
+ *!   @value "!"
+ *!     Ignore the matched characters with respect to any following
+ *!     @expr{"%n"@}.
  *! @endstring
  *!
  *! @note
@@ -1669,6 +1684,7 @@ static void push_sscanf_argument_types(PCHARP format, ptrdiff_t format_len,
 
       case '-':
       case '+':
+      case '!':
       case '0': case '1': case '2': case '3': case '4':
       case '5': case '6': case '7': case '8': case '9':
 	cnt++;
diff --git a/src/testsuite.in b/src/testsuite.in
index a7c955d692f062d75e94cc040cf9dee9afa18f17..286cf916fdd87cd01076f70ac8e9034803ded6b8 100644
--- a/src/testsuite.in
+++ b/src/testsuite.in
@@ -7704,6 +7704,7 @@ test_equal([[ array_sscanf("\51726\30212\66610\30131", "%*[ \t]%s")[0] ]],
 
 test_equal([[ array_sscanf("hej","%s") ]], [[ ({ "hej" }) ]])
 test_equal([[ array_sscanf("hej","%s%n") ]], [[ ({ "hej", 3 }) ]])
+test_equal([[ array_sscanf("hejhopp", "%*!3s%s%n") ]], [[ ({ "hopp", 4 }) ]])
 test_eval_error([[ function f=array_sscanf; f("hej","%s% ") ]])
 
 test_equal([[ array_sscanf("\x304b\x3066\x3044\x308a\x3087\x3046\x308a", "%[^\x3042\x3044\x3046\x3048\x304a]")[0] ]],
diff --git a/src/treeopt.in b/src/treeopt.in
index fd2dd946f4ce02e629f384841f7aead1d9a90cdf..f82bc564827148f40f80de5c2c20121182e381cd 100644
--- a/src/treeopt.in
+++ b/src/treeopt.in
@@ -2,7 +2,7 @@
 // This file is part of Pike. For copyright information see COPYRIGHT.
 // Pike is distributed under GPL, LGPL and MPL. See the file COPYING
 // for more information.
-// $Id: treeopt.in,v 1.101 2010/02/03 20:22:29 grubba Exp $
+// $Id$
 //
 // The tree optimizer
 //
@@ -188,7 +188,7 @@ F_GE(F_APPLY(F_CONSTANT
 //
 //   =>
 //
-// (sscanf(s, "%*" + A + "s" + "xxxx", ...) || 1) - 1
+// (sscanf(s, "%*!" + A + "s" + "xxxx", ...) || 1) - 1
 F_SSCANF(':'(4 = +,
 	     F_ARG_LIST(F_RANGE(0 = +[ pike_types_le($$->type,
 						     string_type_string)],
@@ -199,7 +199,7 @@ F_SSCANF(':'(4 = +,
 {
   struct pike_string *percent_star_string;
   struct pike_string *s_string;
-  MAKE_CONST_STRING(percent_star_string, "%*");
+  MAKE_CONST_STRING(percent_star_string, "%*!");
   MAKE_CONST_STRING(s_string, "s");
   $$ = mkopernode("`-",
 		  mknode(F_LOR,