33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6- // spell-checker:ignore (ToDO) fname, algo
6+ // spell-checker:ignore (ToDO) fname, algo, hwcaps, pclmul, vmull, pmull, vpclmulqdq, pclmulqdq, tunables, behaviour
77
88use clap:: builder:: ValueParser ;
99use clap:: { Arg , ArgAction , Command } ;
10+ use std:: collections:: HashSet ;
11+ use std:: env;
1012use std:: ffi:: { OsStr , OsString } ;
1113use std:: fs:: File ;
1214use std:: io:: { BufReader , Read , Write , stdin, stdout} ;
@@ -37,6 +39,7 @@ struct Options {
3739 length : Option < usize > ,
3840 output_format : OutputFormat ,
3941 line_ending : LineEnding ,
42+ debug : bool ,
4043}
4144
4245/// Reading mode used to compute digest.
@@ -188,6 +191,8 @@ where
188191{
189192 let mut files = files. peekable ( ) ;
190193
194+ emit_crc_debug_info ( & options) ;
195+
191196 while let Some ( filename) = files. next ( ) {
192197 // Check that in raw mode, we are not provided with several files.
193198 if options. output_format . is_raw ( ) && files. peek ( ) . is_some ( ) {
@@ -268,6 +273,160 @@ where
268273 Ok ( ( ) )
269274}
270275
276+ fn log_debug_message ( feature : & str , status : Option < bool > ) {
277+ match status {
278+ Some ( true ) => eprintln ! ( "cksum: using {feature} hardware support" ) ,
279+ Some ( false ) => eprintln ! ( "cksum: {feature} support not detected" ) ,
280+ None => { }
281+ }
282+ }
283+
284+ /// Emit GNU-compatible hardware selection messages for `cksum --debug`.
285+ ///
286+ /// GNU cksum prints which SIMD/SW implementation it picked whenever `--debug` is present.
287+ /// The upstream tests rely on that output to ensure every hardware-specific path can be
288+ /// exercised (including cases where GLIBC_TUNABLES forces a feature off). We mirror that
289+ /// behaviour here by checking both `std::arch` feature flags and the `glibc.cpu.hwcaps`
290+ /// overrides, but only for the legacy CRC algorithms where multiple implementations exist.
291+ fn emit_crc_debug_info ( options : & Options ) {
292+ if !options. debug {
293+ return ;
294+ }
295+
296+ if options. algo_name != ALGORITHM_OPTIONS_CRC && options. algo_name != ALGORITHM_OPTIONS_CRC32B {
297+ return ;
298+ }
299+
300+ let disabled_caps = glibc_disabled_hwcaps ( ) ;
301+
302+ log_debug_message ( "avx512" , detect_avx512 ( & disabled_caps) ) ;
303+ log_debug_message ( "avx2" , detect_avx2 ( & disabled_caps) ) ;
304+ log_debug_message ( "pclmul" , detect_pclmul ( & disabled_caps) ) ;
305+
306+ if options. algo_name == ALGORITHM_OPTIONS_CRC {
307+ log_debug_message ( "vmull" , detect_vmull ( & disabled_caps) ) ;
308+ }
309+ }
310+
311+ fn detect_avx512 ( disabled_caps : & HashSet < String > ) -> Option < bool > {
312+ #[ cfg( any( target_arch = "x86" , target_arch = "x86_64" ) ) ]
313+ {
314+ if disabled_caps. contains ( "AVX512F" )
315+ || disabled_caps. contains ( "AVX512BW" )
316+ || disabled_caps. contains ( "VPCLMULQDQ" )
317+ {
318+ return Some ( false ) ;
319+ }
320+
321+ Some (
322+ std:: arch:: is_x86_feature_detected!( "avx512f" )
323+ && std:: arch:: is_x86_feature_detected!( "avx512bw" )
324+ && std:: arch:: is_x86_feature_detected!( "vpclmulqdq" ) ,
325+ )
326+ }
327+
328+ #[ cfg( not( any( target_arch = "x86" , target_arch = "x86_64" ) ) ) ]
329+ {
330+ let _ = disabled_caps;
331+ None
332+ }
333+ }
334+
335+ fn detect_avx2 ( disabled_caps : & HashSet < String > ) -> Option < bool > {
336+ #[ cfg( any( target_arch = "x86" , target_arch = "x86_64" ) ) ]
337+ {
338+ if disabled_caps. contains ( "AVX2" ) || disabled_caps. contains ( "VPCLMULQDQ" ) {
339+ return Some ( false ) ;
340+ }
341+
342+ Some (
343+ std:: arch:: is_x86_feature_detected!( "avx2" )
344+ && std:: arch:: is_x86_feature_detected!( "vpclmulqdq" ) ,
345+ )
346+ }
347+
348+ #[ cfg( not( any( target_arch = "x86" , target_arch = "x86_64" ) ) ) ]
349+ {
350+ let _ = disabled_caps;
351+ None
352+ }
353+ }
354+
355+ fn detect_pclmul ( disabled_caps : & HashSet < String > ) -> Option < bool > {
356+ #[ cfg( any( target_arch = "x86" , target_arch = "x86_64" ) ) ]
357+ {
358+ if disabled_caps. contains ( "AVX" )
359+ || disabled_caps. contains ( "PCLMUL" )
360+ || disabled_caps. contains ( "PCLMULQDQ" )
361+ {
362+ return Some ( false ) ;
363+ }
364+
365+ Some (
366+ std:: arch:: is_x86_feature_detected!( "avx" )
367+ && std:: arch:: is_x86_feature_detected!( "pclmulqdq" ) ,
368+ )
369+ }
370+
371+ #[ cfg( not( any( target_arch = "x86" , target_arch = "x86_64" ) ) ) ]
372+ {
373+ let _ = disabled_caps;
374+ None
375+ }
376+ }
377+
378+ fn detect_vmull ( disabled_caps : & HashSet < String > ) -> Option < bool > {
379+ #[ cfg( target_arch = "aarch64" ) ]
380+ {
381+ if disabled_caps. contains ( "PMULL" ) {
382+ return Some ( false ) ;
383+ }
384+
385+ Some ( std:: arch:: is_aarch64_feature_detected!( "pmull" ) )
386+ }
387+
388+ #[ cfg( not( target_arch = "aarch64" ) ) ]
389+ {
390+ let _ = disabled_caps;
391+ None
392+ }
393+ }
394+
395+ fn glibc_disabled_hwcaps ( ) -> HashSet < String > {
396+ env:: var ( "GLIBC_TUNABLES" )
397+ . ok ( )
398+ . map ( |value| parse_disabled_hwcaps ( & value) )
399+ . unwrap_or_default ( )
400+ }
401+
402+ fn parse_disabled_hwcaps ( tunables : & str ) -> HashSet < String > {
403+ let mut disabled = HashSet :: new ( ) ;
404+
405+ for entry in tunables. split ( ':' ) {
406+ let trimmed = entry. trim ( ) ;
407+ let Some ( value) = trimmed. strip_prefix ( "glibc.cpu.hwcaps=" ) else {
408+ continue ;
409+ } ;
410+
411+ for token in value. split ( ',' ) {
412+ let token = token. trim ( ) ;
413+ if token. is_empty ( ) {
414+ continue ;
415+ }
416+
417+ if let Some ( feature) = token. strip_prefix ( '-' ) {
418+ let feature = feature. trim ( ) ;
419+
420+ if !feature. is_empty ( ) {
421+ disabled. insert ( feature. to_ascii_uppercase ( ) ) ;
422+ }
423+ }
424+ }
425+ }
426+
427+ disabled
428+ }
429+
271430mod options {
272431 pub const ALGORITHM : & str = "algorithm" ;
273432 pub const FILE : & str = "file" ;
@@ -285,6 +444,7 @@ mod options {
285444 pub const IGNORE_MISSING : & str = "ignore-missing" ;
286445 pub const QUIET : & str = "quiet" ;
287446 pub const ZERO : & str = "zero" ;
447+ pub const DEBUG : & str = "debug" ;
288448}
289449
290450/// cksum has a bunch of legacy behavior. We handle this in this function to
@@ -395,6 +555,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
395555 let matches = uucore:: clap_localization:: handle_clap_result ( uu_app ( ) , args) ?;
396556
397557 let check = matches. get_flag ( options:: CHECK ) ;
558+ let debug_flag = matches. get_flag ( options:: DEBUG ) ;
398559
399560 let algo_cli = matches
400561 . get_one :: < String > ( options:: ALGORITHM )
@@ -470,6 +631,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
470631 length,
471632 output_format,
472633 line_ending,
634+ debug : debug_flag,
473635 } ;
474636
475637 cksum ( opts, files) ?;
@@ -549,6 +711,12 @@ pub fn uu_app() -> Command {
549711 // GNU cksum does not permit these flags to be combined:
550712 . conflicts_with ( options:: RAW ) ,
551713 )
714+ . arg (
715+ Arg :: new ( options:: DEBUG )
716+ . long ( options:: DEBUG )
717+ . help ( translate ! ( "cksum-help-debug" ) )
718+ . action ( ArgAction :: SetTrue ) ,
719+ )
552720 . arg (
553721 Arg :: new ( options:: TEXT )
554722 . long ( options:: TEXT )
@@ -602,3 +770,24 @@ pub fn uu_app() -> Command {
602770 )
603771 . after_help ( translate ! ( "cksum-after-help" ) )
604772}
773+
774+ #[ cfg( test) ]
775+ mod tests {
776+ use super :: parse_disabled_hwcaps;
777+
778+ #[ test]
779+ fn parse_disabled_hwcaps_picks_up_multiple_features ( ) {
780+ let caps = parse_disabled_hwcaps ( "glibc.cpu.hwcaps=-AVX2,-PMULL," ) ;
781+ assert ! ( caps. contains( "AVX2" ) ) ;
782+ assert ! ( caps. contains( "PMULL" ) ) ;
783+ }
784+
785+ #[ test]
786+ fn parse_disabled_hwcaps_ignores_other_entries ( ) {
787+ let caps =
788+ parse_disabled_hwcaps ( "glibc.malloc.trim=0:glibc.cpu.hwcaps=-AVX512F,-VPCLMULQDQ" ) ;
789+ assert ! ( caps. contains( "AVX512F" ) ) ;
790+ assert ! ( caps. contains( "VPCLMULQDQ" ) ) ;
791+ assert ! ( !caps. contains( "MALLOC" ) ) ;
792+ }
793+ }
0 commit comments