@@ -101,7 +101,7 @@ pub struct Options {
101101 pub tmpdir : Option < PathBuf > ,
102102
103103 /// The suffix to append to the temporary file, if any.
104- pub suffix : Option < String > ,
104+ pub suffix : Option < OsString > ,
105105
106106 /// Whether to treat the template argument as a single file path component.
107107 pub treat_as_template : bool ,
@@ -150,7 +150,7 @@ impl Options {
150150 dry_run : matches. get_flag ( OPT_DRY_RUN ) ,
151151 quiet : matches. get_flag ( OPT_QUIET ) ,
152152 tmpdir,
153- suffix : matches. get_one :: < String > ( OPT_SUFFIX ) . map ( String :: from ) ,
153+ suffix : matches. get_one :: < OsString > ( OPT_SUFFIX ) . cloned ( ) ,
154154 treat_as_template : matches. get_flag ( OPT_T ) ,
155155 template,
156156 }
@@ -214,27 +214,39 @@ fn find_last_contiguous_block_of_xs(s: &str) -> Option<(usize, usize)> {
214214impl Params {
215215 fn from ( options : Options ) -> Result < Self , MkTempError > {
216216 // Convert OsString template to string for processing
217- let Some ( template_str) = options. template . to_str ( ) else {
218- // For non-UTF-8 templates, return an error
219- return Err ( MkTempError :: InvalidTemplate ( options. template ) ) ;
217+ // When using -t flag, be permissive with invalid UTF-8 like GNU mktemp
218+ // Otherwise, maintain strict UTF-8 validation (existing behavior)
219+ let template_str = if options. treat_as_template {
220+ // For -t templates, use lossy conversion for GNU compatibility
221+ options. template . to_string_lossy ( ) . into_owned ( )
222+ } else {
223+ // For regular templates, maintain strict validation
224+ match options. template . to_str ( ) {
225+ Some ( s) => s. to_string ( ) ,
226+ None => {
227+ return Err ( MkTempError :: InvalidTemplate (
228+ "template contains invalid UTF-8" . into ( ) ,
229+ ) ) ;
230+ }
231+ }
220232 } ;
221233
222234 // The template argument must end in 'X' if a suffix option is given.
223235 if options. suffix . is_some ( ) && !template_str. ends_with ( 'X' ) {
224- return Err ( MkTempError :: MustEndInX ( template_str. to_string ( ) ) ) ;
236+ return Err ( MkTempError :: MustEndInX ( template_str. clone ( ) ) ) ;
225237 }
226238
227239 // Get the start and end indices of the randomized part of the template.
228240 //
229241 // For example, if the template is "abcXXXXyz", then `i` is 3 and `j` is 7.
230- let Some ( ( i, j) ) = find_last_contiguous_block_of_xs ( template_str) else {
242+ let Some ( ( i, j) ) = find_last_contiguous_block_of_xs ( & template_str) else {
231243 let s = match options. suffix {
232244 // If a suffix is specified, the error message includes the template without the suffix.
233245 Some ( _) => template_str
234246 . chars ( )
235247 . take ( template_str. len ( ) )
236248 . collect :: < String > ( ) ,
237- None => template_str. to_string ( ) ,
249+ None => template_str. clone ( ) ,
238250 } ;
239251 return Err ( MkTempError :: TooFewXs ( s) ) ;
240252 } ;
@@ -249,11 +261,11 @@ impl Params {
249261 let prefix_path = Path :: new ( & prefix_from_option) . join ( prefix_from_template) ;
250262 if options. treat_as_template && prefix_from_template. contains ( MAIN_SEPARATOR ) {
251263 return Err ( MkTempError :: PrefixContainsDirSeparator (
252- template_str. to_string ( ) ,
264+ template_str. clone ( ) ,
253265 ) ) ;
254266 }
255267 if tmpdir. is_some ( ) && Path :: new ( prefix_from_template) . is_absolute ( ) {
256- return Err ( MkTempError :: InvalidTemplate ( template_str. into ( ) ) ) ;
268+ return Err ( MkTempError :: InvalidTemplate ( template_str. clone ( ) . into ( ) ) ) ;
257269 }
258270
259271 // Split the parent directory from the file part of the prefix.
@@ -271,7 +283,7 @@ impl Params {
271283 } ;
272284 let prefix = match prefix_path. file_name ( ) {
273285 None => String :: new ( ) ,
274- Some ( f) => f. to_str ( ) . unwrap ( ) . to_string ( ) ,
286+ Some ( f) => f. to_string_lossy ( ) . to_string ( ) ,
275287 } ;
276288 ( directory, prefix)
277289 }
@@ -281,7 +293,10 @@ impl Params {
281293 //
282294 // For example, if the suffix command-line argument is ".txt" and
283295 // the template is "XXXabc", then `suffix` is "abc.txt".
284- let suffix_from_option = options. suffix . unwrap_or_default ( ) ;
296+ let suffix_from_option = options
297+ . suffix
298+ . map ( |s| s. to_string_lossy ( ) . to_string ( ) )
299+ . unwrap_or_default ( ) ;
285300 let suffix_from_template = & template_str[ j..] ;
286301 let suffix = format ! ( "{suffix_from_template}{suffix_from_option}" ) ;
287302 if suffix. contains ( MAIN_SEPARATOR ) {
@@ -445,7 +460,8 @@ pub fn uu_app() -> Command {
445460 Arg :: new ( OPT_SUFFIX )
446461 . long ( OPT_SUFFIX )
447462 . help ( translate ! ( "mktemp-help-suffix" ) )
448- . value_name ( "SUFFIX" ) ,
463+ . value_name ( "SUFFIX" )
464+ . value_parser ( clap:: value_parser!( OsString ) ) ,
449465 )
450466 . arg (
451467 Arg :: new ( OPT_P )
0 commit comments