1 /* valsort.c - sort attribute values */
2 /* $OpenLDAP: pkg/ldap/servers/slapd/overlays/valsort.c,v 1.17.2.5 2008/02/11 23:26:49 kurt Exp $ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 * Copyright 2005-2008 The OpenLDAP Foundation.
6 * Portions copyright 2005 Symas Corporation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
18 * This work was initially developed by Howard Chu for inclusion in
19 * OpenLDAP Software. This work was sponsored by Stanford University.
23 * This overlay sorts the values of multi-valued attributes when returning
24 * them in a search response.
28 #ifdef SLAPD_OVER_VALSORT
32 #include <ac/string.h>
39 #define VALSORT_ASCEND 0
40 #define VALSORT_DESCEND 1
42 #define VALSORT_ALPHA 2
43 #define VALSORT_NUMERIC 4
45 #define VALSORT_WEIGHTED 8
47 typedef struct valsort_info
{
48 struct valsort_info
*vi_next
;
50 AttributeDescription
*vi_ad
;
54 static int valsort_cid
;
56 static ConfigDriver valsort_cf_func
;
58 static ConfigTable valsort_cfats
[] = {
59 { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC
,
60 valsort_cf_func
, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' "
61 "DESC 'Sorting rule for attribute under given DN' "
62 "EQUALITY caseIgnoreMatch "
63 "SYNTAX OMsDirectoryString )", NULL
, NULL
},
67 static ConfigOCs valsort_cfocs
[] = {
69 "NAME 'olcValSortConfig' "
70 "DESC 'Value Sorting configuration' "
71 "SUP olcOverlayConfig "
72 "MUST olcValSortAttr )",
73 Cft_Overlay
, valsort_cfats
},
77 static slap_verbmasks sorts
[] = {
78 { BER_BVC("alpha-ascend"), VALSORT_ASCEND
|VALSORT_ALPHA
},
79 { BER_BVC("alpha-descend"), VALSORT_DESCEND
|VALSORT_ALPHA
},
80 { BER_BVC("numeric-ascend"), VALSORT_ASCEND
|VALSORT_NUMERIC
},
81 { BER_BVC("numeric-descend"), VALSORT_DESCEND
|VALSORT_NUMERIC
},
82 { BER_BVC("weighted"), VALSORT_WEIGHTED
},
86 static Syntax
*syn_numericString
;
89 valsort_cf_func(ConfigArgs
*c
) {
90 slap_overinst
*on
= (slap_overinst
*)c
->bi
;
91 valsort_info vitmp
, *vi
;
92 const char *text
= NULL
;
94 struct berval bv
= BER_BVNULL
;
96 if ( c
->op
== SLAP_CONFIG_EMIT
) {
97 for ( vi
= on
->on_bi
.bi_private
; vi
; vi
= vi
->vi_next
) {
98 struct berval bv2
= BER_BVNULL
, bvret
;
102 len
= vi
->vi_ad
->ad_cname
.bv_len
+ 1 + vi
->vi_dn
.bv_len
+ 2;
104 if ( i
& VALSORT_WEIGHTED
) {
105 enum_to_verb( sorts
, VALSORT_WEIGHTED
, &bv2
);
106 len
+= bv2
.bv_len
+ 1;
107 i
^= VALSORT_WEIGHTED
;
110 enum_to_verb( sorts
, i
, &bv
);
111 len
+= bv
.bv_len
+ 1;
113 bvret
.bv_val
= ch_malloc( len
+1 );
116 ptr
= lutil_strcopy( bvret
.bv_val
, vi
->vi_ad
->ad_cname
.bv_val
);
119 ptr
= lutil_strcopy( ptr
, vi
->vi_dn
.bv_val
);
121 if ( vi
->vi_sort
& VALSORT_WEIGHTED
) {
123 ptr
= lutil_strcopy( ptr
, bv2
.bv_val
);
127 strcpy( ptr
, bv
.bv_val
);
129 ber_bvarray_add( &c
->rvalue_vals
, &bvret
);
131 i
= ( c
->rvalue_vals
!= NULL
) ? 0 : 1;
133 } else if ( c
->op
== LDAP_MOD_DELETE
) {
135 for ( vi
= on
->on_bi
.bi_private
; vi
; vi
= on
->on_bi
.bi_private
) {
136 on
->on_bi
.bi_private
= vi
->vi_next
;
137 ch_free( vi
->vi_dn
.bv_val
);
143 for (i
=0, prev
= (valsort_info
**)&on
->on_bi
.bi_private
,
144 vi
= *prev
; vi
&& i
<c
->valx
;
145 prev
= &vi
->vi_next
, vi
= vi
->vi_next
, i
++ );
146 (*prev
)->vi_next
= vi
->vi_next
;
147 ch_free( vi
->vi_dn
.bv_val
);
153 i
= slap_str2ad( c
->argv
[1], &vitmp
.vi_ad
, &text
);
155 snprintf( c
->cr_msg
, sizeof( c
->cr_msg
), "<%s> %s", c
->argv
[0], text
);
156 Debug( LDAP_DEBUG_ANY
, "%s: %s (%s)!\n",
157 c
->log
, c
->cr_msg
, c
->argv
[1] );
160 if ( is_at_single_value( vitmp
.vi_ad
->ad_type
)) {
161 snprintf( c
->cr_msg
, sizeof( c
->cr_msg
), "<%s> %s is single-valued, ignoring", c
->argv
[0],
162 vitmp
.vi_ad
->ad_cname
.bv_val
);
163 Debug( LDAP_DEBUG_ANY
, "%s: %s (%s)!\n",
164 c
->log
, c
->cr_msg
, c
->argv
[1] );
167 is_numeric
= ( vitmp
.vi_ad
->ad_type
->sat_syntax
== syn_numericString
||
168 vitmp
.vi_ad
->ad_type
->sat_syntax
== slap_schema
.si_syn_integer
) ? 1
170 ber_str2bv( c
->argv
[2], 0, 0, &bv
);
171 i
= dnNormalize( 0, NULL
, NULL
, &bv
, &vitmp
.vi_dn
, NULL
);
173 snprintf( c
->cr_msg
, sizeof( c
->cr_msg
), "<%s> unable to normalize DN", c
->argv
[0] );
174 Debug( LDAP_DEBUG_ANY
, "%s: %s (%s)!\n",
175 c
->log
, c
->cr_msg
, c
->argv
[2] );
178 i
= verb_to_mask( c
->argv
[3], sorts
);
179 if ( BER_BVISNULL( &sorts
[i
].word
)) {
180 snprintf( c
->cr_msg
, sizeof( c
->cr_msg
), "<%s> unrecognized sort type", c
->argv
[0] );
181 Debug( LDAP_DEBUG_ANY
, "%s: %s (%s)!\n",
182 c
->log
, c
->cr_msg
, c
->argv
[3] );
185 vitmp
.vi_sort
= sorts
[i
].mask
;
186 if ( sorts
[i
].mask
== VALSORT_WEIGHTED
&& c
->argc
== 5 ) {
187 i
= verb_to_mask( c
->argv
[4], sorts
);
188 if ( BER_BVISNULL( &sorts
[i
].word
)) {
189 snprintf( c
->cr_msg
, sizeof( c
->cr_msg
), "<%s> unrecognized sort type", c
->argv
[0] );
190 Debug( LDAP_DEBUG_ANY
, "%s: %s (%s)!\n",
191 c
->log
, c
->cr_msg
, c
->argv
[4] );
194 vitmp
.vi_sort
|= sorts
[i
].mask
;
196 if (( vitmp
.vi_sort
& VALSORT_NUMERIC
) && !is_numeric
) {
197 snprintf( c
->cr_msg
, sizeof( c
->cr_msg
), "<%s> numeric sort specified for non-numeric syntax",
199 Debug( LDAP_DEBUG_ANY
, "%s: %s (%s)!\n",
200 c
->log
, c
->cr_msg
, c
->argv
[1] );
203 vi
= ch_malloc( sizeof(valsort_info
) );
205 vi
->vi_next
= on
->on_bi
.bi_private
;
206 on
->on_bi
.bi_private
= vi
;
210 /* Use Insertion Sort algorithm on selected values */
212 do_sort( Operation
*op
, Attribute
*a
, int beg
, int num
, slap_mask_t sort
)
215 struct berval tmp
, ntmp
, *vals
= NULL
, *nvals
;
217 gotnvals
= (a
->a_vals
!= a
->a_nvals
);
219 nvals
= a
->a_nvals
+ beg
;
221 vals
= a
->a_vals
+ beg
;
223 if ( sort
& VALSORT_NUMERIC
) {
224 long *numbers
= op
->o_tmpalloc( num
* sizeof(long), op
->o_tmpmemctx
),
226 for (i
=0; i
<num
; i
++)
227 numbers
[i
] = strtol( nvals
[i
].bv_val
, NULL
, 0 );
229 for (i
=1; i
<num
; i
++) {
232 if ( gotnvals
) tmp
= vals
[i
];
235 int cmp
= (sort
& VALSORT_DESCEND
) ? numbers
[j
-1] < idx
:
238 numbers
[j
] = numbers
[j
-1];
239 nvals
[j
] = nvals
[j
-1];
240 if ( gotnvals
) vals
[j
] = vals
[j
-1];
245 if ( gotnvals
) vals
[j
] = tmp
;
247 op
->o_tmpfree( numbers
, op
->o_tmpmemctx
);
249 for (i
=1; i
<num
; i
++) {
251 if ( gotnvals
) tmp
= vals
[i
];
254 int cmp
= strcmp( nvals
[j
-1].bv_val
, ntmp
.bv_val
);
255 cmp
= (sort
& VALSORT_DESCEND
) ? (cmp
< 0) : (cmp
> 0);
258 nvals
[j
] = nvals
[j
-1];
259 if ( gotnvals
) vals
[j
] = vals
[j
-1];
263 if ( gotnvals
) vals
[j
] = tmp
;
269 valsort_response( Operation
*op
, SlapReply
*rs
)
275 /* If this is not a search response, or it is a syncrepl response,
276 * or the valsort control wants raw results, pass thru unmodified.
278 if ( rs
->sr_type
!= REP_SEARCH
||
279 ( _SCM(op
->o_sync
) > SLAP_CONTROL_IGNORED
) ||
280 ( op
->o_ctrlflag
[valsort_cid
] & SLAP_CONTROL_DATA0
))
281 return SLAP_CB_CONTINUE
;
283 on
= (slap_overinst
*) op
->o_bd
->bd_info
;
284 vi
= on
->on_bi
.bi_private
;
286 /* And we must have something configured */
287 if ( !vi
) return SLAP_CB_CONTINUE
;
289 /* Find a rule whose baseDN matches this entry */
290 for (; vi
; vi
= vi
->vi_next
) {
293 if ( !dnIsSuffix( &rs
->sr_entry
->e_nname
, &vi
->vi_dn
))
296 /* Find attr that this rule affects */
297 a
= attr_find( rs
->sr_entry
->e_attrs
, vi
->vi_ad
);
300 if (( rs
->sr_flags
& ( REP_ENTRY_MODIFIABLE
|REP_ENTRY_MUSTBEFREED
)) !=
301 ( REP_ENTRY_MODIFIABLE
|REP_ENTRY_MUSTBEFREED
)) {
302 rs
->sr_entry
= entry_dup( rs
->sr_entry
);
303 rs
->sr_flags
|= REP_ENTRY_MODIFIABLE
|REP_ENTRY_MUSTBEFREED
;
304 a
= attr_find( rs
->sr_entry
->e_attrs
, vi
->vi_ad
);
308 if ( vi
->vi_sort
& VALSORT_WEIGHTED
) {
310 long *index
= op
->o_tmpalloc( n
* sizeof(long), op
->o_tmpmemctx
);
312 gotnvals
= (a
->a_vals
!= a
->a_nvals
);
314 for (i
=0; i
<n
; i
++) {
315 char *ptr
= ber_bvchr( &a
->a_nvals
[i
], '{' );
318 Debug(LDAP_DEBUG_TRACE
, "weights missing from attr %s "
319 "in entry %s\n", vi
->vi_ad
->ad_cname
.bv_val
,
320 rs
->sr_entry
->e_name
.bv_val
, 0 );
323 index
[i
] = strtol( ptr
+1, &end
, 0 );
325 Debug(LDAP_DEBUG_TRACE
, "weights misformatted "
327 rs
->sr_entry
->e_name
.bv_val
, 0, 0 );
330 /* Strip out weights */
331 ptr
= a
->a_nvals
[i
].bv_val
;
336 a
->a_nvals
[i
].bv_len
= ptr
- a
->a_nvals
[i
].bv_val
;
338 if ( a
->a_vals
!= a
->a_nvals
) {
339 ptr
= a
->a_vals
[i
].bv_val
;
340 end
= ber_bvchr( &a
->a_vals
[i
], '}' );
341 assert( end
!= NULL
);
346 a
->a_vals
[i
].bv_len
= ptr
- a
->a_vals
[i
].bv_val
;
349 /* An attr was missing weights here, ignore it */
351 op
->o_tmpfree( index
, op
->o_tmpmemctx
);
355 for ( i
=1; i
<n
; i
++) {
357 struct berval tmp
= a
->a_vals
[i
], ntmp
;
358 if ( gotnvals
) ntmp
= a
->a_nvals
[i
];
360 while (( j
>0 ) && (index
[j
-1] > idx
)) {
361 index
[j
] = index
[j
-1];
362 a
->a_vals
[j
] = a
->a_vals
[j
-1];
363 if ( gotnvals
) a
->a_nvals
[j
] = a
->a_nvals
[j
-1];
368 if ( gotnvals
) a
->a_nvals
[j
] = ntmp
;
370 /* Check for secondary sort */
371 if ( vi
->vi_sort
^ VALSORT_WEIGHTED
) {
373 for (j
=i
+1; j
<n
; j
++) {
374 if (index
[i
] != index
[j
])
378 do_sort( op
, a
, i
, j
-i
, vi
->vi_sort
);
382 op
->o_tmpfree( index
, op
->o_tmpmemctx
);
384 do_sort( op
, a
, 0, n
, vi
->vi_sort
);
387 return SLAP_CB_CONTINUE
;
391 valsort_add( Operation
*op
, SlapReply
*rs
)
393 slap_overinst
*on
= (slap_overinst
*)op
->o_bd
->bd_info
;
394 valsort_info
*vi
= on
->on_bi
.bi_private
;
400 /* See if any weighted sorting applies to this entry */
401 for ( ;vi
;vi
=vi
->vi_next
) {
402 if ( !dnIsSuffix( &op
->o_req_ndn
, &vi
->vi_dn
))
404 if ( !(vi
->vi_sort
& VALSORT_WEIGHTED
))
406 a
= attr_find( op
->ora_e
->e_attrs
, vi
->vi_ad
);
409 for (i
=0; !BER_BVISNULL( &a
->a_vals
[i
] ); i
++) {
410 ptr
= ber_bvchr(&a
->a_vals
[i
], '{' );
412 Debug(LDAP_DEBUG_TRACE
, "weight missing from attribute %s\n",
413 vi
->vi_ad
->ad_cname
.bv_val
, 0, 0);
414 send_ldap_error( op
, rs
, LDAP_CONSTRAINT_VIOLATION
,
415 "weight missing from attribute" );
418 strtol( ptr
+1, &end
, 0 );
420 Debug(LDAP_DEBUG_TRACE
, "weight is misformatted in %s\n",
421 vi
->vi_ad
->ad_cname
.bv_val
, 0, 0);
422 send_ldap_error( op
, rs
, LDAP_CONSTRAINT_VIOLATION
,
423 "weight is misformatted" );
428 return SLAP_CB_CONTINUE
;
432 valsort_modify( Operation
*op
, SlapReply
*rs
)
434 slap_overinst
*on
= (slap_overinst
*)op
->o_bd
->bd_info
;
435 valsort_info
*vi
= on
->on_bi
.bi_private
;
441 /* See if any weighted sorting applies to this entry */
442 for ( ;vi
;vi
=vi
->vi_next
) {
443 if ( !dnIsSuffix( &op
->o_req_ndn
, &vi
->vi_dn
))
445 if ( !(vi
->vi_sort
& VALSORT_WEIGHTED
))
447 for (ml
= op
->orm_modlist
; ml
; ml
=ml
->sml_next
) {
448 /* Must be a Delete Attr op, so no values to consider */
449 if ( !ml
->sml_values
)
451 if ( ml
->sml_desc
== vi
->vi_ad
)
456 for (i
=0; !BER_BVISNULL( &ml
->sml_values
[i
] ); i
++) {
457 ptr
= ber_bvchr(&ml
->sml_values
[i
], '{' );
459 Debug(LDAP_DEBUG_TRACE
, "weight missing from attribute %s\n",
460 vi
->vi_ad
->ad_cname
.bv_val
, 0, 0);
461 send_ldap_error( op
, rs
, LDAP_CONSTRAINT_VIOLATION
,
462 "weight missing from attribute" );
465 strtol( ptr
+1, &end
, 0 );
467 Debug(LDAP_DEBUG_TRACE
, "weight is misformatted in %s\n",
468 vi
->vi_ad
->ad_cname
.bv_val
, 0, 0);
469 send_ldap_error( op
, rs
, LDAP_CONSTRAINT_VIOLATION
,
470 "weight is misformatted" );
475 return SLAP_CB_CONTINUE
;
484 return overlay_register_control( be
, LDAP_CONTROL_VALSORT
);
493 slap_overinst
*on
= (slap_overinst
*)be
->bd_info
;
494 valsort_info
*vi
= on
->on_bi
.bi_private
, *next
;
496 for (; vi
; vi
= next
) {
498 ch_free( vi
->vi_dn
.bv_val
);
512 BerElementBuffer berbuf
;
513 BerElement
*ber
= (BerElement
*)&berbuf
;
516 if ( BER_BVISNULL( &ctrl
->ldctl_value
)) {
517 rs
->sr_text
= "valSort control value is absent";
518 return LDAP_PROTOCOL_ERROR
;
521 if ( BER_BVISEMPTY( &ctrl
->ldctl_value
)) {
522 rs
->sr_text
= "valSort control value is empty";
523 return LDAP_PROTOCOL_ERROR
;
526 ber_init2( ber
, &ctrl
->ldctl_value
, 0 );
527 if (( tag
= ber_scanf( ber
, "{b}", &flag
)) == LBER_ERROR
) {
528 rs
->sr_text
= "valSort control: flag decoding error";
529 return LDAP_PROTOCOL_ERROR
;
532 op
->o_ctrlflag
[valsort_cid
] = ctrl
->ldctl_iscritical
?
533 SLAP_CONTROL_CRITICAL
: SLAP_CONTROL_NONCRITICAL
;
535 op
->o_ctrlflag
[valsort_cid
] |= SLAP_CONTROL_DATA0
;
540 static slap_overinst valsort
;
542 int valsort_initialize( void )
546 valsort
.on_bi
.bi_type
= "valsort";
547 valsort
.on_bi
.bi_db_destroy
= valsort_destroy
;
548 valsort
.on_bi
.bi_db_open
= valsort_db_open
;
550 valsort
.on_bi
.bi_op_add
= valsort_add
;
551 valsort
.on_bi
.bi_op_modify
= valsort_modify
;
553 valsort
.on_response
= valsort_response
;
555 valsort
.on_bi
.bi_cf_ocs
= valsort_cfocs
;
557 rc
= register_supported_control( LDAP_CONTROL_VALSORT
,
558 SLAP_CTRL_SEARCH
| SLAP_CTRL_HIDE
, NULL
, valsort_parseCtrl
,
560 if ( rc
!= LDAP_SUCCESS
) {
561 fprintf( stderr
, "Failed to register control %d\n", rc
);
565 syn_numericString
= syn_find( "1.3.6.1.4.1.1466.115.121.1.36" );
567 rc
= config_register_schema( valsort_cfats
, valsort_cfocs
);
570 return overlay_register(&valsort
);
573 #if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC
574 int init_module( int argc
, char *argv
[]) {
575 return valsort_initialize();
579 #endif /* SLAPD_OVER_VALSORT */