diff options
57 files changed, 12149 insertions, 299 deletions
diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index 12cc425bd3..824b72b240 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -99,19 +99,8 @@ static unsigned int vrf_import_rt_hash_key_make(void *p) { struct vrf_irt_node *irt = p; char *pnt = irt->rt.val; - unsigned int key = 0; - int c = 0; - - key += pnt[c]; - key += pnt[c + 1]; - key += pnt[c + 2]; - key += pnt[c + 3]; - key += pnt[c + 4]; - key += pnt[c + 5]; - key += pnt[c + 6]; - key += pnt[c + 7]; - - return key; + + return jhash(pnt, 8, 0x5abc1234); } /* @@ -224,19 +213,8 @@ static unsigned int import_rt_hash_key_make(void *p) { struct irt_node *irt = p; char *pnt = irt->rt.val; - unsigned int key = 0; - int c = 0; - - key += pnt[c]; - key += pnt[c + 1]; - key += pnt[c + 2]; - key += pnt[c + 3]; - key += pnt[c + 4]; - key += pnt[c + 5]; - key += pnt[c + 6]; - key += pnt[c + 7]; - - return (key); + + return jhash(pnt, 8, 0xdeadbeef); } /* diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index ae4759aad0..fc15720ebc 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -418,8 +418,6 @@ extern void bgp_peer_clear_node_queue_drain_immediate(struct peer *peer); extern void bgp_process_queues_drain_immediate(void); /* for encap/vpn */ -extern struct bgp_node *bgp_afi_node_get(struct bgp_table *, afi_t, safi_t, - struct prefix *, struct prefix_rd *); extern struct bgp_node *bgp_afi_node_lookup(struct bgp_table *table, afi_t afi, safi_t safi, struct prefix *p, struct prefix_rd *prd); diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 4b9f4c639a..d30b057cbc 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -2698,7 +2698,7 @@ DEFUN (bgp_default_shutdown, NO_STR BGP_STR "Configure BGP defaults\n" - "Do not automatically activate peers upon configuration\n") + "Apply administrative shutdown to newly configured peers\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); bgp->autoshutdown = !strmatch(argv[0]->text, "no"); @@ -6612,17 +6612,21 @@ static void bgp_show_martian_nexthops(struct vty *vty, struct bgp *bgp) vty); } -DEFUN(show_bgp_martian_nexthop_db, - show_bgp_martian_nexthop_db_cmd, - "show bgp martian next-hop", - SHOW_STR - BGP_STR +DEFUN(show_bgp_martian_nexthop_db, show_bgp_martian_nexthop_db_cmd, + "show bgp [<view|vrf> VIEWVRFNAME] martian next-hop", + SHOW_STR BGP_STR BGP_INSTANCE_HELP_STR "martian next-hops\n" "martian next-hop database\n") { struct bgp *bgp = NULL; + int idx = 0; + + if (argv_find(argv, argc, "view", &idx) + || argv_find(argv, argc, "vrf", &idx)) + bgp = bgp_lookup_by_name(argv[idx + 1]->arg); + else + bgp = bgp_get_default(); - bgp = bgp_get_default(); if (!bgp) { vty_out(vty, "%% No BGP process is configured\n"); return CMD_WARNING; @@ -11608,7 +11612,7 @@ void bgp_vty_init(void) install_element(BGP_NODE, &bgp_listen_range_cmd); install_element(BGP_NODE, &no_bgp_listen_range_cmd); - /* "neighbors auto-shutdown" command */ + /* "bgp default shutdown" command */ install_element(BGP_NODE, &bgp_default_shutdown_cmd); /* "neighbor remote-as" commands. */ diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h index e456f7caed..cbb41f0840 100644 --- a/bgpd/bgp_vty.h +++ b/bgpd/bgp_vty.h @@ -44,9 +44,9 @@ struct bgp; "Address Family modifier\n" extern void bgp_vty_init(void); -extern const char *afi_safi_print(afi_t, safi_t); -extern const char *afi_safi_json(afi_t, safi_t); -extern void bgp_config_write_update_delay(struct vty *, struct bgp *); +extern const char *afi_safi_print(afi_t afi, safi_t safi); +extern const char *afi_safi_json(afi_t afi, safi_t safi); +extern void bgp_config_write_update_delay(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_wpkt_quanta(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_rpkt_quanta(struct vty *vty, struct bgp *bgp); extern void bgp_config_write_listen(struct vty *vty, struct bgp *bgp); diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index de170fdd01..7f7d746256 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -1743,7 +1743,7 @@ static int bgp_zebra_process_local_vni(int command, struct zclient *zclient, struct stream *s; vni_t vni; struct bgp *bgp; - struct in_addr vtep_ip; + struct in_addr vtep_ip = { INADDR_ANY }; vrf_id_t tenant_vrf_id = VRF_DEFAULT; s = zclient->ibuf; diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 19f0c8cabf..4ff0ef41e0 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -7151,7 +7151,7 @@ int bgp_config_write(struct vty *vty) /* BGP default autoshutdown neighbors */ if (bgp->autoshutdown) - vty_out(vty, " bgp default auto-shutdown\n"); + vty_out(vty, " bgp default shutdown\n"); /* BGP client-to-client reflection. */ if (bgp_flag_check(bgp, BGP_FLAG_NO_CLIENT_TO_CLIENT)) diff --git a/bgpd/rfapi/rfapi_vty.c b/bgpd/rfapi/rfapi_vty.c index 37ca5edc96..15d27b3961 100644 --- a/bgpd/rfapi/rfapi_vty.c +++ b/bgpd/rfapi/rfapi_vty.c @@ -494,7 +494,7 @@ void rfapiPrintBi(void *stream, struct bgp_info *bi) char *p = line; int r; int has_macaddr = 0; - struct ethaddr macaddr; + struct ethaddr macaddr = {{0}}; struct rfapi_l2address_option l2o_buf; uint8_t l2hid = 0; /* valid if has_macaddr */ diff --git a/configure.ac b/configure.ac index a3b38559e5..6bd2d44fe8 100755 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ ## AC_PREREQ(2.60) -AC_INIT(frr, 3.1-dev, [https://github.com/frrouting/frr/issues]) +AC_INIT(frr, 4.1-dev, [https://github.com/frrouting/frr/issues]) PACKAGE_URL="https://frrouting.org/" AC_SUBST(PACKAGE_URL) PACKAGE_FULLNAME="FRRouting" diff --git a/doc/OSPF-SR.rst b/doc/OSPF-SR.rst new file mode 100644 index 0000000000..0ee1a12f28 --- /dev/null +++ b/doc/OSPF-SR.rst @@ -0,0 +1,269 @@ +OSPF Segment Routing +==================== + +This is an EXPERIMENTAL support of draft +`draft-ietf-ospf-segment-routing-extensions-24`. +DON'T use it for production network. + +Implementation details +---------------------- + +Concepts +~~~~~~~~ + +Segment Routing used 3 differents OPAQUE LSA in OSPF to carry the various +information: + +* **Router Information:** flood the Segment Routing capabilities of the node. + This include the supported algorithms, the Segment Routing Global Block + (SRGB) and the Maximum Stack Depth (MSD). +* **Extended Link:** flood the Adjaceny and Lan Adjacency Segment Identifier +* **Extended Prefix:** flood the Prefix Segment Identifier + +The implementation follow previous TE and Router Information codes. It used the +OPAQUE LSA functions defined in ospf_opaque.[c,h] as well as the OSPF API. This +latter is mandatory for the implementation as it provides the Callback to +Segment Routing functions (see below) when an Extended Link / Prefix or Router +Information LSA s are received. + +Overview +~~~~~~~~ + +Following files where modified or added: + +* ospd_ri.[c,h] have been modified to add the new TLVs for Segment Routing. +* ospf_ext.[c,h] implement RFC7684 as base support of Extended Link and Prefix + Opaque LSA. +* ospf_sr.[c,h] implement the earth of Segment Routing. It adds a new Segment + Routing database to manage Segment Identifiers per Link and Prefix and + Segment Routing enable node, Callback functions to process incoming LSA and + install MPLS FIB entry through Zebra. + +The figure below shows the relation between the various files: + +* ospf_sr.c centralized all the Segment Routing processing. It receives Opaque + LSA Router Information (4.0.0.0) from ospf_ri.c and Extended Prefix + (7.0.0.X) Link (8.0.0.X) from ospf_ext.c. Once received, it parse TLVs and + SubTLVs and store information in SRDB (which is defined in ospf_sr.h). For + each received LSA, NHLFE is computed and send to Zebra to add/remove new + MPLS labels entries and FEC. New CLI configurations are also centralized in + ospf_sr.c. This CLI will trigger the flooding of new LSA Router Information + (4.0.0.0), Extended Prefix (7.0.0.X) and Link (8.0.0.X) by ospf_ri.c, + respectively ospf_ext.c. +* ospf_ri.c send back to ospf_sr.c received Router Information LSA and update + Self Router Information LSA with paramters provided by ospf_sr.c i.e. SRGB + and MSD. It use ospf_opaque.c functions to send/received these Opaque LSAs. +* ospf_ext.c send back to ospf_sr.c received Extended Prefix and Link Opaque + LSA and send self Extended Prefix and Link Opaque LSA through ospf_opaque.c + functions. + +:: + + +-----------+ +-------+ + | | | | + | ospf_sr.c +-----+ SRDB | + +-----------+ +--+ | | + | +-^-------^-+ | +-------+ + | | | | | + | | | | | + | | | | +--------+ + | | | | | + +---v----------+ | | | +-----v-------+ + | | | | | | | + | ospf_ri.c +--+ | +-------+ ospf_ext.c | + | LSA 4.0.0.0 | | | LSA 7.0.0.X | + | | | | LSA 8.0.0.X | + +---^----------+ | | | + | | +-----^-------+ + | | | + | | | + | +--------v------------+ | + | | | | + | | ZEBRA: Labels + FEC | | + | | | | + | +---------------------+ | + | | + | | + | +---------------+ | + | | | | + +---------> ospf_opaque.c <---------+ + | | + +---------------+ + + Figure 1: Overview of Segment Routing interaction + +Module interactions +~~~~~~~~~~~~~~~~~~~ + +To process incoming LSA, the code is based on the capability to call `hook()` +functions when LSA are inserted or delete to / from the LSDB and the +possibility to register particular treatment for Opaque LSA. The first point +is provided by the OSPF API feature and the second by the Opaque implementation +itself. Indeed, it is possible to register callback function for a given Opaque +LSA ID (see `ospf_register_opaque_functab()` function defined in +`ospf_opaque.c`). Each time a new LSA is added to the LSDB, the +`new_lsa_hook()` function previously register for this LSA type is called. For +Opaque LSA it is the `ospf_opaque_lsa_install_hook()`. For deletion, it is +`ospf_opaque_lsa_delete_hook()`. + +Note that incoming LSA which is already present in the LSDB will be inserted +after the old instance of this LSA remove from the LSDB. Thus, after the first +time, each incoming LSA will trigger a `delete` following by an `install`. This +is not very helpfull to handle real LSA deletion. In fact, LSA deletion is done +by Flushing LSA i.e. flood LSA after seting its age to MAX_AGE. Then, a garbage +function has the role to remove all LSA with `age == MAX_AGE` in the LSDB. So, +to handle LSA Flush, the best is to look to the LSA age to determine if it is +an installation or a future deletion i.e. the flushed LSA is first store in the +LSDB with MAX_AGE waiting for the garbage collector function. + +Router Information LSAs +^^^^^^^^^^^^^^^^^^^^^^^ + +To activate Segment Routing, new CLI command `segment-routing on` has been +introduced. When this command is activated, function +`ospf_router_info_update_sr()` is called to indicate to Router Information +process that Segment Routing TLVs must be flood. Same function is called to +modify the Segment Routing Global Block (SRGB) and Maximum Stack Depth (MSD) +TLV. Only Shortest Path First (SPF) Algorithm is supported, so no possiblity +to modify this TLV is offer by the code. + +When Opaque LSA Tyep 4 i.e. Router Information are stored in LSDB, function +`ospf_opaque_lsa_install_hook()` will call the previously registered function +`ospf_router_info_lsa_update()`. In turn, the function will simply trigger +`ospf_sr_ri_lsa_update()` or `ospf_sr_ri_lsa_delete` in function of the LSA +age. Before, it verifies that the LSA Opaque Type is 4 (Router Information). +Self Opaque LSA are not send back to the Segment Routing functions as +information are already stored. + +Extended Link Prefix LSAs +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Like for Router Information, Segment Routing is activate at the Extended +Link/Prefix level with new `segment-routing on` command. This trigger +automtically the flooding of Extended Link LSA for all ospf interface where +adjacency is full. For Extended Prefix LSA, the new CLI command +`segment-routing prefix ...` will trigger the flooding of Prefix SID +TLV/SubTLVs. + +When Opaque LSA Type 7 i.e. Extended Prefix and Type 8 i.e. Extended Link are +store in the LSDB, `ospf_ext_pref_update_lsa()` respectively +`ospf_ext_link_update_lsa()` are called like for Router Information LSA. In +turn, they respectively trigger `ospf_sr_ext_prefix_lsa_update()` / +`ospf_sr_ext_link_lsa_update()` or `ospf_sr_ext_prefix_lsa_delete()` / +`ospf_sr_ext_link_lsa_delete()` if the LSA age is equal to MAX_AGE. + +Zebra +^^^^^ + +When a new MPLS entry or new Forwarding Equivalent Class (FEC) must be added or +deleted in the data plane, `add_sid_nhlfe()` respectively `del_sid_nhlfe()` are +called. Once check the validity of labels, they are send to ZEBRA layer through +`ZEBRA_MPLS_LABELS_ADD` command, respectively `ZEBRA_MPLS_LABELS_DELETE` +command for deletion. This is completed by a new labelled route through +`ZEBRA_ROUTE_ADD` command, respectively `ZEBRA_ROUTE_DELETE` command. + +Configuration +------------- + +Linux Kernel +~~~~~~~~~~~~ + +In order to use OSPF Segment Routing, you must setup MPLS data plane. Up to +know, only Linux Kernel version >= 4.5 is supported. + +First, the MPLS modules aren't loaded by default, so you'll need to load them +yourself: + +:: + + modprobe mpls_router + modprobe mpls_gso + modprobe mpls_iptunnel + +Then, you must activate MPLS on the interface you would used: + +:: + + sysctl -w net.mpls.conf.enp0s9.input=1 + sysctl -w net.mpls.conf.lo.input=1 + sysctl -w net.mpls.platform_labels=1048575 + +The last line fix the maximum MPLS label value. + +Once OSPFd start with Segment Routing, you could check that MPLS routes are +enable with: + +:: + + ip -M route + ip route + +The first command show the MPLS LFIB table while the second show the FIB +table which contains route with MPLS label encapsulation. + +If you disable Penultimate Hop Popping with the `no-php-flag` (see below), you +MUST check that RP filter is not enable for the interface you intend to use, +especially the `lo` one. For that purpose, disable RP filtering with: + +:: + + systcl -w net.ipv4.conf.all.rp_filter=0 + sysctl -w net.ipv4.conf.lo.rp_filter=0 + +OSPFd +~~~~~ + +Here it is a simple example of configuration to enable Segment Routing. Note +that `opaque capability` and `router information` must be set to activate +Opaque LSA prior to Segment +Routing. + +:: + + router ospf + ospf router-id 192.168.1.11 + capability opaque + mpls-te on + mpls-te router-address 192.168.1.11 + router-info area 0.0.0.0 + segment-routing on + segment-routing global-block 10000 19999 + segment-routing node-msd 8 + segment-routing prefix 192.168.1.11/32 index 1100 + +The first segment-routing statement enable it. The Second one set the SRGB, +third line the MSD and finally, set the Prefix SID index for a given prefix. +Note that only prefix of Loopback interface could be configured with a Prefix +SID. It is possible to add `no-php-flag` at the end of the prefix command to +disbale Penultimate Hop Popping. This advertises peers that they MUST NOT pop +the MPLS label prior to sending the packet. + +Known limitations +----------------- + +* Runs only within default VRF +* Only single Area is supported. ABR is not yet supported +* Only SPF algorithm is supported +* Extended Prefix Range is not supported +* MPLS table are not flush at startup. Thus, restarting zebra process is + mandatory to remove old MPLS entries in the data plane after a crash of + ospfd daemon +* Due to a bug in OSPF Opaque, LSA are not flood when enable Segment Routing + through CLI once OSPFd started. You must configure Segment Routing within + configuration file before launching OSPFd +* With NO Penultimate Hop Popping, it is not possible to express a Segment + Path with an Adjacency SID due to the impossibility for the Linux Kernel to + perform double POP instruction. + +Credits +------- + +* Author: Anselme Sawadogo <anselmesawadogo@gmail.com> +* Author: Olivier Dugeon <olivier.dugeon@orange.com> +* Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + +This work has been performed in the framework of the H2020-ICT-2014 +project 5GEx (Grant Agreement no. 671636), which is partially funded +by the European Commission. + + diff --git a/doc/ospfd.texi b/doc/ospfd.texi index 26a7637048..33341d2be6 100644 --- a/doc/ospfd.texi +++ b/doc/ospfd.texi @@ -22,6 +22,7 @@ networks. * Opaque LSA:: * OSPF Traffic Engineering:: * Router Information:: +* Segment Routing:: * Debugging OSPF:: * OSPF Configuration Examples:: @end menu @@ -724,6 +725,46 @@ Show Router Capabilities flag. Show Router Capabilities PCE parameters. @end deffn +@node Segment Routing +@section Segment Routing + +This is an EXPERIMENTAL support of Segment Routing as per draft + draft-ietf-ospf-segment-routing-extensions-24.txt for MPLS dataplane. + +@deffn {OSPF Command} {segment-routing on} {} +@deffnx {OSPF Command} {no segment-routing} {} +Enable Segment Routing. Even if this also activate routing information support, +it is preferable to also activate routing information, and set accordingly the +Area or AS flooding. +@end deffn + +@deffn {OSPF Command} {segment-routing global-block (0-1048575) (0-1048575)} {} +@deffnx {OSPF Command} {no segment-routing global-block} {} +Fix the Segment Routing Global Block i.e. the label range used by MPLS to store +label in the MPLS FIB. +@end deffn + +@deffn {OSPF Command} {segment-routing node-msd (1-16)} {} +@deffnx {OSPF Command} {no segment-routing node-msd} {} +Fix the Maximum Stack Depth supported by the router. The value depend of the +MPLS dataplane. E.g. for Linux kernel, since version 4.13 it is 32. +@end deffn + +@deffn {OSPF Command} {segment-routing prefix A.B.C.D/M index (0-65535)} {} +@deffnx {OSPF Command} {segment-routing prefix A.B.C.D/M index (0-65535) no-php-flag} {} +@deffnx {OSPF Command} {no segment-routing prefix A.B.C.D/M} {} +Set the Segment Rounting index for the specifyed prefix. Note +that, only prefix with /32 corresponding to a loopback interface are +currently supported. The 'no-php-flag' means NO Penultimate Hop Popping that +allows SR node to request to its neighbor to not pop the label. +@end deffn + +@deffn {Command} {show ip ospf database segment-routing} {} +@deffnx {Command} {show ip ospf database segment-routing adv-router @var{adv-router}} {} +@deffnx {Command} {show ip ospf database segment-routing self-originate} {} +Show Segment Routing Data Base, all SR nodes, specific advertized router or self router. +@end deffn + @node Debugging OSPF @section Debugging OSPF diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c index 614f46c78b..e1e9ccee48 100644 --- a/isisd/isis_lsp.c +++ b/isisd/isis_lsp.c @@ -286,7 +286,7 @@ static void put_lsp_hdr(struct isis_lsp *lsp, size_t *len_pointer, bool keep) (lsp->level == IS_LEVEL_1) ? L1_LINK_STATE : L2_LINK_STATE; struct isis_lsp_hdr *hdr = &lsp->hdr; struct stream *stream = lsp->pdu; - size_t orig_getp, orig_endp; + size_t orig_getp = 0, orig_endp = 0; if (keep) { orig_getp = stream_get_getp(lsp->pdu); diff --git a/isisd/isis_tlvs.c b/isisd/isis_tlvs.c index b7389947b7..55b0a362f6 100644 --- a/isisd/isis_tlvs.c +++ b/isisd/isis_tlvs.c @@ -1312,7 +1312,7 @@ static int unpack_tlv_dynamic_hostname(enum isis_tlv_context context, bool sane = true; for (uint8_t i = 0; i < tlv_len; i++) { if ((unsigned char)tlvs->hostname[i] > 127 - || !isprint(tlvs->hostname[i])) { + || !isprint((int)tlvs->hostname[i])) { sane = false; tlvs->hostname[i] = '?'; } diff --git a/lib/command.c b/lib/command.c index 83c91c4c60..d17f2c3d48 100644 --- a/lib/command.c +++ b/lib/command.c @@ -96,7 +96,6 @@ const char *node_names[] = { "ldp l2vpn", // LDP_L2VPN_NODE, "ldp", // LDP_PSEUDOWIRE_NODE, "isis", // ISIS_NODE, - "pim", // PIM_NODE, "masc", // MASC_NODE, "irdp", // IRDP_NODE, "static ip", // IP_NODE, @@ -1308,7 +1307,6 @@ void cmd_exit(struct vty *vty) case KEYCHAIN_NODE: case MASC_NODE: case RMAP_NODE: - case PIM_NODE: case VTY_NODE: vty->node = CONFIG_NODE; break; @@ -1414,7 +1412,6 @@ DEFUN (config_end, case KEYCHAIN_NODE: case KEYCHAIN_KEY_NODE: case MASC_NODE: - case PIM_NODE: case VTY_NODE: case LINK_PARAMS_NODE: vty_config_unlock(vty); @@ -1865,7 +1862,7 @@ DEFUN (config_password, return CMD_SUCCESS; } - if (!isalnum(argv[idx_8]->arg[0])) { + if (!isalnum((int)argv[idx_8]->arg[0])) { vty_out(vty, "Please specify string starting with alphanumeric\n"); return CMD_WARNING_CONFIG_FAILED; @@ -1917,7 +1914,7 @@ DEFUN (config_enable_password, } } - if (!isalnum(argv[idx_8]->arg[0])) { + if (!isalnum((int)argv[idx_8]->arg[0])) { vty_out(vty, "Please specify string starting with alphanumeric\n"); return CMD_WARNING_CONFIG_FAILED; diff --git a/lib/command.h b/lib/command.h index fa8323bf2d..e1edc1ef32 100644 --- a/lib/command.h +++ b/lib/command.h @@ -119,7 +119,6 @@ enum node_type { LDP_L2VPN_NODE, /* LDP L2VPN node */ LDP_PSEUDOWIRE_NODE, /* LDP Pseudowire node */ ISIS_NODE, /* ISIS protocol mode */ - PIM_NODE, /* PIM protocol mode */ MASC_NODE, /* MASC for multicast. */ IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ IP_NODE, /* Static ip route node. */ @@ -358,6 +357,7 @@ struct cmd_node { #define OSPF_RI_STR "OSPF Router Information specific commands\n" #define PCE_STR "PCE Router Information specific commands\n" #define MPLS_STR "MPLS information\n" +#define SR_STR "Segment-Routing specific commands\n" #define WATCHFRR_STR "watchfrr information\n" #define ZEBRA_STR "Zebra information\n" diff --git a/lib/command_graph.c b/lib/command_graph.c index fce11a70cc..f00b126536 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -97,7 +97,7 @@ void cmd_token_varname_set(struct cmd_token *token, const char *varname) token->varname[i] = '_'; break; default: - token->varname[i] = tolower(varname[i]); + token->varname[i] = tolower((int)varname[i]); } token->varname[len] = '\0'; } diff --git a/lib/command_lex.l b/lib/command_lex.l index 436f3a241d..530900659b 100644 --- a/lib/command_lex.l +++ b/lib/command_lex.l @@ -23,8 +23,9 @@ */ %{ -/* ignore harmless bug in old versions of flex */ +/* ignore harmless bugs in old versions of flex */ #pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wmissing-prototypes" #include "command_parse.h" diff --git a/lib/defun_lex.l b/lib/defun_lex.l index 024445be63..87775a0e75 100644 --- a/lib/defun_lex.l +++ b/lib/defun_lex.l @@ -34,8 +34,9 @@ * code documentation in it. */ -/* ignore harmless bug in old versions of flex */ +/* ignore harmless bugs in old versions of flex */ #pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wunused-value" #include "config.h" #include <Python.h> diff --git a/lib/mpls.h b/lib/mpls.h index 6ef00375e8..95882c26ec 100644 --- a/lib/mpls.h +++ b/lib/mpls.h @@ -41,8 +41,16 @@ #define MPLS_MAX_UNRESERVED_LABEL 1048575 /* Default min and max SRGB label range */ -#define MPLS_DEFAULT_MIN_SRGB_LABEL 16000 -#define MPLS_DEFAULT_MAX_SRGB_LABEL 23999 +/* Even if the SRGB allows to manage different Label space between routers, + * if an operator want to use the same SRGB for all its router, we must fix + * a common range. However, Cisco start its SRGB at 16000 and Juniper ends + * its SRGB at 16384 for OSPF. Thus, by fixing the minimum SRGB label to + * 8000 we could deal with both Cisco and Juniper. + */ +#define MPLS_DEFAULT_MIN_SRGB_LABEL 8000 +#define MPLS_DEFAULT_MAX_SRGB_LABEL 50000 +#define MPLS_DEFAULT_MIN_SRGB_SIZE 5000 +#define MPLS_DEFAULT_MAX_SRGB_SIZE 20000 /* Maximum # labels that can be pushed. */ #define MPLS_MAX_LABELS 16 @@ -100,7 +108,8 @@ enum lsp_types_t { ZEBRA_LSP_NONE = 0, /* No LSP. */ ZEBRA_LSP_STATIC = 1, /* Static LSP. */ ZEBRA_LSP_LDP = 2, /* LDP LSP. */ - ZEBRA_LSP_BGP = 3 /* BGP LSP. */ + ZEBRA_LSP_BGP = 3, /* BGP LSP. */ + ZEBRA_LSP_SR = 4 /* Segment Routing LSP. */ }; /* Functions for basic label operations. */ diff --git a/lib/prefix.h b/lib/prefix.h index 7e947ea48a..bcc2230607 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -39,12 +39,6 @@ #define ETH_ALEN 6 #endif -/* for compatibility */ -#ifdef ETHER_ADDR_LEN -#undef ETHER_ADDR_LEN -#endif -#define ETHER_ADDR_LEN 6 CPP_WARN("ETHER_ADDR_LEN is being replaced by ETH_ALEN.\\n") - #define ETHER_ADDR_STRLEN (3*ETH_ALEN) /* * there isn't a portable ethernet address type. We define our diff --git a/lib/ptm_lib.c b/lib/ptm_lib.c index 28d26149e5..fea5a8cc40 100644 --- a/lib/ptm_lib.c +++ b/lib/ptm_lib.c @@ -120,7 +120,7 @@ static int _ptm_lib_decode_header(csv_t *csv, int *msglen, int *version, } /* remove leading spaces */ for (i = j = 0; i < csv_field_len(fld); i++) { - if (!isspace(hdr[i])) { + if (!isspace((int)hdr[i])) { client_name[j] = hdr[i]; j++; } @@ -347,7 +347,7 @@ int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen, { int rc, len; char client_name[32]; - int cmd_id, type, ver, msglen; + int cmd_id = 0, type = 0, ver = 0, msglen = 0; csv_t *csv; ptm_lib_msg_ctxt_t *p_ctxt = NULL; diff --git a/lib/spf_backoff.c b/lib/spf_backoff.c index 92b7620eda..d7907fafdf 100644 --- a/lib/spf_backoff.c +++ b/lib/spf_backoff.c @@ -205,7 +205,7 @@ static const char *timeval_format(struct timeval *tv) size_t offset = strlen(timebuf); snprintf(timebuf + offset, sizeof(timebuf) - offset, ".%ld", - tv->tv_usec); + (long int)tv->tv_usec); return timebuf; } @@ -732,7 +732,6 @@ static void vty_end_config(struct vty *vty) case KEYCHAIN_NODE: case KEYCHAIN_KEY_NODE: case MASC_NODE: - case PIM_NODE: case VTY_NODE: case BGP_EVPN_VNI_NODE: vty_config_unlock(vty); @@ -1130,7 +1129,6 @@ static void vty_stop_input(struct vty *vty) case KEYCHAIN_NODE: case KEYCHAIN_KEY_NODE: case MASC_NODE: - case PIM_NODE: case VTY_NODE: vty_config_unlock(vty); vty->node = ENABLE_NODE; diff --git a/lib/zclient.c b/lib/zclient.c index d3717d0cdd..4d5b0f211d 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -976,7 +976,7 @@ int zapi_route_encode(u_char cmd, struct stream *s, struct zapi_route *api) stream_putw(s, api->nexthop_num); if (api->nexthop_num) - stream_putw(s, api->nh_vrf_id); + stream_putl(s, api->nh_vrf_id); for (i = 0; i < api->nexthop_num; i++) { api_nh = &api->nexthops[i]; @@ -1127,7 +1127,7 @@ int zapi_route_decode(struct stream *s, struct zapi_route *api) } if (api->nexthop_num) - STREAM_GETW(s, api->nh_vrf_id); + STREAM_GETL(s, api->nh_vrf_id); for (i = 0; i < api->nexthop_num; i++) { api_nh = &api->nexthops[i]; diff --git a/ospfd/ospf_dump.c b/ospfd/ospf_dump.c index 6a410f4ed3..66ab59b816 100644 --- a/ospfd/ospf_dump.c +++ b/ospfd/ospf_dump.c @@ -51,6 +51,8 @@ unsigned long conf_debug_ospf_lsa = 0; unsigned long conf_debug_ospf_zebra = 0; unsigned long conf_debug_ospf_nssa = 0; unsigned long conf_debug_ospf_te = 0; +unsigned long conf_debug_ospf_ext = 0; +unsigned long conf_debug_ospf_sr = 0; /* Enable debug option variables -- valid only session. */ unsigned long term_debug_ospf_packet[5] = {0, 0, 0, 0, 0}; @@ -61,7 +63,8 @@ unsigned long term_debug_ospf_lsa = 0; unsigned long term_debug_ospf_zebra = 0; unsigned long term_debug_ospf_nssa = 0; unsigned long term_debug_ospf_te = 0; - +unsigned long term_debug_ospf_ext = 0; +unsigned long term_debug_ospf_sr = 0; const char *ospf_redist_string(u_int route_type) { @@ -1441,6 +1444,33 @@ DEFUN (no_debug_ospf_te, return CMD_SUCCESS; } +DEFUN (debug_ospf_sr, + debug_ospf_sr_cmd, + "debug ospf sr", + DEBUG_STR + OSPF_STR + "OSPF-SR information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_ON(sr, SR); + TERM_DEBUG_ON(sr, SR); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_sr, + no_debug_ospf_sr_cmd, + "no debug ospf sr", + NO_STR + DEBUG_STR + OSPF_STR + "OSPF-SR information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_OFF(sr, SR); + TERM_DEBUG_OFF(sr, SR); + return CMD_SUCCESS; +} + DEFUN (no_debug_ospf, no_debug_ospf_cmd, "no debug ospf", @@ -1758,6 +1788,18 @@ static int config_write_debug(struct vty *vty) write = 1; } + /* debug ospf te */ + if (IS_CONF_DEBUG_OSPF(te, TE) == OSPF_DEBUG_TE) { + vty_out(vty, "debug ospf%s te\n", str); + write = 1; + } + + /* debug ospf sr */ + if (IS_CONF_DEBUG_OSPF(sr, SR) == OSPF_DEBUG_SR) { + vty_out(vty, "debug ospf%s sr\n", str); + write = 1; + } + return write; } @@ -1774,6 +1816,7 @@ void debug_init() install_element(ENABLE_NODE, &debug_ospf_event_cmd); install_element(ENABLE_NODE, &debug_ospf_nssa_cmd); install_element(ENABLE_NODE, &debug_ospf_te_cmd); + install_element(ENABLE_NODE, &debug_ospf_sr_cmd); install_element(ENABLE_NODE, &no_debug_ospf_ism_cmd); install_element(ENABLE_NODE, &no_debug_ospf_nsm_cmd); install_element(ENABLE_NODE, &no_debug_ospf_lsa_cmd); @@ -1781,6 +1824,7 @@ void debug_init() install_element(ENABLE_NODE, &no_debug_ospf_event_cmd); install_element(ENABLE_NODE, &no_debug_ospf_nssa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_te_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_sr_cmd); install_element(ENABLE_NODE, &show_debugging_ospf_instance_cmd); install_element(ENABLE_NODE, &debug_ospf_packet_cmd); @@ -1809,12 +1853,14 @@ void debug_init() install_element(CONFIG_NODE, &debug_ospf_event_cmd); install_element(CONFIG_NODE, &debug_ospf_nssa_cmd); install_element(CONFIG_NODE, &debug_ospf_te_cmd); + install_element(CONFIG_NODE, &debug_ospf_sr_cmd); install_element(CONFIG_NODE, &no_debug_ospf_nsm_cmd); install_element(CONFIG_NODE, &no_debug_ospf_lsa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_zebra_cmd); install_element(CONFIG_NODE, &no_debug_ospf_event_cmd); install_element(CONFIG_NODE, &no_debug_ospf_nssa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_te_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_sr_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_nsm_cmd); install_element(CONFIG_NODE, &debug_ospf_instance_lsa_cmd); diff --git a/ospfd/ospf_dump.h b/ospfd/ospf_dump.h index ead2f526ba..99d7512f16 100644 --- a/ospfd/ospf_dump.h +++ b/ospfd/ospf_dump.h @@ -57,6 +57,8 @@ #define OSPF_DEBUG_EVENT 0x01 #define OSPF_DEBUG_NSSA 0x02 #define OSPF_DEBUG_TE 0x04 +#define OSPF_DEBUG_EXT 0x08 +#define OSPF_DEBUG_SR 0x10 /* Macro for setting debug option. */ #define CONF_DEBUG_PACKET_ON(a, b) conf_debug_ospf_packet[a] |= (b) @@ -92,11 +94,15 @@ /* Macro for checking debug option. */ #define IS_DEBUG_OSPF_PACKET(a, b) (term_debug_ospf_packet[a] & OSPF_DEBUG_##b) #define IS_DEBUG_OSPF(a, b) (term_debug_ospf_##a & OSPF_DEBUG_##b) -#define IS_DEBUG_OSPF_EVENT IS_DEBUG_OSPF(event,EVENT) +#define IS_DEBUG_OSPF_EVENT IS_DEBUG_OSPF(event, EVENT) -#define IS_DEBUG_OSPF_NSSA IS_DEBUG_OSPF(nssa,NSSA) +#define IS_DEBUG_OSPF_NSSA IS_DEBUG_OSPF(nssa, NSSA) -#define IS_DEBUG_OSPF_TE IS_DEBUG_OSPF(te,TE) +#define IS_DEBUG_OSPF_TE IS_DEBUG_OSPF(te, TE) + +#define IS_DEBUG_OSPF_EXT IS_DEBUG_OSPF(ext, EXT) + +#define IS_DEBUG_OSPF_SR IS_DEBUG_OSPF(sr, SR) #define IS_CONF_DEBUG_OSPF_PACKET(a, b) \ (conf_debug_ospf_packet[a] & OSPF_DEBUG_##b) @@ -119,6 +125,8 @@ extern unsigned long term_debug_ospf_lsa; extern unsigned long term_debug_ospf_zebra; extern unsigned long term_debug_ospf_nssa; extern unsigned long term_debug_ospf_te; +extern unsigned long term_debug_ospf_ext; +extern unsigned long term_debug_ospf_sr; /* Message Strings. */ extern char *ospf_lsa_type_str[]; diff --git a/ospfd/ospf_ext.c b/ospfd/ospf_ext.c new file mode 100644 index 0000000000..d42476b6d8 --- /dev/null +++ b/ospfd/ospf_ext.c @@ -0,0 +1,1827 @@ +/* + * This is an implementation of RFC7684 OSPFv2 Prefix/Link Attribute + * Advertisement + * + * Module name: Extended Prefix/Link Opaque LSA + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Author: Anselme Sawadogo <anselmesawadogo@gmail.com> + * + * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "thread.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "network.h" +#include "if.h" +#include "libospf.h" /* for ospf interface types */ + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ext.h" + +/* Following structure are internal use only. */ + +/* + * Global variable to manage Extended Prefix/Link Opaque LSA on this node. + * Note that all parameter values are stored in network byte order. + */ +static struct ospf_ext_lp OspfEXT; + +/* + * ----------------------------------------------------------------------- + * Followings are initialize/terminate functions for Extended Prefix/Link + * Opaque LSA handling. + * ----------------------------------------------------------------------- + */ + +/* Extended Prefix Opaque LSA related callback functions */ +static void ospf_ext_pref_ism_change(struct ospf_interface *oi, int old_status); +static void ospf_ext_pref_show_info(struct vty *vty, struct ospf_lsa *lsa); +static int ospf_ext_pref_lsa_originate(void *arg); +static struct ospf_lsa *ospf_ext_pref_lsa_refresh(struct ospf_lsa *lsa); +static void ospf_ext_pref_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode); +/* Extended Link Opaque LSA related callback functions */ +static int ospf_ext_link_new_if(struct interface *ifp); +static int ospf_ext_link_del_if(struct interface *ifp); +static void ospf_ext_link_ism_change(struct ospf_interface *oi, int old_status); +static void ospf_ext_link_nsm_change(struct ospf_neighbor *nbr, int old_status); +static void ospf_ext_link_show_info(struct vty *vty, struct ospf_lsa *lsa); +static int ospf_ext_link_lsa_originate(void *arg); +static struct ospf_lsa *ospf_ext_link_lsa_refresh(struct ospf_lsa *lsa); +static void ospf_ext_link_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode); +static void ospf_ext_lsa_schedule(struct ext_itf *exti, enum lsa_opcode op); +static int ospf_ext_link_lsa_update(struct ospf_lsa *lsa); +static int ospf_ext_pref_lsa_update(struct ospf_lsa *lsa); +static void del_ext_info(void *val); + +/* + * Extended Link/Prefix initialization + * + * @param - none + * + * @return - 0 if OK, <> 0 otherwise + */ +int ospf_ext_init(void) +{ + int rc = 0; + + memset(&OspfEXT, 0, sizeof(struct ospf_ext_lp)); + OspfEXT.enabled = false; + /* Only Area flooding is supported yet */ + OspfEXT.scope = OSPF_OPAQUE_AREA_LSA; + /* Initialize interface list */ + OspfEXT.iflist = list_new(); + OspfEXT.iflist->del = del_ext_info; + + zlog_info("EXT (%s): Register Extended Link Opaque LSA", __func__); + rc = ospf_register_opaque_functab( + OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_EXTENDED_LINK_LSA, + ospf_ext_link_new_if, /* new if */ + ospf_ext_link_del_if, /* del if */ + ospf_ext_link_ism_change, /* ism change */ + ospf_ext_link_nsm_change, /* nsm change */ + NULL, /* Write router config. */ + NULL, /* Write interface conf. */ + NULL, /* Write debug config. */ + ospf_ext_link_show_info, /* Show LSA info */ + ospf_ext_link_lsa_originate, /* Originate LSA */ + ospf_ext_link_lsa_refresh, /* Refresh LSA */ + ospf_ext_link_lsa_update, /* new_lsa_hook */ + NULL); /* del_lsa_hook */ + + if (rc != 0) { + zlog_warn("EXT (%s): Failed to register Extended Link LSA", + __func__); + return rc; + } + + zlog_info("EXT (%s): Register Extended Prefix Opaque LSA", __func__); + rc = ospf_register_opaque_functab( + OspfEXT.scope, OPAQUE_TYPE_EXTENDED_PREFIX_LSA, + NULL, /* new if handle by link */ + NULL, /* del if handle by link */ + ospf_ext_pref_ism_change, /* ism change */ + NULL, /* nsm change */ + ospf_sr_config_write_router, /* Write router config. */ + NULL, /* Write interface conf. */ + NULL, /* Write debug config. */ + ospf_ext_pref_show_info, /* Show LSA info */ + ospf_ext_pref_lsa_originate, /* Originate LSA */ + ospf_ext_pref_lsa_refresh, /* Refresh LSA */ + ospf_ext_pref_lsa_update, /* new_lsa_hook */ + NULL); /* del_lsa_hook */ + if (rc != 0) { + zlog_warn("EXT (%s): Failed to register Extended Prefix LSA", + __func__); + return rc; + } + + return rc; +} + +/* + * Extended Link/Prefix termination function + * + * @param - none + * @return - none + */ +void ospf_ext_term(void) +{ + + if ((OspfEXT.scope != OSPF_OPAQUE_AREA_LSA) + || (OspfEXT.scope != OSPF_OPAQUE_AS_LSA)) + zlog_warn( + "EXT: Unable to unregister Extended Prefix " + "Opaque LSA functions: Wrong scope!"); + else + ospf_delete_opaque_functab(OspfEXT.scope, + OPAQUE_TYPE_EXTENDED_PREFIX_LSA); + + ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_EXTENDED_LINK_LSA); + + list_delete_and_null(&OspfEXT.iflist); + OspfEXT.scope = 0; + OspfEXT.enabled = false; + + return; +} + +/* + * Extended Link/Prefix finish function + * + * @param - none + * @return - none + */ +void ospf_ext_finish(void) +{ + // list_delete_all_node(OspfEXT.iflist); + OspfEXT.enabled = false; +} + +/* + * --------------------------------------------------------------------- + * Followings are control functions for Extended Prefix/Link Opaque LSA + * parameters management. + * --------------------------------------------------------------------- + */ + +/* Functions to free memory space */ +static void del_ext_info(void *val) +{ + XFREE(MTYPE_OSPF_EXT_PARAMS, val); +} + +/* Increment instance value for Extended Prefix Opaque LSAs Opaque ID field */ +static uint32_t get_ext_pref_instance_value(void) +{ + static uint32_t seqno = 0; + + if (seqno < MAX_LEGAL_EXT_INSTANCE_NUM) + seqno += 1; + else + seqno = 1; /* Avoid zero. */ + + return seqno; +} + +/* Increment instance value for Extended Link Opaque LSAs Opaque ID field */ +static uint32_t get_ext_link_instance_value(void) +{ + static uint32_t seqno = 0; + + if (seqno < MAX_LEGAL_EXT_INSTANCE_NUM) + seqno += 1; + else + seqno = 1; /* Avoid zero. */ + + return seqno; +} + +/* Lookup Extended Prefix/Links by ifp from OspfEXT struct iflist */ +static struct ext_itf *lookup_ext_by_ifp(struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ext_itf *exti; + + for (ALL_LIST_ELEMENTS(OspfEXT.iflist, node, nnode, exti)) + if (exti->ifp == ifp) + return exti; + + return NULL; +} + +/* Lookup Extended Prefix/Links by LSA ID from OspfEXT struct iflist */ +static struct ext_itf *lookup_ext_by_instance(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct ext_itf *exti; + uint32_t key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); + uint8_t type = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + + + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + if ((exti->instance == key) && (exti->type == type)) + return exti; + + return NULL; +} + +/* + * ---------------------------------------------------------------------- + * The underlying subsection defines setters and unsetters to create and + * delete tlvs and subtlvs + * ---------------------------------------------------------------------- + */ + +/* Extended Prefix TLV - RFC7684 section 2.1 */ +static void set_ext_prefix(struct ext_itf *exti, uint8_t route_type, + uint8_t flags, struct prefix_ipv4 p) +{ + + TLV_TYPE(exti->prefix) = htons(EXT_TLV_PREFIX); + /* Warning: Size must be adjust depending of subTLV's */ + TLV_LEN(exti->prefix) = htons(EXT_TLV_PREFIX_SIZE); + exti->prefix.route_type = route_type; + exti->prefix.flags = flags; + /* Only Address Family Ipv4 (0) is defined in RFC 7684 */ + exti->prefix.af = 0; + exti->prefix.pref_length = p.prefixlen; + exti->prefix.address = p.prefix; +} + +/* Extended Link TLV - RFC7684 section 3.1 */ +static void set_ext_link(struct ext_itf *exti, uint8_t type, struct in_addr id, + struct in_addr data) +{ + + TLV_TYPE(exti->link) = htons(EXT_TLV_LINK); + /* Warning: Size must be adjust depending of subTLV's */ + TLV_LEN(exti->link) = htons(EXT_TLV_LINK_SIZE); + exti->link.link_type = type; + exti->link.link_id = id; + exti->link.link_data = data; +} + +/* Prefix SID SubTLV - section 5 */ +static void set_prefix_sid(struct ext_itf *exti, uint8_t algorithm, + uint32_t value, int value_type, uint8_t flags) +{ + + if ((algorithm != SR_ALGORITHM_SPF) + && (algorithm != SR_ALGORITHM_STRICT_SPF)) { + zlog_warn( + "EXT (%s): unrecognized algorithm, not SPF or S-SPF", + __func__); + return; + } + + /* Update flags according to the type of value field: label or index */ + if (value_type == SID_LABEL) + SET_FLAG(flags, EXT_SUBTLV_PREFIX_SID_VFLG); + + /* set prefix sid subtlv for an extended prefix tlv */ + TLV_TYPE(exti->node_sid) = htons(EXT_SUBTLV_PREFIX_SID); + exti->node_sid.algorithm = algorithm; + exti->node_sid.flags = flags; + exti->node_sid.mtid = 0; /* Multi-Topology is not supported */ + + /* Set Label or Index value */ + if (value_type == SID_LABEL) { + TLV_LEN(exti->node_sid) = htons(SID_LABEL_SIZE); + exti->node_sid.value = htonl(SET_LABEL(value)); + } else { + TLV_LEN(exti->node_sid) = htons(SID_INDEX_SIZE); + exti->node_sid.value = htonl(value); + } + +} + +/* Adjacency SID SubTLV - section 6.1 */ +static void set_adj_sid(struct ext_itf *exti, bool backup, uint32_t value, + int value_type) +{ + int index; + uint8_t flags; + + /* Determine which ADJ_SID must be set: nominal or backup */ + if (backup) { + flags = EXT_SUBTLV_LINK_ADJ_SID_BFLG; + index = 1; + } else { + index = 0; + flags = 0; + } + + /* Set Header */ + TLV_TYPE(exti->adj_sid[index]) = htons(EXT_SUBTLV_ADJ_SID); + + /* Only Local ADJ-SID is supported for the moment */ + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); + + exti->adj_sid[index].mtid = 0; /* Multi-Topology is not supported */ + + /* Adjust Length, Flags and Value depending on the type of Label */ + if (value_type == SID_LABEL) { + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->adj_sid[index]) = htons(SID_LABEL_SIZE); + exti->adj_sid[index].value = htonl(SET_LABEL(value)); + } else { + UNSET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->adj_sid[index]) = htons(SID_INDEX_SIZE); + exti->adj_sid[index].value = htonl(value); + } + + exti->adj_sid[index].flags = flags; /* Set computed flags */ + exti->adj_sid[index].mtid = 0; /* Multi-Topology is not supported */ + exti->adj_sid[index].weight = 0; /* Load-Balancing is not supported */ + +} + +/* LAN Adjacency SID SubTLV - section 6.2 */ +static void set_lan_adj_sid(struct ext_itf *exti, bool backup, uint32_t value, + int value_type, struct in_addr neighbor_id) +{ + + int index; + uint8_t flags; + + /* Determine which ADJ_SID must be set: nominal or backup */ + if (backup) { + flags = EXT_SUBTLV_LINK_ADJ_SID_BFLG; + index = 1; + } else { + index = 0; + flags = 0; + } + + /* Set Header */ + TLV_TYPE(exti->lan_sid[index]) = htons(EXT_SUBTLV_ADJ_SID); + + /* Only Local ADJ-SID is supported for the moment */ + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); + + /* Adjust Length, Flags and Value depending on the type of Label */ + if (value_type == SID_LABEL) { + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->lan_sid[index]) = htons(SID_LABEL_SIZE); + exti->lan_sid[index].value = htonl(SET_LABEL(value)); + } else { + UNSET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->lan_sid[index]) = htons(SID_INDEX_SIZE); + exti->lan_sid[index].value = htonl(value); + } + + exti->lan_sid[index].flags = flags; /* Set computed flags */ + exti->lan_sid[index].mtid = 0; /* Multi-Topology is not supported */ + exti->lan_sid[index].weight = 0; /* Load-Balancing is not supported */ + exti->lan_sid[index].neighbor_id = neighbor_id; + +} + +/* Experimental SubTLV from Cisco */ +static void set_rmt_itf_addr(struct ext_itf *exti, struct in_addr rmtif) +{ + + TLV_TYPE(exti->rmt_itf_addr) = htons(EXT_SUBTLV_RMT_ITF_ADDR); + TLV_LEN(exti->rmt_itf_addr) = htons(sizeof(struct in_addr)); + exti->rmt_itf_addr.value = rmtif; + +} + +/* + * Update Extended prefix SID index for Loopback interface type + * + * @param ifname - Loopback interface name + * @param index - new value for the prefix SID of this interface + * @param p - prefix for this interface or NULL if Extended Prefix + * should be remove + * + * @return instance number if update is OK, 0 otherwise + */ +uint32_t ospf_ext_schedule_prefix_index(struct interface *ifp, uint32_t index, + struct prefix_ipv4 *p, uint8_t flags) +{ + int rc = 0; + struct ext_itf *exti; + + /* Find Extended Prefix interface */ + exti = lookup_ext_by_ifp(ifp); + if (exti == NULL) + return rc; + + if (p != NULL) { + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): Schedule new prefix %s/%u with " + "index %u on interface %s", + __func__, inet_ntoa(p->prefix), p->prefixlen, + index, ifp->name); + + /* Set first Extended Prefix then the Prefix SID information */ + set_ext_prefix(exti, OSPF_PATH_INTRA_AREA, EXT_TLV_PREF_NFLG, + *p); + set_prefix_sid(exti, SR_ALGORITHM_SPF, index, SID_INDEX, flags); + + /* Try to Schedule LSA */ + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_pref_lsa_schedule(exti, REFRESH_THIS_LSA); + else + ospf_ext_pref_lsa_schedule(exti, REORIGINATE_THIS_LSA); + } else { + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): Remove prefix for interface %s", + __func__, ifp->name); + + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + ospf_ext_pref_lsa_schedule(exti, FLUSH_THIS_LSA); + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); + } + } + + return SET_OPAQUE_LSID(exti->type, exti->instance); +} + +/* + * Used by Segment Routing to activate/deactivate Extended Link/Prefix flooding + * + * @param enable To activate or not Segment Routing Extended LSA flooding + * + * @return none + */ +void ospf_ext_update_sr(bool enable) +{ + struct listnode *node; + struct ext_itf *exti; + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): %s Extended LSAs for Segment Routing ", + __func__, enable ? "Enable" : "Disable"); + + if (enable) { + OspfEXT.enabled = true; + /* Refresh LSAs if already engaged or originate */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_lsa_schedule(exti, REFRESH_THIS_LSA); + else + ospf_ext_lsa_schedule(exti, + REORIGINATE_THIS_LSA); + } else { + /* Start by Flushing engaged LSAs */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_lsa_schedule(exti, FLUSH_THIS_LSA); + /* And then disable Extended Link/Prefix */ + OspfEXT.enabled = false; + } +} +/* + * ----------------------------------------------------------------------- + * Followings are callback functions against generic Opaque-LSAs handling + * ----------------------------------------------------------------------- + */ + +/* Add new Interface in Extended Interface List */ +static int ospf_ext_link_new_if(struct interface *ifp) +{ + struct ext_itf *new; + int rc = -1; + + if (lookup_ext_by_ifp(ifp) != NULL) { + zlog_warn( + "EXT (%s): interface %s is already in use", + __func__, ifp ? ifp->name : "-"); + rc = 0; /* Do nothing here. */ + return rc; + } + + new = XCALLOC(MTYPE_OSPF_EXT_PARAMS, sizeof(struct ext_itf)); + if (new == NULL) { + zlog_warn("EXT (%s): XCALLOC: %s", __func__, + safe_strerror(errno)); + return rc; + } + + /* initialize new information and link back the interface */ + new->ifp = ifp; + new->flags = EXT_LPFLG_LSA_INACTIVE; + + listnode_add(OspfEXT.iflist, new); + + rc = 0; + return rc; +} + +/* Remove existing Interface from Extended Interface List */ +static int ospf_ext_link_del_if(struct interface *ifp) +{ + struct ext_itf *exti; + int rc = -1; + + exti = lookup_ext_by_ifp(ifp); + if (exti != NULL) { + struct list *iflist = OspfEXT.iflist; + + /* Dequeue listnode entry from the list. */ + listnode_delete(iflist, exti); + + XFREE(MTYPE_OSPF_EXT_PARAMS, exti); + + rc = 0; + } else { + zlog_warn( + "EXT (%s): interface %s is not found", + __func__, ifp ? ifp->name : "-"); + } + + return rc; +} + +/* + * Determine if an Interface belongs to an Extended Link Adjacency or LAN Adj. + * type and allocate new instance value accordingly + */ +static void ospf_ext_link_ism_change(struct ospf_interface *oi, int old_status) +{ + struct ext_itf *exti; + + /* Get interface information for Segment Routing */ + exti = lookup_ext_by_ifp(oi->ifp); + if (exti == NULL) { + zlog_warn( + "EXT (%s): Cannot get Extended info. from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + /* Determine if interface is related to Adjacency or LAN Adj. SID */ + if (oi->type != OSPF_IFTYPE_LOOPBACK) { + if (oi->state == ISM_DR) + exti->stype = LAN_ADJ_SID; + else + exti->stype = ADJ_SID; + + exti->instance = get_ext_link_instance_value(); + exti->type = OPAQUE_TYPE_EXTENDED_LINK_LSA; + + zlog_debug( + "EXT (%s): Set %s SID to interface %s ", __func__, + exti->stype == ADJ_SID ? "Adj." : "LAN Adj.", + oi->ifp->name); + } +} + +/* + * Determine if an Interface belongs to an Extended Prefix and + * allocate new instance value accordingly + */ +static void ospf_ext_pref_ism_change(struct ospf_interface *oi, int old_status) +{ + struct ext_itf *exti; + + /* Get interface information for Segment Routing */ + exti = lookup_ext_by_ifp(oi->ifp); + if (exti == NULL) { + zlog_warn( + "EXT (%s): Cannot get Extended info. from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + /* Determine if interface is related to a Node SID */ + if (oi->type == OSPF_IFTYPE_LOOPBACK) { + exti->stype = PREF_SID; + exti->instance = get_ext_pref_instance_value(); + exti->type = OPAQUE_TYPE_EXTENDED_PREFIX_LSA; + + zlog_debug( + "EXT (%s): Set Node SID to interface %s ", __func__, + oi->ifp->name); + + /* Complete SRDB if the interface belongs to a Prefix */ + if (OspfEXT.enabled) + ospf_sr_update_prefix(oi->ifp, oi->address); + } +} + +/* + * Finish Extended Link configuration and flood corresponding LSA + * when OSPF adjacency on this link fire up + */ +static void ospf_ext_link_nsm_change(struct ospf_neighbor *nbr, int old_status) +{ + struct ospf_interface *oi = nbr->oi; + struct ext_itf *exti; + uint32_t label; + + /* Process Neighbor only when its state is NSM Full */ + if (nbr->state != NSM_Full) + return; + + /* Get interface information for Segment Routing */ + exti = lookup_ext_by_ifp(oi->ifp); + if (exti == NULL) { + zlog_warn( + "EXT (%s): Cannot get Extended info. from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + if (oi->area == NULL || oi->area->ospf == NULL) { + zlog_warn( + "EXT (%s): Cannot refer to OSPF from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + /* Keep Area information in combination with SR info. */ + exti->area = oi->area; + OspfEXT.area = oi->area; + + /* Process only Adjacency/LAN SID */ + if (exti->stype == PREF_SID) + return; + + switch (oi->state) { + case ISM_PointToPoint: + /* Segment ID is an Adjacency one */ + exti->stype = ADJ_SID; + + /* Set Extended Link TLV with link_id == Nbr Router ID */ + set_ext_link(exti, OSPF_IFTYPE_POINTOPOINT, nbr->router_id, + oi->address->u.prefix4); + + /* Set Extended Link Adjacency SubTLVs, backup first */ + label = get_ext_link_label_value(); + set_adj_sid(exti, true, label, SID_LABEL); + label = get_ext_link_label_value(); + set_adj_sid(exti, false, label, SID_LABEL); + /* And Remote Interface address */ + set_rmt_itf_addr(exti, nbr->address.u.prefix4); + + break; + + case ISM_DR: + /* Segment ID is a LAN Adjacency for the DR only */ + exti->stype = LAN_ADJ_SID; + + /* Set Extended Link TLV with link_id == DR */ + set_ext_link(exti, OSPF_IFTYPE_BROADCAST, DR(oi), + oi->address->u.prefix4); + + /* Set Extended Link Adjacency SubTLVs, backup first */ + label = get_ext_link_label_value(); + set_lan_adj_sid(exti, true, label, SID_LABEL, nbr->router_id); + label = get_ext_link_label_value(); + set_lan_adj_sid(exti, false, label, SID_LABEL, nbr->router_id); + + break; + + case ISM_DROther: + case ISM_Backup: + /* Segment ID is an Adjacency if not the DR */ + exti->stype = ADJ_SID; + + /* Set Extended Link TLV with link_id == DR */ + set_ext_link(exti, OSPF_IFTYPE_BROADCAST, DR(oi), + oi->address->u.prefix4); + + /* Set Extended Link Adjacency SubTLVs, backup first */ + label = get_ext_link_label_value(); + set_adj_sid(exti, true, label, SID_LABEL); + label = get_ext_link_label_value(); + set_adj_sid(exti, false, label, SID_LABEL); + + break; + + default: + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + ospf_ext_link_lsa_schedule(exti, FLUSH_THIS_LSA); + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); + } + return; + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): Complete %s SID to interface %s ", __func__, + exti->stype == ADJ_SID ? "Adj." : "LAN Adj.", + oi->ifp->name); + + /* flood this links params if everything is ok */ + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); + if (OspfEXT.enabled) { + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_link_lsa_schedule(exti, REFRESH_THIS_LSA); + else + ospf_ext_link_lsa_schedule(exti, REORIGINATE_THIS_LSA); + } + +} + +/* Callbacks to handle Extended Link Segment Routing LSA information */ +static int ospf_ext_link_lsa_update(struct ospf_lsa *lsa) +{ + /* Sanity Check */ + if (lsa == NULL) { + zlog_warn("EXT (%s): Abort! LSA is NULL", __func__); + return -1; + } + + /* Process only Opaque LSA */ + if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) + && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) + return 0; + + /* Process only Extended Link LSA */ + if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) + != OPAQUE_TYPE_EXTENDED_LINK_LSA) + return 0; + + /* Check if Extended is enable */ + if (!OspfEXT.enabled) + return 0; + + /* Call Segment Routing LSA update or deletion */ + if (!IS_LSA_MAXAGE(lsa)) + ospf_sr_ext_link_lsa_update(lsa); + else + ospf_sr_ext_link_lsa_delete(lsa); + + return 0; +} + +/* Callbacks to handle Extended Prefix Segment Routing LSA information */ +static int ospf_ext_pref_lsa_update(struct ospf_lsa *lsa) +{ + + /* Sanity Check */ + if (lsa == NULL) { + zlog_warn("EXT (%s): Abort! LSA is NULL", __func__); + return -1; + } + + /* Process only Opaque LSA */ + if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) + && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) + return 0; + + /* Process only Extended Prefix LSA */ + if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) + != OPAQUE_TYPE_EXTENDED_PREFIX_LSA) + return 0; + + /* Check if it is not my LSA */ + if (IS_LSA_SELF(lsa)) + return 0; + + /* Check if Extended is enable */ + if (!OspfEXT.enabled) + return 0; + + /* Call Segment Routing LSA update or deletion */ + if (!IS_LSA_MAXAGE(lsa)) + ospf_sr_ext_prefix_lsa_update(lsa); + else + ospf_sr_ext_prefix_lsa_delete(lsa); + + return 0; +} + +/* + * ------------------------------------------------------- + * Followings are OSPF protocol processing functions for + * Extended Prefix/Link Opaque LSA + * ------------------------------------------------------- + */ + +static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) +{ + stream_put(s, tlvh, sizeof(struct tlv_header)); + +} + +static void build_tlv(struct stream *s, struct tlv_header *tlvh) +{ + + if ((tlvh != NULL) && (ntohs(tlvh->type) != 0)) { + build_tlv_header(s, tlvh); + stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); + } + +} + +/* Build an Extended Prefix Opaque LSA body for extended prefix TLV */ +static void ospf_ext_pref_lsa_body_set(struct stream *s, struct ext_itf *exti) +{ + + /* Sanity check */ + if ((exti == NULL) || (exti->stype != PREF_SID)) + return; + + /* Adjust Extended Prefix TLV size */ + TLV_LEN(exti->prefix) = + htons(ntohs(TLV_LEN(exti->node_sid)) + EXT_TLV_PREFIX_SIZE + + TLV_HDR_SIZE); + + /* Build LSA body for an Extended Prefix TLV */ + build_tlv_header(s, &exti->prefix.header); + stream_put(s, TLV_DATA(&exti->prefix.header), EXT_TLV_PREFIX_SIZE); + /* Then add Prefix SID SubTLV */ + build_tlv(s, &exti->node_sid.header); + +} + +/* Build an Extended Link Opaque LSA body for extended link TLV */ +static void ospf_ext_link_lsa_body_set(struct stream *s, struct ext_itf *exti) +{ + size_t size; + + /* Sanity check */ + if ((exti == NULL) + || ((exti->stype != ADJ_SID) && (exti->stype != LAN_ADJ_SID))) + return; + + if (exti->stype == ADJ_SID) { + /* Adjust Extended Link TLV size for Adj. SID */ + size = EXT_TLV_LINK_SIZE + 2 * EXT_SUBTLV_ADJ_SID_SIZE + + 2 * TLV_HDR_SIZE; + if (ntohs(TLV_TYPE(exti->rmt_itf_addr)) != 0) + size = size + EXT_SUBTLV_RMT_ITF_ADDR_SIZE + + TLV_HDR_SIZE; + TLV_LEN(exti->link) = htons(size); + + /* Build LSA body for an Extended Link TLV with Adj. SID */ + build_tlv_header(s, &exti->link.header); + stream_put(s, TLV_DATA(&exti->link.header), EXT_TLV_LINK_SIZE); + /* then add Ajacency SubTLVs */ + build_tlv(s, &exti->adj_sid[1].header); + build_tlv(s, &exti->adj_sid[0].header); + + /* Add Cisco experimental SubTLV if interface is PtoP */ + if (ntohs(TLV_TYPE(exti->rmt_itf_addr)) != 0) + build_tlv(s, &exti->rmt_itf_addr.header); + } else { + /* Adjust Extended Link TLV size for LAN SID */ + size = EXT_TLV_LINK_SIZE + + 2 * (EXT_SUBTLV_LAN_ADJ_SID_SIZE + TLV_HDR_SIZE); + TLV_LEN(exti->link) = htons(size); + + /* Build LSA body for an Extended Link TLV with LAN SID */ + build_tlv_header(s, &exti->link.header); + stream_put(s, &exti->link.header, EXT_TLV_LINK_SIZE); + /* then add LAN-Ajacency SubTLVs */ + build_tlv(s, &exti->lan_sid[1].header); + build_tlv(s, &exti->lan_sid[0].header); + } + +} + +/* Create new Extended Prefix opaque-LSA for every extended prefix */ +static struct ospf_lsa *ospf_ext_pref_lsa_new(struct ospf_area *area, + struct ext_itf *exti) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new = NULL; + struct ospf *top; + u_char options, lsa_type; + struct in_addr lsa_id; + struct in_addr router_id; + uint32_t tmp; + uint16_t length; + + /* Sanity Check */ + if (exti == NULL) + return NULL; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + if (s == NULL) { + zlog_warn("EXT (%s): stream_new() error", __func__); + return NULL; + } + + /* Prepare LSA Header */ + lsah = (struct lsa_header *)STREAM_DATA(s); + + lsa_type = OspfEXT.scope; + + /* + * LSA ID is a variable number identifying different instances of + * Extended Prefix Opaque LSA from the same router see RFC 7684 + */ + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, exti->instance); + lsa_id.s_addr = htonl(tmp); + + options = OSPF_OPTION_O; /* Don't forget this :-) */ + + /* Fix Options and Router ID depending of the flooding scope */ + if ((OspfEXT.scope == OSPF_OPAQUE_AS_LSA) || (area == NULL)) { + options = OSPF_OPTION_E; + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + router_id.s_addr = top ? top->router_id.s_addr : 0; + } else { + options |= LSA_OPTIONS_GET(area); /* Get area default option */ + options |= LSA_OPTIONS_NSSA_GET(area); + router_id = area->ospf->router_id; + } + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, router_id); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "EXT (%s): LSA[Type%u:%s]: Create an Opaque-LSA " + "Extended Prefix Opaque LSA instance", + __func__, lsa_type, inet_ntoa(lsa_id)); + + + /* Set opaque-LSA body fields. */ + ospf_ext_pref_lsa_body_set(s, exti); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new(); + if (new == NULL) { + zlog_warn("EXT (%s): ospf_lsa_new() error", __func__); + stream_free(s); + return NULL; + } + new->data = ospf_lsa_data_new(length); + if (new->data == NULL) { + zlog_warn("EXT (%s): ospf_lsa_data_new() error", __func__); + ospf_lsa_unlock(&new); + new = NULL; + stream_free(s); + return NULL; + } + + /* Segment Routing belongs only to default VRF */ + new->vrf_id = VRF_DEFAULT; + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Create new Extended Link opaque-LSA for every extended link TLV */ +static struct ospf_lsa *ospf_ext_link_lsa_new(struct ospf_area *area, + struct ext_itf *exti) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new = NULL; + u_char options, lsa_type; + struct in_addr lsa_id; + uint32_t tmp; + uint16_t length; + + /* Sanity Check */ + if (exti == NULL) + return NULL; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + if (s == NULL) { + zlog_warn("EXT (%s): stream_new() error", __func__); + return NULL; + } + lsah = (struct lsa_header *)STREAM_DATA(s); + + options = OSPF_OPTION_O; /* Don't forget this :-) */ + options |= LSA_OPTIONS_GET(area); /* Get area default option */ + options |= LSA_OPTIONS_NSSA_GET(area); + /* Extended Link Opaque LSA are only flooded within an area */ + lsa_type = OSPF_OPAQUE_AREA_LSA; + + /* + * LSA ID is a variable number identifying different instances of + * Extended Link Opaque LSA from the same router see RFC 7684 + */ + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); + lsa_id.s_addr = htonl(tmp); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "EXT (%s) LSA[Type%u:%s]: Create an Opaque-LSA " + "Extended Link Opaque LSA instance", + __func__, lsa_type, inet_ntoa(lsa_id)); + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, area->ospf->router_id); + + /* Set opaque-LSA body fields. */ + ospf_ext_link_lsa_body_set(s, exti); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new(); + if (new == NULL) { + zlog_warn("EXT (%s): ospf_lsa_new() error", __func__); + stream_free(s); + return NULL; + } + new->data = ospf_lsa_data_new(length); + if (new->data == NULL) { + zlog_warn("EXT (%s): ospf_lsa_data_new() error", __func__); + ospf_lsa_unlock(&new); + new = NULL; + stream_free(s); + return NULL; + } + + /* Segment Routing belongs only to default VRF */ + new->vrf_id = VRF_DEFAULT; + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* + * Process the origination of an Extended Prefix Opaque LSA + * for every extended prefix TLV + */ +static int ospf_ext_pref_lsa_originate1(struct ospf_area *area, + struct ext_itf *exti) +{ + struct ospf_lsa *new; + int rc = -1; + + + /* Create new Opaque-LSA/Extended Prefix Opaque LSA instance. */ + new = ospf_ext_pref_lsa_new(area, exti); + if (new == NULL) { + zlog_warn("EXT (%s): ospf_ext_pref_lsa_new() error", __func__); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(area->ospf, NULL /*oi */, new) == NULL) { + zlog_warn("EXT (%s): ospf_lsa_install() error", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Now this Extended Prefix Opaque LSA info parameter entry has + * associated LSA. + */ + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + + /* Update new LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flood new LSA through area. */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + char area_id[INET_ADDRSTRLEN]; + + strncpy(area_id, inet_ntoa(area->area_id), INET_ADDRSTRLEN); + zlog_debug( + "EXT (%s): LSA[Type%u:%s]: Originate Opaque-LSA " + "Extended Prefix Opaque LSA: Area(%s), Link(%s)", + __func__, new->data->type, inet_ntoa(new->data->id), + area_id, exti->ifp->name); + ospf_lsa_header_dump(new->data); + } + + rc = 0; + + return rc; +} + +/* + * Process the origination of an Extended Link Opaque LSA + * for every extended link TLV + */ +static int ospf_ext_link_lsa_originate1(struct ospf_area *area, + struct ext_itf *exti) +{ + struct ospf_lsa *new; + int rc = -1; + + /* Create new Opaque-LSA/Extended Link Opaque LSA instance. */ + new = ospf_ext_link_lsa_new(area, exti); + if (new == NULL) { + zlog_warn("EXT (%s): ospf_ext_link_lsa_new() error", __func__); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(area->ospf, NULL /*oi */, new) == NULL) { + zlog_warn("EXT (%s): ospf_lsa_install() error", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Now this link-parameter entry has associated LSA. */ + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + + /* Update new LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flood new LSA through area. */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + char area_id[INET_ADDRSTRLEN]; + + strncpy(area_id, inet_ntoa(area->area_id), INET_ADDRSTRLEN); + zlog_debug( + "EXT (%s): LSA[Type%u:%s]: Originate Opaque-LSA " + "Extended Link Opaque LSA: Area(%s), Link(%s)", + __func__, new->data->type, inet_ntoa(new->data->id), + area_id, exti->ifp->name); + ospf_lsa_header_dump(new->data); + } + + rc = 0; + + return rc; +} + +/* Trigger the origination of Extended Prefix Opaque LSAs */ +static int ospf_ext_pref_lsa_originate(void *arg) +{ + struct ospf_area *area = (struct ospf_area *)arg; + struct listnode *node; + struct ext_itf *exti; + int rc = -1; + + if (!OspfEXT.enabled) { + zlog_info( + "EXT (%s): Segment Routing " + "functionality is Disabled now", __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): Start Originate Prefix LSA for area %s", + __func__, inet_ntoa(area->area_id)); + + /* Check if Extended Prefix Opaque LSA is already engaged */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { + + /* Process only Prefix SID */ + if (exti->stype != PREF_SID) + continue; + + /* Process only Extended Prefix with valid Area ID */ + if ((exti->area == NULL) + || (!IPV4_ADDR_SAME(&exti->area->area_id, &area->area_id))) + continue; + + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + if (CHECK_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH)) { + zlog_warn( + "EXT (%s): Refresh instead of " + "Originate", __func__); + UNSET_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH); + ospf_ext_pref_lsa_schedule(exti, + REFRESH_THIS_LSA); + } + continue; + } + + /* Ok, let's try to originate an LSA */ + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): Let's finally reoriginate the " + "LSA 7.0.0.%u for Itf %s", + __func__, exti->instance, + exti->ifp ? exti->ifp->name : ""); + ospf_ext_pref_lsa_originate1(area, exti); + } + + rc = 0; + return rc; +} + +/* Trigger the origination of Extended Link Opaque LSAs */ +static int ospf_ext_link_lsa_originate(void *arg) +{ + struct ospf_area *area = (struct ospf_area *)arg; + struct listnode *node; + struct ext_itf *exti; + int rc = -1; + + if (!OspfEXT.enabled) { + zlog_info( + "EXT (%s): Segment Routing " + "functionality is Disabled now", __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + + /* Check if Extended Prefix Opaque LSA is already engaged */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { + /* Process only Adjacency or LAN SID */ + if (exti->stype == PREF_SID) + continue; + + /* Process only Extended Link with valid Area ID */ + if ((exti->area == NULL) + || (!IPV4_ADDR_SAME(&exti->area->area_id, &area->area_id))) + continue; + + /* Check if LSA not already engaged */ + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + if (CHECK_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH)) { + zlog_warn( + "EXT (%s): Refresh instead of " + "Originate", __func__); + UNSET_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH); + ospf_ext_link_lsa_schedule(exti, + REFRESH_THIS_LSA); + } + continue; + } + + /* Ok, let's try to originate an LSA */ + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "EXT (%s): Let's finally reoriginate the " + "LSA 8.0.0.%u for Itf %s through the Area %s", + __func__, exti->instance, + exti->ifp ? exti->ifp->name : "-", + inet_ntoa(area->area_id)); + ospf_ext_link_lsa_originate1(area, exti); + } + + rc = 0; + return rc; +} + +/* Refresh an Extended Prefix Opaque LSA */ +static struct ospf_lsa *ospf_ext_pref_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf_lsa *new = NULL; + struct ospf_area *area = lsa->area; + struct ospf *top; + struct ext_itf *exti; + + if (!OspfEXT.enabled) { + /* + * This LSA must have flushed before due to Extended Prefix + * Opaque LSA status change. + * It seems a slip among routers in the routing domain. + */ + zlog_info( + "EXT (%s): Segment Routing functionality is " + "Disabled", __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Lookup this lsa corresponding Extended parameters */ + exti = lookup_ext_by_instance(lsa); + if (exti == NULL) { + zlog_warn("EXT (%s): Invalid parameter LSA ID", __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Check if Interface was not disable in the interval */ + if ((exti != NULL) && !CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { + zlog_warn("EXT (%s): Interface was Disabled: Flush it!", + __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* If the lsa's age reached to MaxAge, start flushing procedure. */ + if (IS_LSA_MAXAGE(lsa)) { + if (exti) + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + + /* Create new Opaque-LSA/Extended Prefix Opaque LSA instance. */ + new = ospf_ext_pref_lsa_new(area, exti); + + if (new == NULL) { + zlog_warn("EXT (%s): ospf_ext_pref_lsa_new() error", __func__); + return NULL; + } + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* + * Install this LSA into LSDB + * Given "lsa" will be freed in the next function + * As area could be NULL i.e. when using OPAQUE_LSA_AS, we prefer to use + * ospf_lookup() to get ospf instance + */ + if (area) + top = area->ospf; + else + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + zlog_warn("EXT (%s): ospf_lsa_install() error", __func__); + ospf_lsa_unlock(&new); + return NULL; + } + + /* Flood updated LSA through the Prefix Area according to the RFC7684 */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "EXT (%s): LSA[Type%u:%s] Refresh Extended Prefix LSA", + __func__, new->data->type, inet_ntoa(new->data->id)); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +/* Refresh an Extended Link Opaque LSA */ +static struct ospf_lsa *ospf_ext_link_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ext_itf *exti; + struct ospf_area *area = lsa->area; + struct ospf *top = area->ospf; + struct ospf_lsa *new = NULL; + + if (!OspfEXT.enabled) { + /* + * This LSA must have flushed before due to OSPF-SR status + * change. It seems a slip among routers in the routing domain. + */ + zlog_info( + "EXT (%s): Segment Routing functionality is Disabled", + __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Lookup this LSA corresponding Extended parameters */ + exti = lookup_ext_by_instance(lsa); + if (exti == NULL) { + zlog_warn("EXT (%s): Invalid parameter LSA ID", __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Check if Interface was not disable in the interval */ + if ((exti != NULL) && !CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { + zlog_warn( + "EXT (%s): Interface was Disabled: Flush it!", + __func__); + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* If the lsa's age reached to MaxAge, start flushing procedure */ + if (IS_LSA_MAXAGE(lsa)) { + if (exti) + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + + /* Create new Opaque-LSA/Extended Link instance */ + new = ospf_ext_link_lsa_new(area, exti); + if (new == NULL) { + zlog_warn("EXT (%s): Error creating new LSA", __func__); + return NULL; + } + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* Install this LSA into LSDB. */ + /* Given "lsa" will be freed in the next function */ + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + zlog_warn("EXT (%s): Error installing new LSA", __func__); + ospf_lsa_unlock(&new); + return NULL; + } + + /* Flood updated LSA through the link Area according to the RFC7684 */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "EXT (%s): LSA[Type%u:%s]: Refresh Extended Link LSA", + __func__, new->data->type, inet_ntoa(new->data->id)); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +/* Schedule Extended Prefix Opaque LSA origination/refreshment/flushing */ +static void ospf_ext_pref_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode) +{ + struct ospf_lsa lsa; + struct lsa_header lsah; + struct ospf *top; + uint32_t tmp; + + memset(&lsa, 0, sizeof(lsa)); + memset(&lsah, 0, sizeof(lsah)); + + /* Sanity Check */ + if (exti == NULL) + return; + + /* Check if the corresponding link is ready to be flooded */ + if (!(CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE))) + return; + + zlog_debug( + "EXT (%s): Schedule %s%s%s LSA for interface %s", __func__, + opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", + opcode == REFRESH_THIS_LSA ? "Refresh" : "", + opcode == FLUSH_THIS_LSA ? "Flush" : "", + exti->ifp ? exti->ifp->name : "-"); + + /* Set LSA header information */ + if (exti->area == NULL) { + zlog_warn( + "EXT (%s): Flooding is Area scope but area is not yet " + "set", __func__); + if (OspfEXT.area == NULL) { + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + OspfEXT.area = ospf_area_lookup_by_area_id( + top, OspfEXT.area_id); + } + exti->area = OspfEXT.area; + } + lsa.area = exti->area; + lsa.data = &lsah; + lsah.type = OSPF_OPAQUE_AREA_LSA; + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, exti->instance); + lsah.id.s_addr = htonl(tmp); + + switch (opcode) { + case REORIGINATE_THIS_LSA: + ospf_opaque_lsa_reoriginate_schedule( + (void *)exti->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_EXTENDED_PREFIX_LSA); + break; + case REFRESH_THIS_LSA: + ospf_opaque_lsa_refresh_schedule(&lsa); + break; + case FLUSH_THIS_LSA: + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(&lsa); + break; + default: + zlog_warn("EXT (%s): Unknown opcode", __func__); + break; + } + +} + +/* Schedule Extended Link Opaque LSA origination/refreshment/flushing */ +static void ospf_ext_link_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode) +{ + struct ospf_lsa lsa; + struct lsa_header lsah; + struct ospf *top; + uint32_t tmp; + + memset(&lsa, 0, sizeof(lsa)); + memset(&lsah, 0, sizeof(lsah)); + + /* Sanity Check */ + if (exti == NULL) + return; + + /* Check if the corresponding link is ready to be flooded */ + if (!(CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE))) + return; + + zlog_debug( + "EXT (%s): Schedule %s%s%s LSA for interface %s", __func__, + opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", + opcode == REFRESH_THIS_LSA ? "Refresh" : "", + opcode == FLUSH_THIS_LSA ? "Flush" : "", + exti->ifp ? exti->ifp->name : "-"); + + /* Set LSA header information */ + if (exti->area == NULL) { + zlog_warn( + "EXT (%s): Flooding is Area scope but area is not " + "yet set", __func__); + if (OspfEXT.area == NULL) { + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + OspfEXT.area = ospf_area_lookup_by_area_id( + top, OspfEXT.area_id); + } + exti->area = OspfEXT.area; + } + lsa.area = exti->area; + lsa.data = &lsah; + lsah.type = OSPF_OPAQUE_AREA_LSA; + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); + lsah.id.s_addr = htonl(tmp); + + switch (opcode) { + case REORIGINATE_THIS_LSA: + ospf_opaque_lsa_reoriginate_schedule( + (void *)exti->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_EXTENDED_LINK_LSA); + break; + case REFRESH_THIS_LSA: + ospf_opaque_lsa_refresh_schedule(&lsa); + break; + case FLUSH_THIS_LSA: + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(&lsa); + break; + default: + zlog_warn("EXT (%s): Unknown opcode", __func__); + break; + } + +} + +/* Schedule Extended Link or Prefix depending of the Type of LSA */ +static void ospf_ext_lsa_schedule(struct ext_itf *exti, enum lsa_opcode op) +{ + + if (exti->stype == PREF_SID) + ospf_ext_pref_lsa_schedule(exti, op); + else + ospf_ext_link_lsa_schedule(exti, op); +} + +/* + * ------------------------------------ + * Followings are vty show functions. + * ------------------------------------ + */ + +/* Cisco experimental SubTLV */ +static uint16_t show_vty_ext_link_rmt_itf_addr(struct vty *vty, + struct tlv_header *tlvh) +{ + struct ext_subtlv_rmt_itf_addr *top; + + top = (struct ext_subtlv_rmt_itf_addr *)tlvh; + + vty_out(vty, + " Remote Interface Address Sub-TLV: Length %u\n " + "Address: %s\n", + ntohs(top->header.length), inet_ntoa(top->value)); + + return TLV_SIZE(tlvh); +} + +/* Adjacency SID SubTLV */ +static uint16_t show_vty_ext_link_adj_sid(struct vty *vty, + struct tlv_header *tlvh) +{ + struct ext_subtlv_adj_sid *top = (struct ext_subtlv_adj_sid *)tlvh; + + vty_out(vty, + " Adj-SID Sub-TLV: Length %u\n\tFlags: " + "0x%x\n\tMT-ID:0x%x\n\tWeight: 0x%x\n\t%s: %u\n", + ntohs(top->header.length), top->flags, top->mtid, top->weight, + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) ? "Label" + : "Index", + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? GET_LABEL(ntohl(top->value)) + : ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +/* LAN Adjacency SubTLV */ +static uint16_t show_vty_ext_link_lan_adj_sid(struct vty *vty, + struct tlv_header *tlvh) +{ + struct ext_subtlv_lan_adj_sid *top = + (struct ext_subtlv_lan_adj_sid *)tlvh; + + vty_out(vty, + " LAN-Adj-SID Sub-TLV: Length %u\n\tFlags: " + "0x%x\n\tMT-ID:0x%x\n\tWeight: 0x%x\n\tNeighbor ID: " + "%s\n\tLabel: %u\n", + ntohs(top->header.length), top->flags, top->mtid, top->weight, + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) ? "Label" + : "Index", + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? GET_LABEL(ntohl(top->value)) + : ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh) +{ + vty_out(vty, " Unknown TLV: [type(0x%x), length(0x%x)]\n", + ntohs(tlvh->type), ntohs(tlvh->length)); + + return TLV_SIZE(tlvh); +} + +/* Extended Link Sub TLVs */ +static uint16_t show_vty_link_info(struct vty *vty, struct tlv_header *ext) +{ + struct ext_tlv_link *top = (struct ext_tlv_link *)ext; + struct tlv_header *tlvh; + uint16_t length = ntohs(top->header.length) - 3 * sizeof(uint32_t); + uint16_t sum = 0; + + vty_out(vty, + " Extended Link TLV: Length %u\n Link Type: 0x%x\n" + " Link ID: %s\n", + ntohs(top->header.length), top->link_type, + inet_ntoa(top->link_id)); + vty_out(vty, " Link data: %s\n", inet_ntoa(top->link_data)); + + tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + + EXT_TLV_LINK_SIZE); + for (; sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_SUBTLV_ADJ_SID: + sum += show_vty_ext_link_adj_sid(vty, tlvh); + break; + case EXT_SUBTLV_LAN_ADJ_SID: + sum += show_vty_ext_link_lan_adj_sid(vty, tlvh); + break; + case EXT_SUBTLV_RMT_ITF_ADDR: + sum += show_vty_ext_link_rmt_itf_addr(vty, tlvh); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh); + break; + } + } + + return sum + sizeof(struct ext_tlv_link); +} + +/* Extended Link TLVs */ +static void ospf_ext_link_show_info(struct vty *vty, struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + struct tlv_header *tlvh; + uint16_t length = 0, sum = 0; + + /* Initialize TLV browsing */ + length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length; + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_TLV_LINK: + sum += show_vty_link_info(vty, tlvh); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh); + break; + } + } + +} + +/* Prefix SID SubTLV */ +static uint16_t show_vty_ext_pref_pref_sid(struct vty *vty, + struct tlv_header *tlvh) +{ + struct ext_subtlv_prefix_sid *top = + (struct ext_subtlv_prefix_sid *)tlvh; + + vty_out(vty, + " Prefix SID Sub-TLV: Length %u\n\tAlgorithm: " + "%u\n\tFlags: 0x%x\n\tMT-ID:0x%x\n\t%s: %u\n", + ntohs(top->header.length), top->algorithm, top->flags, + top->mtid, + CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) ? "Label" + : "Index", + CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) + ? GET_LABEL(ntohl(top->value)) + : ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +/* Extended Prefix SubTLVs */ +static uint16_t show_vty_pref_info(struct vty *vty, struct tlv_header *ext) +{ + struct ext_tlv_prefix *top = (struct ext_tlv_prefix *)ext; + struct tlv_header *tlvh; + uint16_t length = ntohs(top->header.length) - 2 * sizeof(uint32_t); + uint16_t sum = 0; + + vty_out(vty, + " Extended Prefix TLV: Length %u\n\tRoute Type: %u\n" + "\tAddress Family: 0x%x\n\tFlags: 0x%x\n\tAddress: %s/%u\n", + ntohs(top->header.length), top->route_type, top->af, top->flags, + inet_ntoa(top->address), top->pref_length); + + tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + + EXT_TLV_PREFIX_SIZE); + for (; sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_SUBTLV_PREFIX_SID: + sum += show_vty_ext_pref_pref_sid(vty, tlvh); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh); + break; + } + } + + return sum + sizeof(struct ext_tlv_prefix); +} + +/* Extended Prefix TLVs */ +static void ospf_ext_pref_show_info(struct vty *vty, struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + struct tlv_header *tlvh; + uint16_t length = 0, sum = 0; + + /* Initialize TLV browsing */ + length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length; + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_TLV_PREFIX: + sum += show_vty_pref_info(vty, tlvh); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh); + break; + } + } + +} diff --git a/ospfd/ospf_ext.h b/ospfd/ospf_ext.h new file mode 100644 index 0000000000..67280754dd --- /dev/null +++ b/ospfd/ospf_ext.h @@ -0,0 +1,200 @@ +/* + * This is an implementation of RFC7684 OSPFv2 Prefix/Link Attribute + * Advertisement + * + * Module name: Extended Prefix/Link Opaque LSA header definition + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Author: Anselme Sawadogo <anselmesawadogo@gmail.com> + * + * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_OSPF_EXT_PREF_H_ +#define _FRR_OSPF_EXT_PREF_H_ + +/* + * Opaque LSA's link state ID for Extended Prefix/Link is + * structured as follows. + * + * 24 16 8 0 + * +--------+--------+--------+--------+ + * | 7/8 |........|........|........| + * +--------+--------+--------+--------+ + * |<-Type->|<------- Instance ------->| + * + * + * Type: IANA has assigned '7' for Extended Prefix Opaque LSA + * and '8' for Extended Link Opaque LSA + * Instance: User may select arbitrary 24-bit values to identify + * different instances of Extended Prefix/Link Opaque LSA + * + */ + +/* + * 24 16 8 0 + * +--------+--------+--------+--------+ --- + * | LS age |Options | 10,11 | A + * +--------+--------+--------+--------+ | Standard (Opaque) LSA header; + * | 7/8 | Instance | | + * +--------+--------+--------+--------+ | Type 10 or 11 are used for Extended + * | Advertising router | | Prefix Opaque LSA + * +--------+--------+--------+--------+ | + * | LS sequence number | | Type 10 only is used for Extended + * +--------+--------+--------+--------+ | Link Opaque LSA + * | LS checksum | Length | V + * +--------+--------+--------+--------+ --- + * | Type | Length | A + * +--------+--------+--------+--------+ | TLV part for Extended Prefix/Link + * | | | Opaque LSA; + * ~ Values ... ~ | Values might be structured as a set + * | | V of sub-TLVs. + * +--------+--------+--------+--------+ --- + */ + +/* Global use constant numbers */ + +#define MAX_LEGAL_EXT_INSTANCE_NUM (0xffff) +#define LEGAL_EXT_INSTANCE_RANGE(i) (0 <= (i) && (i) <= 0xffff) + +/* Flags to manage Extended Link/Prefix Opaque LSA */ +#define EXT_LPFLG_LSA_INACTIVE 0x00 +#define EXT_LPFLG_LSA_ACTIVE 0x01 +#define EXT_LPFLG_LSA_ENGAGED 0x02 +#define EXT_LPFLG_LSA_LOOKUP_DONE 0x04 +#define EXT_LPFLG_LSA_FORCED_REFRESH 0x08 +#define EXT_LPFLG_FIB_ENTRY_SET 0x10 + +/* + * Following section defines TLV (tag, length, value) structures, + * used in Extended Prefix/Link Opaque LSA. + */ + +/* Extended Prefix TLV Route Types */ +#define EXT_TLV_PREF_ROUTE_UNSPEC 0 +#define EXT_TLV_PREF_ROUTE_INTRA_AREA 1 +#define EXT_TLV_PREF_ROUTE_INTER_AREA 3 +#define EXT_TLV_PREF_ROUTE_AS_EXT 5 +#define EXT_TLV_PREF_ROUTE_NSSA_EXT 7 + +/* + * Extended Prefix and Extended Prefix Range TLVs' + * Address family flag for IPv4 + */ +#define EXT_TLV_PREF_AF_IPV4 0 + +/* Extended Prefix TLV Flags */ +#define EXT_TLV_PREF_AFLG 0x80 +#define EXT_TLV_PREF_NFLG 0x40 + +/* Extended Prefix Range TLV Flags */ +#define EXT_TLV_PREF_RANGE_IAFLG 0x80 + +/* ERO subtlvs Flags */ +#define EXT_SUBTLV_ERO_LFLG 0x80 + +/* Extended Prefix TLV see RFC 7684 section 2.1 */ +#define EXT_TLV_PREFIX 1 +#define EXT_TLV_PREFIX_SIZE 8 +struct ext_tlv_prefix { + struct tlv_header header; + uint8_t route_type; + uint8_t pref_length; + uint8_t af; + uint8_t flags; + struct in_addr address; +}; + +/* Extended Link TLV see RFC 7684 section 3.1 */ +#define EXT_TLV_LINK 1 +#define EXT_TLV_LINK_SIZE 12 +struct ext_tlv_link { + struct tlv_header header; + uint8_t link_type; + uint8_t reserved[3]; + struct in_addr link_id; + struct in_addr link_data; +}; + +/* Remote Interface Address Sub-TLV, Cisco experimental use Sub-TLV */ +#define EXT_SUBTLV_RMT_ITF_ADDR 32768 +#define EXT_SUBTLV_RMT_ITF_ADDR_SIZE 4 +struct ext_subtlv_rmt_itf_addr { + struct tlv_header header; + struct in_addr value; +}; + +/* Internal structure to manage Extended Link/Prefix Opaque LSA */ +struct ospf_ext_lp { + bool enabled; + + /* Flags to manage this Extended Prefix/Link Opaque LSA */ + uint32_t flags; + + /* + * Scope is area Opaque Type 10 or AS Opaque LSA Type 11 for + * Extended Prefix and area Opaque Type 10 for Extended Link + */ + uint8_t scope; + + /* area pointer if flooding is Type 10 Null if flooding is AS scope */ + struct ospf_area *area; + struct in_addr area_id; + + /* List of interface with Segment Routing enable */ + struct list *iflist; +}; + +/* Structure to aggregate interfaces information for Extended Prefix/Link */ +struct ext_itf { + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + uint8_t type; /* Extended Prefix (7) or Link (8) */ + + /* Reference pointer to a Zebra-interface. */ + struct interface *ifp; + + /* Area info in which this SR link belongs to. */ + struct ospf_area *area; + + /* Flags to manage this link parameters. */ + uint32_t flags; + + /* SID type: Node, Adjacency or LAN Adjacency */ + enum sid_type stype; + + /* extended link/prefix TLV information */ + struct ext_tlv_prefix prefix; + struct ext_subtlv_prefix_sid node_sid; + struct ext_tlv_link link; + struct ext_subtlv_adj_sid adj_sid[2]; + struct ext_subtlv_lan_adj_sid lan_sid[2]; + + /* cisco experimental subtlv */ + struct ext_subtlv_rmt_itf_addr rmt_itf_addr; +}; + +/* Prototypes. */ +extern int ospf_ext_init(void); +extern void ospf_ext_term(void); +extern void ospf_ext_finish(void); +extern void ospf_ext_update_sr(bool enable); +extern uint32_t ospf_ext_schedule_prefix_index(struct interface *ifp, + uint32_t index, + struct prefix_ipv4 *p, + uint8_t flags); +#endif /* _FRR_OSPF_EXT_PREF_H_ */ diff --git a/ospfd/ospf_memory.c b/ospfd/ospf_memory.c index cdc9b929fa..1332104b0a 100644 --- a/ospfd/ospf_memory.c +++ b/ospfd/ospf_memory.c @@ -53,3 +53,5 @@ DEFINE_MTYPE(OSPFD, OSPF_IF_PARAMS, "OSPF if params") DEFINE_MTYPE(OSPFD, OSPF_MESSAGE, "OSPF message") DEFINE_MTYPE(OSPFD, OSPF_MPLS_TE, "OSPF MPLS parameters") DEFINE_MTYPE(OSPFD, OSPF_PCE_PARAMS, "OSPF PCE parameters") +DEFINE_MTYPE(OSPFD, OSPF_EXT_PARAMS, "OSPF Extended parameters") +DEFINE_MTYPE(OSPFD, OSPF_SR_PARAMS, "OSPF Segment Routing parameters") diff --git a/ospfd/ospf_memory.h b/ospfd/ospf_memory.h index 5f5960eec7..50c6f33ecf 100644 --- a/ospfd/ospf_memory.h +++ b/ospfd/ospf_memory.h @@ -52,5 +52,7 @@ DECLARE_MTYPE(OSPF_IF_PARAMS) DECLARE_MTYPE(OSPF_MESSAGE) DECLARE_MTYPE(OSPF_MPLS_TE) DECLARE_MTYPE(OSPF_PCE_PARAMS) +DECLARE_MTYPE(OSPF_SR_PARAMS) +DECLARE_MTYPE(OSPF_EXT_PARAMS) #endif /* _QUAGGA_OSPF_MEMORY_H */ diff --git a/ospfd/ospf_opaque.c b/ospfd/ospf_opaque.c index 1bd6bf8d4c..1c586d252c 100644 --- a/ospfd/ospf_opaque.c +++ b/ospfd/ospf_opaque.c @@ -50,6 +50,10 @@ #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_te.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_ext.h" DEFINE_MTYPE_STATIC(OSPFD, OSPF_OPAQUE_FUNCTAB, "OSPF opaque function table") DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_TYPE, "OSPF opaque per-type info") @@ -59,9 +63,6 @@ DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_ID, "OSPF opaque per-ID info") * Followings are initialize/terminate functions for Opaque-LSAs handling. *------------------------------------------------------------------------*/ -#include "ospfd/ospf_te.h" -#include "ospfd/ospf_ri.h" - #ifdef SUPPORT_OSPF_API int ospf_apiserver_init(void); void ospf_apiserver_term(void); @@ -85,9 +86,16 @@ void ospf_opaque_init(void) if (ospf_mpls_te_init() != 0) exit(1); + /* Segment Routing init */ + if (ospf_sr_init() != 0) + exit(1); + if (ospf_router_info_init() != 0) exit(1); + if (ospf_ext_init() != 0) + exit(1); + #ifdef SUPPORT_OSPF_API if ((ospf_apiserver_enable) && (ospf_apiserver_init() != 0)) exit(1); @@ -102,6 +110,10 @@ void ospf_opaque_term(void) ospf_router_info_term(); + ospf_ext_term(); + + ospf_sr_term(); + #ifdef SUPPORT_OSPF_API ospf_apiserver_term(); #endif /* SUPPORT_OSPF_API */ @@ -110,6 +122,15 @@ void ospf_opaque_term(void) return; } +void ospf_opaque_finish(void) +{ + ospf_router_info_finish(); + + ospf_ext_finish(); + + ospf_sr_finish(); +} + int ospf_opaque_type9_lsa_init(struct ospf_interface *oi) { if (oi->opaque_lsa_self != NULL) @@ -209,6 +230,12 @@ static const char *ospf_opaque_type_name(u_char opaque_type) case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: name = "Router Information LSA"; break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + name = "Extended Prefix Opaque LSA"; + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + name = "Extended Link Opaque LSA"; + break; default: if (OPAQUE_TYPE_RANGE_UNASSIGNED(opaque_type)) name = "Unassigned"; @@ -1762,7 +1789,7 @@ void ospf_opaque_lsa_reoriginate_schedule(void *lsa_type_dependent, lsa_type, delay, GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); - OSPF_OPAQUE_TIMER_ON(oipt->t_opaque_lsa_self, func, oipt, delay * 1000); + OSPF_OPAQUE_TIMER_ON(oipt->t_opaque_lsa_self, func, oipt, delay); out: return; diff --git a/ospfd/ospf_opaque.h b/ospfd/ospf_opaque.h index 9dc1f92f4d..632b7b039e 100644 --- a/ospfd/ospf_opaque.h +++ b/ospfd/ospf_opaque.h @@ -59,7 +59,9 @@ #define OPAQUE_TYPE_L1VPN_LSA 5 #define OPAQUE_TYPE_ROUTER_INFORMATION_LSA 4 #define OPAQUE_TYPE_INTER_AS_LSA 6 -#define OPAQUE_TYPE_MAX 6 +#define OPAQUE_TYPE_EXTENDED_PREFIX_LSA 7 +#define OPAQUE_TYPE_EXTENDED_LINK_LSA 8 +#define OPAQUE_TYPE_MAX 8 /* Followings types are proposed in internet-draft documents. */ #define OPAQUE_TYPE_8021_QOSPF 129 @@ -120,6 +122,7 @@ enum lsa_opcode { extern void ospf_opaque_init(void); extern void ospf_opaque_term(void); +extern void ospf_opaque_finish(void); extern int ospf_opaque_type9_lsa_init(struct ospf_interface *oi); extern void ospf_opaque_type9_lsa_term(struct ospf_interface *oi); extern int ospf_opaque_type10_lsa_init(struct ospf_area *area); diff --git a/ospfd/ospf_ri.c b/ospfd/ospf_ri.c index ead6923435..7c7a6fd795 100644 --- a/ospfd/ospf_ri.c +++ b/ospfd/ospf_ri.c @@ -3,9 +3,8 @@ * with support of RFC5088 PCE Capabilites announcement * * Module name: Router Information - * Version: 0.99.22 - * Created: 2012-02-01 by Olivier Dugeon - * Copyright (C) 2012 Orange Labs http://www.orange.com/ + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/ * * This file is part of GNU Quagga. * @@ -39,6 +38,7 @@ #include "thread.h" #include "hash.h" #include "sockunion.h" /* for inet_aton() */ +#include "mpls.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" @@ -55,8 +55,8 @@ #include "ospfd/ospf_route.h" #include "ospfd/ospf_ase.h" #include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_sr.h" #include "ospfd/ospf_ri.h" -#include "ospfd/ospf_te.h" /* Store Router Information PCE TLV and SubTLV in network byte order. */ struct ospf_pce_info { @@ -69,6 +69,23 @@ struct ospf_pce_info { struct ri_pce_subtlv_cap_flag pce_cap_flag; }; +/* + * Store Router Information Segment Routing TLV and SubTLV + * in network byte order + */ +struct ospf_ri_sr_info { + bool enabled; + /* Algorithms supported by the node */ + struct ri_sr_tlv_sr_algorithm algo; + /* + * Segment Routing Global Block i.e. label range + * Only one range supported in this code + */ + struct ri_sr_tlv_sid_label_range range; + /* Maximum SID Depth supported by the node */ + struct ri_sr_tlv_node_msd msd; +}; + /* Following structure are internal use only. */ struct ospf_router_info { bool enabled; @@ -77,7 +94,7 @@ struct ospf_router_info { u_int8_t scope; /* Flags to manage this router information. */ -#define RIFLG_LSA_ENGAGED 0x1 +#define RIFLG_LSA_ENGAGED 0x1 #define RIFLG_LSA_FORCED_REFRESH 0x2 u_int32_t flags; @@ -90,6 +107,9 @@ struct ospf_router_info { /* Store PCE capability LSA */ struct ospf_pce_info pce_info; + + /* Store SR capability LSA */ + struct ospf_ri_sr_info sr_info; }; /* @@ -113,15 +133,19 @@ static int ospf_router_info_lsa_originate(void *arg); static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa); static void ospf_router_info_lsa_schedule(enum lsa_opcode opcode); static void ospf_router_info_register_vty(void); +static int ospf_router_info_lsa_update(struct ospf_lsa *lsa); static void del_pce_info(void *val); int ospf_router_info_init(void) { + zlog_info("RI -> Initialize Router Information"); + memset(&OspfRI, 0, sizeof(struct ospf_router_info)); OspfRI.enabled = false; OspfRI.registered = 0; OspfRI.scope = OSPF_OPAQUE_AS_LSA; + OspfRI.area_id.s_addr = 0; OspfRI.flags = 0; /* Initialize pce domain and neighbor list */ @@ -131,6 +155,9 @@ int ospf_router_info_init(void) OspfRI.pce_info.pce_neighbor = list_new(); OspfRI.pce_info.pce_neighbor->del = del_pce_info; + /* Initialize Segment Routing information structure */ + OspfRI.sr_info.enabled = false; + ospf_router_info_register_vty(); return 0; @@ -143,19 +170,22 @@ static int ospf_router_info_register(u_int8_t scope) if (OspfRI.registered) return rc; - zlog_info("Register Router Information with scope %s(%d)", + zlog_info("RI -> Register Router Information with scope %s(%d)", scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS", scope); rc = ospf_register_opaque_functab( scope, OPAQUE_TYPE_ROUTER_INFORMATION_LSA, NULL, /* new interface */ NULL, /* del interface */ - ospf_router_info_ism_change, ospf_router_info_nsm_change, + ospf_router_info_ism_change, + ospf_router_info_nsm_change, ospf_router_info_config_write_router, NULL, /* Config. write interface */ NULL, /* Config. write debug */ - ospf_router_info_show_info, ospf_router_info_lsa_originate, - ospf_router_info_lsa_refresh, NULL, /* new_lsa_hook */ - NULL); /* del_lsa_hook */ + ospf_router_info_show_info, + ospf_router_info_lsa_originate, + ospf_router_info_lsa_refresh, + ospf_router_info_lsa_update, + NULL); /* del_lsa_hook */ if (rc != 0) { zlog_warn( @@ -198,12 +228,35 @@ void ospf_router_info_term(void) return; } +void ospf_router_info_finish(void) +{ + list_delete_all_node(OspfRI.pce_info.pce_domain); + list_delete_all_node(OspfRI.pce_info.pce_neighbor); + + OspfRI.enabled = false; +} + static void del_pce_info(void *val) { XFREE(MTYPE_OSPF_PCE_PARAMS, val); return; } +/* Catch RI LSA flooding Scope for ospf_ext.[h,c] code */ +struct scope_info ospf_router_info_get_flooding_scope(void) +{ + struct scope_info flooding_scope; + + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { + flooding_scope.scope = OSPF_OPAQUE_AS_LSA; + flooding_scope.area_id.s_addr = 0; + return flooding_scope; + } + flooding_scope.scope = OSPF_OPAQUE_AREA_LSA; + flooding_scope.area_id.s_addr = OspfRI.area_id.s_addr; + return flooding_scope; +} + /*------------------------------------------------------------------------* * Followings are control functions for ROUTER INFORMATION parameters *management. @@ -399,6 +452,78 @@ static void set_pce_cap_flag(u_int32_t cap, struct ospf_pce_info *pce) return; } +/* Segment Routing TLV setter */ + +/* Algorithm SubTLV - section 3.1 */ +static void set_sr_algorithm(uint8_t algo) +{ + + OspfRI.sr_info.algo.value[0] = algo; + for (int i = 1; i < ALGORITHM_COUNT; i++) + OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET; + + /* Set TLV type and length == only 1 Algorithm */ + TLV_TYPE(OspfRI.sr_info.algo) = htons(RI_SR_TLV_SR_ALGORITHM); + TLV_LEN(OspfRI.sr_info.algo) = htons(sizeof(uint8_t)); + +} + +/* unset Aglogithm SubTLV */ +static void unset_sr_algorithm(uint8_t algo) +{ + + for (int i = 0; i < ALGORITHM_COUNT; i++) + OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET; + + /* Unset TLV type and length */ + TLV_TYPE(OspfRI.sr_info.algo) = htons(0); + TLV_LEN(OspfRI.sr_info.algo) = htons(0); + +} + +/* Segment Routing Global Block SubTLV - section 3.2 */ +static void set_sr_sid_label_range(struct sr_srgb srgb) +{ + /* Set Header */ + TLV_TYPE(OspfRI.sr_info.range) = htons(RI_SR_TLV_SID_LABEL_RANGE); + TLV_LEN(OspfRI.sr_info.range) = + htons(SUBTLV_SID_LABEL_SIZE + sizeof(uint32_t)); + /* Set Range Size */ + OspfRI.sr_info.range.size = htonl(SET_RANGE_SIZE(srgb.range_size)); + /* Set Lower bound label SubTLV */ + TLV_TYPE(OspfRI.sr_info.range.lower) = htons(SUBTLV_SID_LABEL); + TLV_LEN(OspfRI.sr_info.range.lower) = htons(SID_RANGE_LABEL_LENGTH); + OspfRI.sr_info.range.lower.value = htonl(SET_LABEL(srgb.lower_bound)); + +} + +/* Unset this SRGB SubTLV */ +static void unset_sr_sid_label_range(void) +{ + + TLV_TYPE(OspfRI.sr_info.range) = htons(0); + TLV_LEN(OspfRI.sr_info.range) = htons(0); + TLV_TYPE(OspfRI.sr_info.range.lower) = htons(0); + TLV_LEN(OspfRI.sr_info.range.lower) = htons(0); + +} + +/* Set Maximum Stack Depth for this router */ +static void set_sr_node_msd(uint8_t msd) +{ + TLV_TYPE(OspfRI.sr_info.msd) = htons(RI_SR_TLV_NODE_MSD); + TLV_LEN(OspfRI.sr_info.msd) = htons(sizeof(uint32_t)); + OspfRI.sr_info.msd.value = msd; + +} + +/* Unset this router MSD */ +static void unset_sr_node_msd(void) +{ + TLV_TYPE(OspfRI.sr_info.msd) = htons(0); + TLV_LEN(OspfRI.sr_info.msd) = htons(0); + +} static void unset_param(struct tlv_header *tlv) { @@ -466,11 +591,62 @@ static int is_mandated_params_set(struct ospf_router_info ori) && (ntohs(ori.pce_info.pce_cap_flag.header.type) == 0)) return rc; + if ((ori.sr_info.enabled) && (ntohs(TLV_TYPE(ori.sr_info.algo)) == 0) + && (ntohs(TLV_TYPE(ori.sr_info.range)) == 0)) + return rc; + rc = 1; return rc; } +/* + * Used by Segment Routing to set new TLVs and Sub-TLVs values + * + * @param enable To activate or not Segment Routing router Information flooding + * @param size Size of Label Range i.e. SRGB size + * @param lower Lower bound of the Label Range i.e. SRGB first label + * @param msd Maximum label Stack Depth suported by the router + * + * @return none + */ +void ospf_router_info_update_sr(bool enable, struct sr_srgb srgb, uint8_t msd) +{ + + /* First activate and initialize Router Information is necessary */ + if (!OspfRI.enabled) { + OspfRI.enabled = true; + initialize_params(&OspfRI); + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug("RI-> %s Routing Information for Segment Routing", + enable ? "Enable" : "Disable"); + + /* Unset or Set SR parameters */ + if (!enable) { + unset_sr_algorithm(SR_ALGORITHM_SPF); + unset_sr_sid_label_range(); + unset_sr_node_msd(); + OspfRI.sr_info.enabled = false; + } else { + // Only SR_ALGORITHM_SPF is supported + set_sr_algorithm(SR_ALGORITHM_SPF); + set_sr_sid_label_range(srgb); + if (msd != 0) + set_sr_node_msd(msd); + else + unset_sr_node_msd(); + OspfRI.sr_info.enabled = true; + } + + /* Refresh if already engaged or originate RI LSA */ + if (CHECK_FLAG(OspfRI.flags, RIFLG_LSA_ENGAGED)) + ospf_router_info_lsa_schedule(REFRESH_THIS_LSA); + else + ospf_router_info_lsa_schedule(REORIGINATE_THIS_LSA); +} + /*------------------------------------------------------------------------* * Followings are callback functions against generic Opaque-LSAs handling. *------------------------------------------------------------------------*/ @@ -519,12 +695,22 @@ static void ospf_router_info_lsa_body_set(struct stream *s) /* Build Router Information TLV */ build_tlv(s, &OspfRI.router_cap.header); - /* Compute PCE Info header first */ - set_pce_header (&OspfRI.pce_info); + /* Build Segment Routing TLVs if enabled */ + if (OspfRI.sr_info.enabled) { + /* Build Algorithm TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.algo)); + /* Build SRGB TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.range)); + /* Build MSD TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.msd)); + } /* Add RI PCE TLV if it is set */ if (OspfRI.pce_info.enabled) { + /* Compute PCE Info header first */ + set_pce_header (&OspfRI.pce_info); + /* Build PCE TLV */ build_tlv_header(s, &OspfRI.pce_info.pce_header.header); @@ -855,6 +1041,43 @@ static void ospf_router_info_lsa_schedule(enum lsa_opcode opcode) return; } +/* Callback to handle Segment Routing information */ +static int ospf_router_info_lsa_update(struct ospf_lsa *lsa) +{ + + /* Sanity Check */ + if (lsa == NULL) { + zlog_warn("OSPF-RI (%s): Abort! LSA is NULL", __func__); + return -1; + } + + /* Process only Opaque LSA */ + if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) + && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) + return 0; + + /* Process only Router Information LSA */ + if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) != + OPAQUE_TYPE_ROUTER_INFORMATION_LSA) + return 0; + + /* Check if it is not my LSA */ + if (IS_LSA_SELF(lsa)) + return 0; + + /* Check if Router Info & Segment Routing are enable */ + if (!OspfRI.enabled || !OspfRI.sr_info.enabled) + return 0; + + /* Call Segment Routing LSA update or deletion */ + if (!IS_LSA_MAXAGE(lsa)) + ospf_sr_ri_lsa_update(lsa); + else + ospf_sr_ri_lsa_delete(lsa); + + return 0; +} + /*------------------------------------------------------------------------* * Followings are vty session control functions. *------------------------------------------------------------------------*/ @@ -1021,6 +1244,99 @@ static u_int16_t show_vty_pce_info(struct vty *vty, struct tlv_header *ri, return sum; } +/* Display Segment Routing Algorithm TLV information */ +static uint16_t show_vty_sr_algorithm(struct vty *vty, struct tlv_header *tlvh) +{ + struct ri_sr_tlv_sr_algorithm *algo = + (struct ri_sr_tlv_sr_algorithm *)tlvh; + int i; + + if (vty != NULL) { + vty_out(vty, " Segment Routing Algorithm TLV:\n"); + for (i = 0; i < ntohs(algo->header.length); i++) { + switch (algo->value[i]) { + case 0: + vty_out(vty, " Algorithm %d: SPF\n", i); + break; + case 1: + vty_out(vty, " Algorithm %d: Strict SPF\n", + i); + break; + default: + vty_out(vty, + " Algorithm %d: Unknown value %d\n", i, + algo->value[i]); + break; + } + } + } + + else { + zlog_debug(" Segment Routing Algorithm TLV:\n"); + for (i = 0; i < ntohs(algo->header.length); i++) + switch (algo->value[i]) { + case 0: + zlog_debug(" Algorithm %d: SPF\n", i); + break; + case 1: + zlog_debug(" Algorithm %d: Strict SPF\n", i); + break; + default: + zlog_debug( + " Algorithm %d: Unknown value %d\n", + i, algo->value[i]); + break; + } + } + + return TLV_SIZE(tlvh); +} + +/* Display Segment Routing SID/Label Range TLV information */ +static uint16_t show_vty_sr_range(struct vty *vty, struct tlv_header *tlvh) +{ + struct ri_sr_tlv_sid_label_range *range = + (struct ri_sr_tlv_sid_label_range *)tlvh; + + if (vty != NULL) { + vty_out(vty, + " Segment Routing Range TLV:\n" + " Range Size = %d\n" + " SID Label = %d\n\n", + GET_RANGE_SIZE(ntohl(range->size)), + GET_LABEL(ntohl(range->lower.value))); + } else { + zlog_debug( + " Segment Routing Range TLV:\n" + " Range Size = %d\n" + " SID Label = %d\n\n", + GET_RANGE_SIZE(ntohl(range->size)), + GET_LABEL(ntohl(range->lower.value))); + } + + return TLV_SIZE(tlvh); +} + +/* Display Segment Routing Maximum Stack Depth TLV information */ +static uint16_t show_vty_sr_msd(struct vty *vty, struct tlv_header *tlvh) +{ + struct ri_sr_tlv_node_msd *msd = (struct ri_sr_tlv_node_msd *)tlvh; + + if (vty != NULL) { + vty_out(vty, + " Segment Routing MSD TLV:\n" + " Node Maximum Stack Depth = %d\n", + msd->value); + } else { + zlog_debug( + " Segment Routing MSD TLV:\n" + " Node Maximum Stack Depth = %d\n", + msd->value); + } + + return TLV_SIZE(tlvh); +} + static void ospf_router_info_show_info(struct vty *vty, struct ospf_lsa *lsa) { struct lsa_header *lsah = (struct lsa_header *)lsa->data; @@ -1041,6 +1357,16 @@ static void ospf_router_info_show_info(struct vty *vty, struct ospf_lsa *lsa) sum += TLV_HDR_SIZE; sum += show_vty_pce_info(vty, tlvh, length - sum); break; + case RI_SR_TLV_SR_ALGORITHM: + sum += show_vty_sr_algorithm(vty, tlvh); + break; + case RI_SR_TLV_SID_LABEL_RANGE: + sum += show_vty_sr_range(vty, tlvh); + break; + case RI_SR_TLV_NODE_MSD: + sum += show_vty_sr_msd(vty, tlvh); + break; + default: sum += show_vty_unknown_tlv(vty, tlvh); break; @@ -1058,53 +1384,54 @@ static void ospf_router_info_config_write_router(struct vty *vty) struct ri_pce_subtlv_neighbor *neighbor; struct in_addr tmp; - if (OspfRI.enabled) { - if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) - vty_out(vty, " router-info as\n"); - else - vty_out(vty, " router-info area %s\n", - inet_ntoa(OspfRI.area_id)); - - if (OspfRI.pce_info.enabled) { - - if (pce->pce_address.header.type != 0) - vty_out(vty, " pce address %s\n", - inet_ntoa(pce->pce_address.address.value)); - - if (pce->pce_cap_flag.header.type != 0) - vty_out(vty, " pce flag 0x%x\n", - ntohl(pce->pce_cap_flag.value)); - - for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { - if (domain->header.type != 0) { - if (domain->type == PCE_DOMAIN_TYPE_AREA) { - tmp.s_addr = domain->value; - vty_out(vty, " pce domain area %s\n", - inet_ntoa(tmp)); - } else { - vty_out(vty, " pce domain as %d\n", - ntohl(domain->value)); - } + if (!OspfRI.enabled) + return; + + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) + vty_out(vty, " router-info as\n"); + else + vty_out(vty, " router-info area %s\n", + inet_ntoa(OspfRI.area_id)); + + if (OspfRI.pce_info.enabled) { + + if (pce->pce_address.header.type != 0) + vty_out(vty, " pce address %s\n", + inet_ntoa(pce->pce_address.address.value)); + + if (pce->pce_cap_flag.header.type != 0) + vty_out(vty, " pce flag 0x%x\n", + ntohl(pce->pce_cap_flag.value)); + + for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { + if (domain->header.type != 0) { + if (domain->type == PCE_DOMAIN_TYPE_AREA) { + tmp.s_addr = domain->value; + vty_out(vty, " pce domain area %s\n", + inet_ntoa(tmp)); + } else { + vty_out(vty, " pce domain as %d\n", + ntohl(domain->value)); } } + } - for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { - if (neighbor->header.type != 0) { - if (neighbor->type == PCE_DOMAIN_TYPE_AREA) { - tmp.s_addr = neighbor->value; - vty_out(vty, " pce neighbor area %s\n", - inet_ntoa(tmp)); - } else { - vty_out(vty, " pce neighbor as %d\n", - ntohl(neighbor->value)); - } + for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { + if (neighbor->header.type != 0) { + if (neighbor->type == PCE_DOMAIN_TYPE_AREA) { + tmp.s_addr = neighbor->value; + vty_out(vty, " pce neighbor area %s\n", + inet_ntoa(tmp)); + } else { + vty_out(vty, " pce neighbor as %d\n", + ntohl(neighbor->value)); } } - - if (pce->pce_scope.header.type != 0) - vty_out(vty, " pce scope 0x%x\n", - ntohl(OspfRI.pce_info.pce_scope.value)); } + + if (pce->pce_scope.header.type != 0) + vty_out(vty, " pce scope 0x%x\n", + ntohl(OspfRI.pce_info.pce_scope.value)); } return; } @@ -1195,9 +1522,6 @@ DEFUN (no_router_info, if (CHECK_FLAG(OspfRI.flags, RIFLG_LSA_ENGAGED)) ospf_router_info_lsa_schedule(FLUSH_THIS_LSA); - /* Unregister the callbacks */ - ospf_router_info_unregister(); - OspfRI.enabled = false; return CMD_SUCCESS; @@ -1539,7 +1863,7 @@ DEFUN (show_ip_opsf_router_info_pce, struct ri_pce_subtlv_domain *domain; struct ri_pce_subtlv_neighbor *neighbor; - if (OspfRI.enabled) { + if ((OspfRI.enabled) && (OspfRI.pce_info.enabled)) { vty_out(vty, "--- PCE parameters ---\n"); if (pce->pce_address.header.type != 0) @@ -1568,7 +1892,7 @@ DEFUN (show_ip_opsf_router_info_pce, } else { vty_out(vty, - " Router Information is disabled on this router\n"); + " PCE info is disabled on this router\n"); } return CMD_SUCCESS; diff --git a/ospfd/ospf_ri.h b/ospfd/ospf_ri.h index 2d90730d93..39ebb72009 100644 --- a/ospfd/ospf_ri.h +++ b/ospfd/ospf_ri.h @@ -1,11 +1,13 @@ /* * This is an implementation of RFC4970 Router Information * with support of RFC5088 PCE Capabilites announcement + * and support of draft-ietf-ospf-segment-routing-extensions-18 + * for Segment Routing Capabilities announcement + * * * Module name: Router Information - * Version: 0.99.22 - * Created: 2012-02-01 by Olivier Dugeon - * Copyright (C) 2012 Orange Labs http://www.orange.com/ + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/ * * This file is part of GNU Zebra. * @@ -33,7 +35,7 @@ * * 24 16 8 0 * +--------+--------+--------+--------+ - * | 1 | MBZ |........|........| + * | 4 | MBZ |........|........| * +--------+--------+--------+--------+ * |<-Type->|<Resv'd>|<-- Instance --->| * @@ -57,9 +59,8 @@ * +--------+--------+--------+--------+ | * | LS checksum | Length | V * +--------+--------+--------+--------+ --- - * | Type | Length | A - * +--------+--------+--------+--------+ | TLV part for Router Information; - * Values might be + * | Type | Length | A TLV part for Router Information; + * +--------+--------+--------+--------+ | Values might be * | Values ... | V structured as a set of sub-TLVs. * +--------+--------+--------+--------+ --- */ @@ -68,9 +69,9 @@ * Following section defines TLV body parts. */ -/* Up to now, 8 code point have been assigned to Router Information */ +/* Up to now, 11 code points have been assigned to Router Information */ /* Only type 1 Router Capabilities and 6 PCE are supported with this code */ -#define RI_IANA_MAX_TYPE 8 +#define RI_IANA_MAX_TYPE 11 /* RFC4970: Router Information Capabilities TLV */ /* Mandatory */ #define RI_TLV_CAPABILITIES 1 @@ -80,12 +81,13 @@ struct ri_tlv_router_cap { u_int32_t value; }; -#define RI_GRACE_RESTART 0x01 -#define RI_GRACE_HELPER 0x02 -#define RI_STUB_SUPPORT 0x04 -#define RI_TE_SUPPORT 0x08 -#define RI_P2P_OVER_LAN 0x10 -#define RI_TE_EXPERIMENTAL 0x20 +/* Capabilities bits are left align */ +#define RI_GRACE_RESTART 0x80000000 +#define RI_GRACE_HELPER 0x40000000 +#define RI_STUB_SUPPORT 0x20000000 +#define RI_TE_SUPPORT 0x10000000 +#define RI_P2P_OVER_LAN 0x08000000 +#define RI_TE_EXPERIMENTAL 0x04000000 #define RI_TLV_LENGTH 4 @@ -151,22 +153,32 @@ struct ri_pce_subtlv_neighbor { #define RI_PCE_SUBTLV_CAP_FLAG 5 #define PCE_CAP_GMPLS_LINK 0x0001 -#define PCE_CAP_BIDIRECTIONAL 0x0002 -#define PCE_CAP_DIVERSE_PATH 0x0004 -#define PCE_CAP_LOAD_BALANCE 0x0008 -#define PCE_CAP_SYNCHRONIZED 0x0010 +#define PCE_CAP_BIDIRECTIONAL 0x0002 +#define PCE_CAP_DIVERSE_PATH 0x0004 +#define PCE_CAP_LOAD_BALANCE 0x0008 +#define PCE_CAP_SYNCHRONIZED 0x0010 #define PCE_CAP_OBJECTIVES 0x0020 #define PCE_CAP_ADDITIVE 0x0040 -#define PCE_CAP_PRIORIZATION 0x0080 -#define PCE_CAP_MULTIPLE_REQ 0x0100 +#define PCE_CAP_PRIORIZATION 0x0080 +#define PCE_CAP_MULTIPLE_REQ 0x0100 struct ri_pce_subtlv_cap_flag { struct tlv_header header; /* Type = 5; Length = n x 4 bytes. */ u_int32_t value; }; +/* Structure to share flooding scope info for Segment Routing */ +struct scope_info { + uint8_t scope; + struct in_addr area_id; +}; + /* Prototypes. */ extern int ospf_router_info_init(void); extern void ospf_router_info_term(void); - +extern void ospf_router_info_finish(void); +extern int ospf_router_info_enable(void); +extern void ospf_router_info_update_sr(bool enable, struct sr_srgb srgb, + uint8_t msd); +extern struct scope_info ospf_router_info_get_flooding_scope(void); #endif /* _ZEBRA_OSPF_ROUTER_INFO_H */ diff --git a/ospfd/ospf_spf.c b/ospfd/ospf_spf.c index 22fff1b53d..9c747cd565 100644 --- a/ospfd/ospf_spf.c +++ b/ospfd/ospf_spf.c @@ -46,6 +46,7 @@ #include "ospfd/ospf_ase.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_dump.h" +#include "ospfd/ospf_sr.h" /* Variables to ensure a SPF scheduled log message is printed only once */ @@ -1339,7 +1340,6 @@ static int ospf_spf_calculate_timer(struct thread *thread) ospf_ase_calculate_timer_add(ospf); - if (IS_DEBUG_OSPF_EVENT) zlog_debug("%s: ospf install new route, vrf %s id %u new_table count %lu", __PRETTY_FUNCTION__, @@ -1366,6 +1366,9 @@ static int ospf_spf_calculate_timer(struct thread *thread) ospf_abr_task(ospf); abr_time = monotime_since(&start_time, NULL); + /* Schedule Segment Routing update */ + ospf_sr_update_timer_add(ospf); + total_spf_time = monotime_since(&spf_start_time, &ospf->ts_spf_duration); diff --git a/ospfd/ospf_sr.c b/ospfd/ospf_sr.c new file mode 100644 index 0000000000..725bf952e5 --- /dev/null +++ b/ospfd/ospf_sr.c @@ -0,0 +1,2281 @@ +/* + * This is an implementation of Segment Routing + * as per draft draft-ietf-ospf-segment-routing-extensions-24 + * + * Module name: Segment Routing + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Author: Anselme Sawadogo <anselmesawadogo@gmail.com> + * + * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <zebra.h> + +#include "command.h" +#include "hash.h" +#include "if.h" +#include "if.h" +#include "jhash.h" +#include "libospf.h" /* for ospf interface types */ +#include "linklist.h" +#include "log.h" +#include "memory.h" +#include "monotime.h" +#include "network.h" +#include "prefix.h" +#include "sockunion.h" /* for inet_aton() */ +#include "stream.h" +#include "table.h" +#include "thread.h" +#include "vty.h" +#include "zclient.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_ext.h" +#include "ospfd/ospf_zebra.h" + +/* + * Global variable to manage Segment Routing on this node. + * Note that all parameter values are stored in network byte order. + */ +static struct ospf_sr_db OspfSR; +static void ospf_sr_register_vty(void); +static inline void del_sid_nhlfe(struct sr_nhlfe nhlfe); + +/* + * Segment Routing Data Base functions + */ + +/* Hash function for Segment Routing entry */ +static unsigned int sr_hash(void *p) +{ + const struct in_addr *rid = p; + + return jhash_1word(rid->s_addr, 0); +} + +/* Compare 2 Router ID hash entries based on SR Node */ +static int sr_cmp(const void *p1, const void *p2) +{ + const struct sr_node *srn = p1; + const struct in_addr *rid = p2; + + return IPV4_ADDR_SAME(&srn->adv_router, rid); +} + +/* Functions to remove an SR Link */ +static void del_sr_link(void *val) +{ + struct sr_link *srl = (struct sr_link *)val; + + del_sid_nhlfe(srl->nhlfe[0]); + del_sid_nhlfe(srl->nhlfe[1]); + XFREE(MTYPE_OSPF_SR_PARAMS, val); + +} + +/* Functions to remove an SR Prefix */ +static void del_sr_pref(void *val) +{ + struct sr_prefix *srp = (struct sr_prefix *)val; + + del_sid_nhlfe(srp->nhlfe); + XFREE(MTYPE_OSPF_SR_PARAMS, val); + +} + +/* Allocate new Segment Routine node */ +static struct sr_node *sr_node_new(struct in_addr *rid) +{ + + if (rid == NULL) + return NULL; + + struct sr_node *new; + + /* Allocate Segment Routing node memory */ + new = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_node)); + + /* Sanity Check */ + if (new == NULL) { + zlog_err("SR (%s): Abort! can't create new SR node", __func__); + return NULL; + } + + /* Default Algorithm, SRGB and MSD */ + for (int i = 0; i < ALGORITHM_COUNT; i++) + new->algo[i] = SR_ALGORITHM_UNSET; + + new->srgb.range_size = 0; + new->srgb.lower_bound = 0; + new->msd = 0; + + /* Create Link, Prefix and Range TLVs list */ + new->ext_link = list_new(); + new->ext_prefix = list_new(); + new->ext_link->del = del_sr_link; + new->ext_prefix->del = del_sr_pref; + + /* Check if list are correctly created */ + if (new->ext_link == NULL || new->ext_prefix == NULL) { + list_delete_original(new->ext_link); + list_delete_original(new->ext_prefix); + XFREE(MTYPE_OSPF_SR_PARAMS, new); + return NULL; + } + + IPV4_ADDR_COPY(&new->adv_router, rid); + new->neighbor = NULL; + new->instance = 0; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Created new SR node for %s", + inet_ntoa(new->adv_router)); + return new; +} + +/* Delete Segment Routing node */ +static void sr_node_del(struct sr_node *srn) +{ + /* Sanity Check */ + if (srn == NULL) + return; + + /* Clean Extended Link */ + list_delete_and_null(&srn->ext_link); + + /* Clean Prefix List */ + list_delete_and_null(&srn->ext_prefix); + + XFREE(MTYPE_OSPF_SR_PARAMS, srn); +} + +/* Get SR Node for a given nexthop */ +static struct sr_node *get_sr_node_by_nexthop(struct ospf *ospf, + struct in_addr nexthop) +{ + struct ospf_interface *oi = NULL; + struct ospf_neighbor *nbr = NULL; + struct listnode *node; + struct route_node *rn; + struct sr_node *srn; + bool found; + + /* Sanity check */ + if (OspfSR.neighbors == NULL) + return NULL; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Search SR-Node for nexthop %s", + inet_ntoa(nexthop)); + + /* First, search neighbor Router ID for this nexthop */ + found = false; + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if ((nbr) && (IPV4_ADDR_SAME(&nexthop, &nbr->src))) { + found = true; + break; + } + } + if (found) + break; + } + + if (!found) + return NULL; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Found nexthop Router ID %s", + inet_ntoa(nbr->router_id)); + /* Then, search SR Node */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, &nbr->router_id); + + return srn; +} + +/* + * Segment Routing Initialization functions + */ + +/* Segment Routing starter function */ +static int ospf_sr_start(struct ospf *ospf) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + struct sr_node *srn; + int rc = 0; + + if (IS_DEBUG_OSPF_SR) + zlog_debug("SR (%s): Start Segment Routing", __func__); + + /* Initialize self SR Node */ + srn = hash_get(OspfSR.neighbors, (void *)&(ospf->router_id), + (void *)sr_node_new); + + /* Sanity Check */ + if (srn == NULL) + return rc; + + /* Complete & Store self SR Node */ + srn->srgb.range_size = OspfSR.srgb.range_size; + srn->srgb.lower_bound = OspfSR.srgb.lower_bound; + srn->algo[0] = OspfSR.algo[0]; + srn->msd = OspfSR.msd; + OspfSR.self = srn; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SR (%s): Update SR-DB from LSDB", __func__); + + /* Start by looking to Router Info & Extended LSA in lsdb */ + if ((ospf != NULL) && (ospf->backbone != NULL)) { + LSDB_LOOP(OPAQUE_AREA_LSDB(ospf->backbone), rn, lsa) + { + if (IS_LSA_MAXAGE(lsa) || IS_LSA_SELF(lsa)) + continue; + int lsa_id = + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + switch (lsa_id) { + case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: + ospf_sr_ri_lsa_update(lsa); + break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + ospf_sr_ext_prefix_lsa_update(lsa); + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + ospf_sr_ext_link_lsa_update(lsa); + break; + default: + break; + } + } + } + + rc = 1; + return rc; +} + +/* Stop Segment Routing */ +static void ospf_sr_stop(void) +{ + + if (IS_DEBUG_OSPF_SR) + zlog_debug("SR (%s): Stop Segment Routing", __func__); + + /* + * Remove all SR Nodes from the Hash table. Prefix and Link SID will + * be remove though list_delete_and_null() call. See sr_node_del() + */ + hash_clean(OspfSR.neighbors, (void *)sr_node_del); +} + +/* + * Segment Routing initialize function + * + * @param - nothing + * + * @return 0 if OK, -1 otherwise + */ +int ospf_sr_init(void) +{ + int rc = -1; + + zlog_info("SR (%s): Initialize SR Data Base", __func__); + + memset(&OspfSR, 0, sizeof(struct ospf_sr_db)); + OspfSR.enabled = false; + /* Only AREA flooding is supported in this release */ + OspfSR.scope = OSPF_OPAQUE_AREA_LSA; + + /* Initialize SRGB, Algorithms and MSD TLVs */ + /* Only Algorithm SPF is supported */ + OspfSR.algo[0] = SR_ALGORITHM_SPF; + for (int i = 1; i < ALGORITHM_COUNT; i++) + OspfSR.algo[i] = SR_ALGORITHM_UNSET; + + OspfSR.srgb.range_size = MPLS_DEFAULT_MAX_SRGB_SIZE; + OspfSR.srgb.lower_bound = MPLS_DEFAULT_MIN_SRGB_LABEL; + OspfSR.msd = 0; + + /* Initialize Hash table for neighbor SR nodes */ + OspfSR.neighbors = hash_create(sr_hash, sr_cmp, "OSPF_SR"); + if (OspfSR.neighbors == NULL) + return rc; + + /* Initialize Route Table for prefix */ + OspfSR.prefix = route_table_init(); + if (OspfSR.prefix == NULL) + return rc; + + /* Register Segment Routing VTY command */ + ospf_sr_register_vty(); + + rc = 0; + return rc; +} + +/* + * Segment Routing termination function + * + * @param - nothing + * @return - nothing + */ +void ospf_sr_term(void) +{ + + /* Stop Segment Routing */ + ospf_sr_stop(); + + /* Clear SR Node Table */ + if (OspfSR.neighbors) + hash_free(OspfSR.neighbors); + + /* Clear Prefix Table */ + if (OspfSR.prefix) + route_table_finish(OspfSR.prefix); + + OspfSR.enabled = false; + OspfSR.self = NULL; +} + +/* + * Segment Routing finish function + * + * @param - nothing + * @return - nothing + */ +void ospf_sr_finish(void) +{ + /* Stop Segment Routing */ + ospf_sr_stop(); + + OspfSR.enabled = false; +} + +/* + * Following functions are used to manipulate the + * Next Hop Label Forwarding entry (NHLFE) + */ + +/* Compute label from index */ +static mpls_label_t index2label(uint32_t index, struct sr_srgb srgb) +{ + mpls_label_t label; + + label = srgb.lower_bound + index; + if (label > (srgb.lower_bound + srgb.range_size)) + return MPLS_INVALID_LABEL; + else + return label; +} + +/* Get neighbor full structure from address */ +static struct ospf_neighbor *get_neighbor_by_addr(struct ospf *top, + struct in_addr addr) +{ + struct ospf_neighbor *nbr; + struct ospf_interface *oi; + struct listnode *node; + struct route_node *rn; + + /* Sanity Check */ + if (top == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(top->oiflist, node, oi)) + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (nbr) + if (IPV4_ADDR_SAME(&nbr->address.u.prefix4, + &addr) + || IPV4_ADDR_SAME(&nbr->router_id, &addr)) { + route_unlock_node(rn); + return nbr; + } + } + return NULL; +} + +/* Get OSPF Path from address */ +static struct ospf_path *get_nexthop_by_addr(struct ospf *top, + struct prefix_ipv4 p) +{ + struct ospf_route *or; + struct ospf_path *path; + struct listnode *node; + struct route_node *rn; + + /* Sanity Check */ + if ((top == NULL) && (top->new_table)) + return NULL; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Search Nexthop for prefix %s/%u", + inet_ntoa(p.prefix), p.prefixlen); + + rn = route_node_lookup(top->new_table, (struct prefix *)&p); + + /* + * Check if we found an OSPF route. May be NULL if SPF has not + * yet populate routing table for this prefix. + */ + if (rn == NULL) + return NULL; + + route_unlock_node(rn); + or = rn->info; + if (or == NULL) + return NULL; + + /* Then search path from this route */ + for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) + if (path->nexthop.s_addr != INADDR_ANY || path->ifindex != 0) + return path; + + return NULL; +} + +/* Compute NHLFE entry for Extended Link */ +static int compute_link_nhlfe(struct sr_link *srl) +{ + struct ospf *top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct ospf_neighbor *nh; + int rc = 0; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Compute NHLFE for link %s/%u", + inet_ntoa(srl->nhlfe[0].prefv4.prefix), + srl->nhlfe[0].prefv4.prefixlen); + + /* First determine the OSPF Neighbor */ + nh = get_neighbor_by_addr(top, srl->nhlfe[0].nexthop); + + /* Neighbor could be not found when OSPF Adjacency just fire up + * because SPF don't yet populate routing table. This NHLFE will + * be fixed later when SR SPF schedule will be called. + */ + if (nh == NULL) + return rc; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Found nexthop NHLFE %s", + inet_ntoa(nh->router_id)); + + /* Set ifindex for this neighbor */ + srl->nhlfe[0].ifindex = nh->oi->ifp->ifindex; + srl->nhlfe[1].ifindex = nh->oi->ifp->ifindex; + + /* Set Input & Output Label */ + if (CHECK_FLAG(srl->flags[0], EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->nhlfe[0].label_in = srl->sid[0]; + else + srl->nhlfe[0].label_in = + index2label(srl->sid[0], srl->srn->srgb); + if (CHECK_FLAG(srl->flags[1], EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->nhlfe[1].label_in = srl->sid[1]; + else + srl->nhlfe[1].label_in = + index2label(srl->sid[1], srl->srn->srgb); + + srl->nhlfe[0].label_out = MPLS_IMP_NULL_LABEL; + srl->nhlfe[1].label_out = MPLS_IMP_NULL_LABEL; + + rc = 1; + return rc; +} + +/* + * Compute NHLFE entry for Extended Prefix + * + * @param srp - Segment Routing Prefix + * + * @return -1 if next hop is not found, 0 if nexthop has not changed + * and 1 if success + */ +static int compute_prefix_nhlfe(struct sr_prefix *srp) +{ + struct ospf *top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct ospf_path *nh = NULL; + struct sr_node *srnext; + int rc = -1; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Compute NHLFE for prefix %s/%u", + inet_ntoa(srp->nhlfe.prefv4.prefix), + srp->nhlfe.prefv4.prefixlen); + + /* First determine the nexthop */ + nh = get_nexthop_by_addr(top, srp->nhlfe.prefv4); + + /* Nexthop could be not found when OSPF Adjacency just fire up + * because SPF don't yet populate routing table. This NHLFE will + * be fixed later when SR SPF schedule will be called. + */ + if (nh == NULL) + return rc; + + /* Check if NextHop has changed when call after running a new SPF */ + if (IPV4_ADDR_SAME(&nh->nexthop, &srp->nhlfe.nexthop) + && (nh->ifindex == srp->nhlfe.ifindex)) + return 0; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Found new next hop for this NHLFE: %s", + inet_ntoa(nh->nexthop)); + + /* + * Get SR-Node for this nexthop. Could be not yet available + * as Extende Link / Prefix and Router Information are flooded + * after LSA Type 1 & 2 which populate the OSPF Route Table + */ + srnext = get_sr_node_by_nexthop(top, nh->nexthop); + if (srnext == NULL) + return rc; + + /* And store this information for later update if SR Node is found */ + srnext->neighbor = OspfSR.self; + if (IPV4_ADDR_SAME(&srnext->adv_router, &srp->adv_router)) + srp->nexthop = NULL; + else + srp->nexthop = srnext; + + /* + * SR Node could be known, but SRGB could be not initialize + * This is due to the fact that Extended Link / Prefix could + * be received before corresponding Router Information LSA + */ + if ((srnext == NULL) || (srnext->srgb.lower_bound == 0) + || (srnext->srgb.range_size == 0)) + return rc; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Found SRGB %u/%u for next hop SR-Node %s", + srnext->srgb.range_size, srnext->srgb.lower_bound, + inet_ntoa(srnext->adv_router)); + + /* Set ip addr & ifindex for this neighbor */ + IPV4_ADDR_COPY(&srp->nhlfe.nexthop, &nh->nexthop); + srp->nhlfe.ifindex = nh->ifindex; + + /* Compute Input Label with self SRGB */ + srp->nhlfe.label_in = index2label(srp->sid, OspfSR.srgb); + /* + * and Output Label with Next hop SR Node SRGB or Implicit Null label + * if next hop is the destination and request PHP + */ + if ((srp->nexthop == NULL) + && (!CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG))) + srp->nhlfe.label_out = MPLS_IMP_NULL_LABEL; + else if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) + srp->nhlfe.label_out = srp->sid; + else + srp->nhlfe.label_out = index2label(srp->sid, srnext->srgb); + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Computed new labels in: %u out: %u", + srp->nhlfe.label_in, srp->nhlfe.label_out); + + rc = 1; + return rc; +} + +/* Send MPLS Label entry to Zebra for installation or deletion */ +static int ospf_zebra_send_mpls_labels(int cmd, struct sr_nhlfe nhlfe) +{ + struct stream *s; + + /* Reset stream. */ + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, cmd, VRF_DEFAULT); + stream_putc(s, ZEBRA_LSP_SR); + /* OSPF Segment Routing currently support only IPv4 */ + stream_putl(s, nhlfe.prefv4.family); + stream_put_in_addr(s, &nhlfe.prefv4.prefix); + stream_putc(s, nhlfe.prefv4.prefixlen); + stream_put_in_addr(s, &nhlfe.nexthop); + stream_putl(s, nhlfe.ifindex); + stream_putc(s, OSPF_SR_PRIORITY_DEFAULT); + stream_putl(s, nhlfe.label_in); + stream_putl(s, nhlfe.label_out); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- %s LSP %u/%u for %s/%u via %u", + cmd == ZEBRA_MPLS_LABELS_ADD ? "Add" : "Delete", + nhlfe.label_in, nhlfe.label_out, + inet_ntoa(nhlfe.prefv4.prefix), + nhlfe.prefv4.prefixlen, nhlfe.ifindex); + + return zclient_send_message(zclient); +} + +/* Request zebra to install/remove FEC in FIB */ +static int ospf_zebra_send_mpls_ftn(int cmd, struct sr_nhlfe nhlfe) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + + /* Support only IPv4 */ + if (nhlfe.prefv4.family != AF_INET) + return -1; + + memset(&api, 0, sizeof(api)); + api.vrf_id = VRF_DEFAULT; + api.type = ZEBRA_ROUTE_OSPF; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, &nhlfe.prefv4, sizeof(struct prefix_ipv4)); + + if (cmd == ZEBRA_ROUTE_ADD) { + /* Metric value. */ + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = OSPF_SR_DEFAULT_METRIC; + /* Nexthop */ + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + api_nh = &api.nexthops[0]; + IPV4_ADDR_COPY(&api_nh->gate.ipv4, &nhlfe.nexthop); + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + api_nh->ifindex = nhlfe.ifindex; + /* MPLS labels */ + SET_FLAG(api.message, ZAPI_MESSAGE_LABEL); + api_nh->labels[0] = nhlfe.label_out; + api_nh->label_num = 1; + api.nexthop_num = 1; + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- %s FEC %u for %s/%u via %u", + cmd == ZEBRA_ROUTE_ADD ? "Add" : "Delete", + nhlfe.label_out, inet_ntoa(nhlfe.prefv4.prefix), + nhlfe.prefv4.prefixlen, nhlfe.ifindex); + + return zclient_route_send(cmd, zclient, &api); +} + +/* Add new NHLFE entry for SID */ +static inline void add_sid_nhlfe(struct sr_nhlfe nhlfe) +{ + if ((nhlfe.label_in != 0) && (nhlfe.label_out != 0)) { + ospf_zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_ADD, nhlfe); + if (nhlfe.label_out != MPLS_IMP_NULL_LABEL) + ospf_zebra_send_mpls_ftn(ZEBRA_ROUTE_ADD, nhlfe); + } +} + +/* Remove NHLFE entry for SID */ +static inline void del_sid_nhlfe(struct sr_nhlfe nhlfe) +{ + if ((nhlfe.label_in != 0) && (nhlfe.label_out != 0)) { + ospf_zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_DELETE, nhlfe); + if (nhlfe.label_out != MPLS_IMP_NULL_LABEL) + ospf_zebra_send_mpls_ftn(ZEBRA_ROUTE_DELETE, nhlfe); + } +} + +/* Update NHLFE entry for SID */ +static inline void update_sid_nhlfe(struct sr_nhlfe n1, struct sr_nhlfe n2) +{ + + del_sid_nhlfe(n1); + add_sid_nhlfe(n2); +} + +/* + * Functions to parse and get Extended Link / Prefix + * TLVs and SubTLVs + */ + +/* Extended Link SubTLVs Getter */ +static struct sr_link *get_ext_link_sid(struct tlv_header *tlvh) +{ + + struct sr_link *srl; + struct ext_tlv_link *link = (struct ext_tlv_link *)tlvh; + struct ext_subtlv_adj_sid *adj_sid; + struct ext_subtlv_lan_adj_sid *lan_sid; + struct ext_subtlv_rmt_itf_addr *rmt_itf; + + struct tlv_header *sub_tlvh; + uint16_t length = 0, sum = 0, i = 0; + + srl = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_link)); + + if (srl == NULL) + return NULL; + + /* Initialize TLV browsing */ + length = ntohs(tlvh->length) - EXT_TLV_LINK_SIZE; + sub_tlvh = (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE + + EXT_TLV_LINK_SIZE); + for (; sum < length; sub_tlvh = TLV_HDR_NEXT(sub_tlvh)) { + switch (ntohs(sub_tlvh->type)) { + case EXT_SUBTLV_ADJ_SID: + adj_sid = (struct ext_subtlv_adj_sid *)sub_tlvh; + srl->type = ADJ_SID; + i = CHECK_FLAG(adj_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_BFLG) + ? 1 + : 0; + srl->flags[i] = adj_sid->flags; + if (CHECK_FLAG(adj_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[i] = GET_LABEL(ntohl(adj_sid->value)); + else + srl->sid[i] = ntohl(adj_sid->value); + IPV4_ADDR_COPY(&srl->nhlfe[i].nexthop, &link->link_id); + break; + case EXT_SUBTLV_LAN_ADJ_SID: + lan_sid = (struct ext_subtlv_lan_adj_sid *)sub_tlvh; + srl->type = LAN_ADJ_SID; + i = CHECK_FLAG(lan_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_BFLG) + ? 1 + : 0; + srl->flags[i] = lan_sid->flags; + if (CHECK_FLAG(lan_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[i] = GET_LABEL(ntohl(lan_sid->value)); + else + srl->sid[i] = ntohl(lan_sid->value); + IPV4_ADDR_COPY(&srl->nhlfe[i].nexthop, + &lan_sid->neighbor_id); + break; + case EXT_SUBTLV_RMT_ITF_ADDR: + rmt_itf = (struct ext_subtlv_rmt_itf_addr *)sub_tlvh; + IPV4_ADDR_COPY(&srl->nhlfe[0].nexthop, &rmt_itf->value); + IPV4_ADDR_COPY(&srl->nhlfe[1].nexthop, &rmt_itf->value); + break; + default: + break; + } + sum += TLV_SIZE(sub_tlvh); + } + + IPV4_ADDR_COPY(&srl->nhlfe[0].prefv4.prefix, &link->link_data); + srl->nhlfe[0].prefv4.prefixlen = IPV4_MAX_PREFIXLEN; + srl->nhlfe[0].prefv4.family = AF_INET; + apply_mask_ipv4(&srl->nhlfe[0].prefv4); + IPV4_ADDR_COPY(&srl->nhlfe[1].prefv4.prefix, &link->link_data); + srl->nhlfe[1].prefv4.prefixlen = IPV4_MAX_PREFIXLEN; + srl->nhlfe[1].prefv4.family = AF_INET; + apply_mask_ipv4(&srl->nhlfe[1].prefv4); + + if (IS_DEBUG_OSPF_SR) { + zlog_debug(" |- Found primary Adj/Lan Sid %u for %s/%u", + srl->sid[0], inet_ntoa(srl->nhlfe[0].prefv4.prefix), + srl->nhlfe[0].prefv4.prefixlen); + zlog_debug(" |- Found backup Adj/Lan Sid %u for %s/%u", + srl->sid[1], inet_ntoa(srl->nhlfe[1].prefv4.prefix), + srl->nhlfe[1].prefv4.prefixlen); + } + + return srl; +} + +/* Extended Prefix SubTLVs Getter */ +static struct sr_prefix *get_ext_prefix_sid(struct tlv_header *tlvh) +{ + + struct sr_prefix *srp; + struct ext_tlv_prefix *pref = (struct ext_tlv_prefix *)tlvh; + struct ext_subtlv_prefix_sid *psid; + + struct tlv_header *sub_tlvh; + uint16_t length = 0, sum = 0; + + srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); + + if (srp == NULL) + return NULL; + + /* Initialize TLV browsing */ + length = ntohs(tlvh->length) - EXT_TLV_PREFIX_SIZE; + sub_tlvh = (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE + + EXT_TLV_PREFIX_SIZE); + for (; sum < length; sub_tlvh = TLV_HDR_NEXT(sub_tlvh)) { + switch (ntohs(sub_tlvh->type)) { + case EXT_SUBTLV_PREFIX_SID: + psid = (struct ext_subtlv_prefix_sid *)sub_tlvh; + if (psid->algorithm != SR_ALGORITHM_SPF) { + zlog_err( + "SR (%s): Unsupported Algorithm", + __func__); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + return NULL; + } + srp->type = PREF_SID; + srp->flags = psid->flags; + if (CHECK_FLAG(psid->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) + srp->sid = GET_LABEL(ntohl(psid->value)); + else + srp->sid = ntohl(psid->value); + IPV4_ADDR_COPY(&srp->nhlfe.prefv4.prefix, + &pref->address); + srp->nhlfe.prefv4.prefixlen = pref->pref_length; + srp->nhlfe.prefv4.family = AF_INET; + apply_mask_ipv4(&srp->nhlfe.prefv4); + break; + default: + break; + } + sum += TLV_SIZE(sub_tlvh); + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Found SID %u for prefix %s/%u", srp->sid, + inet_ntoa(srp->nhlfe.prefv4.prefix), + srp->nhlfe.prefv4.prefixlen); + return srp; +} + +/* + * Functions to manipulate Segment Routing Link & Prefix structures + */ + +/* Compare two Segment Link: return 0 if equal, 1 otherwise */ +static inline int sr_link_cmp(struct sr_link *srl1, struct sr_link *srl2) +{ + if ((srl1->sid[0] == srl2->sid[0]) && (srl1->sid[1] == srl2->sid[1]) + && (srl1->type == srl2->type) && (srl1->flags[0] == srl2->flags[0]) + && (srl1->flags[1] == srl2->flags[1])) + return 0; + else + return 1; +} + +/* Compare two Segment Prefix: return 0 if equal, 1 otherwise */ +static inline int sr_prefix_cmp(struct sr_prefix *srp1, struct sr_prefix *srp2) +{ + if ((srp1->sid == srp2->sid) && (srp1->flags == srp2->flags)) + return 0; + else + return 1; +} + +/* Update Segment Link of given Segment Routing Node */ +static void update_ext_link_sid(struct sr_node *srn, struct sr_link *srl, + u_char lsa_flags) +{ + struct listnode *node; + struct sr_link *lk; + bool found = false; + + /* Sanity check */ + if ((srn == NULL) || (srl == NULL)) + return; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Process Extended Link Adj/Lan-SID"); + + /* Process only Local Adj/Lan_Adj SID coming from LSA SELF */ + if (!CHECK_FLAG(srl->flags[0], EXT_SUBTLV_LINK_ADJ_SID_LFLG) + || !CHECK_FLAG(srl->flags[1], EXT_SUBTLV_LINK_ADJ_SID_LFLG) + || !CHECK_FLAG(lsa_flags, OSPF_LSA_SELF)) + return; + + /* Search for existing Segment Link */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, lk)) + if (lk->instance == srl->instance) { + found = true; + break; + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- %s SR Link 8.0.0.%u for SR node %s", + found ? "Update" : "Add", + GET_OPAQUE_ID(srl->instance), + inet_ntoa(srn->adv_router)); + + /* if not found, add new Segment Link and install NHLFE */ + if (!found) { + /* Complete SR-Link and add it to SR-Node list */ + srl->srn = srn; + IPV4_ADDR_COPY(&srl->adv_router, &srn->adv_router); + listnode_add(srn->ext_link, srl); + /* Try to set MPLS table */ + if (compute_link_nhlfe(srl)) { + add_sid_nhlfe(srl->nhlfe[0]); + add_sid_nhlfe(srl->nhlfe[1]); + } + } else { + if (sr_link_cmp(lk, srl)) { + if (compute_link_nhlfe(srl)) { + update_sid_nhlfe(lk->nhlfe[0], srl->nhlfe[0]); + update_sid_nhlfe(lk->nhlfe[1], srl->nhlfe[1]); + /* Replace Segment List */ + listnode_delete(srn->ext_link, lk); + XFREE(MTYPE_OSPF_SR_PARAMS, lk); + srl->srn = srn; + IPV4_ADDR_COPY(&srl->adv_router, + &srn->adv_router); + listnode_add(srn->ext_link, srl); + } else { + /* New NHLFE was not found. + * Just free the SR Link + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + } + } else { + /* + * This is just an LSA refresh. + * Stop processing and free SR Link + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + } + } +} + +/* Update Segment Prefix of given Segment Routing Node */ +static void update_ext_prefix_sid(struct sr_node *srn, struct sr_prefix *srp) +{ + + struct listnode *node; + struct sr_prefix *pref; + bool found = false; + + /* Sanity check */ + if (srn == NULL || srp == NULL) + return; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Process Extended Prefix SID %u", srp->sid); + + /* Process only Global Prefix SID */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_LFLG)) + return; + + /* Search for existing Segment Prefix */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, pref)) + if (pref->instance == srp->instance) { + found = true; + break; + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- %s SR LSA ID 7.0.0.%u for SR node %s", + found ? "Update" : "Add", + GET_OPAQUE_ID(srp->instance), + inet_ntoa(srn->adv_router)); + + /* if not found, add new Segment Prefix and install NHLFE */ + if (!found) { + /* Complete SR-Prefix and add it to SR-Node list */ + srp->srn = srn; + IPV4_ADDR_COPY(&srp->adv_router, &srn->adv_router); + listnode_add(srn->ext_prefix, srp); + /* Try to set MPLS table */ + if (compute_prefix_nhlfe(srp) == 1) + add_sid_nhlfe(srp->nhlfe); + } else { + if (sr_prefix_cmp(pref, srp)) { + if (compute_prefix_nhlfe(srp) == 1) { + update_sid_nhlfe(pref->nhlfe, srp->nhlfe); + /* Replace Segment Prefix */ + listnode_delete(srn->ext_prefix, pref); + XFREE(MTYPE_OSPF_SR_PARAMS, pref); + srp->srn = srn; + IPV4_ADDR_COPY(&srp->adv_router, + &srn->adv_router); + listnode_add(srn->ext_prefix, srp); + } else { + /* New NHLFE was not found. + * Just free the SR Prefix + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + } + } else { + /* This is just an LSA refresh. + * Stop processing and free SR Prefix + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + } + } +} + +/* + * When change the FRR Self SRGB, update the NHLFE Input Label + * for all Extended Prefix with SID index through hash_iterate() + */ +static void update_in_nhlfe(struct hash_backet *backet, void *args) +{ + struct listnode *node; + struct sr_node *srn = (struct sr_node *)backet->data; + struct sr_prefix *srp; + struct sr_nhlfe new; + + /* Process Every Extended Prefix for this SR-Node */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + /* Process Self SRN only if NO-PHP is requested */ + if ((srn == OspfSR.self) + && !CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) + continue; + + /* Process only SID Index */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) + continue; + + /* OK. Compute new NHLFE */ + memcpy(&new, &srp->nhlfe, sizeof(struct sr_nhlfe)); + new.label_in = index2label(srp->sid, OspfSR.srgb); + /* Update MPLS LFIB */ + update_sid_nhlfe(srp->nhlfe, new); + /* Finally update Input Label */ + srp->nhlfe.label_in = new.label_in; + } +} + +/* + * When SRGB has changed, update NHLFE Output Label for all Extended Prefix + * with SID index which use the given SR-Node as nexthop though hash_iterate() + */ +static void update_out_nhlfe(struct hash_backet *backet, void *args) +{ + struct listnode *node; + struct sr_node *srn = (struct sr_node *)backet->data; + struct sr_node *srnext = (struct sr_node *)args; + struct sr_prefix *srp; + struct sr_nhlfe new; + + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + /* Process only SID Index for next hop without PHP */ + if ((srp->nexthop == NULL) + && (!CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG))) + continue; + memcpy(&new, &srp->nhlfe, sizeof(struct sr_nhlfe)); + new.label_out = index2label(srp->sid, srnext->srgb); + update_sid_nhlfe(srp->nhlfe, new); + srp->nhlfe.label_out = new.label_out; + } +} + +/* + * Following functions are call when new Segment Routing LSA are received + * - Router Information: ospf_sr_ri_lsa_update() & ospf_sr_ri_lsa_delete() + * - Extended Link: ospf_sr_ext_link_update() & ospf_sr_ext_link_delete() + * - Extended Prefix: ospf_ext_prefix_update() & ospf_sr_ext_prefix_delete() + */ + +/* Update Segment Routing from Router Information LSA */ +void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct tlv_header *tlvh; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + struct ri_sr_tlv_sid_label_range *ri_srgb; + struct ri_sr_tlv_sr_algorithm *algo; + struct sr_srgb srgb; + uint16_t length = 0, sum = 0; + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Process Router " + "Information LSA 4.0.0.%u from %s", + __func__, + GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + + /* Sanity check */ + if (IS_LSA_SELF(lsa)) + return; + + if (OspfSR.neighbors == NULL) { + zlog_err("SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Get SR Node in hash table from Router ID */ + srn = hash_get(OspfSR.neighbors, (void *)&(lsah->adv_router), + (void *)sr_node_new); + + /* Sanity check */ + if (srn == NULL) { + zlog_err( + "SR (%s): Abort! can't create SR node in hash table", + __func__); + return; + } + + if ((srn->instance != 0) && (srn->instance != ntohl(lsah->id.s_addr))) { + zlog_err( + "SR (%s): Abort! Wrong " + "LSA ID 4.0.0.%u for SR node %s/%u", + __func__, + GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router), srn->instance); + return; + } + + /* Collect Router Information Sub TLVs */ + /* Initialize TLV browsing */ + length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + srgb.range_size = 0; + srgb.lower_bound = 0; + + for (tlvh = TLV_HDR_TOP(lsah); (sum < length) && (tlvh != NULL); + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case RI_SR_TLV_SR_ALGORITHM: + algo = (struct ri_sr_tlv_sr_algorithm *)tlvh; + int i; + + for (i = 0; i < ntohs(algo->header.length); i++) + srn->algo[i] = algo->value[0]; + for (; i < ALGORITHM_COUNT; i++) + srn->algo[i] = SR_ALGORITHM_UNSET; + sum += TLV_SIZE(tlvh); + break; + case RI_SR_TLV_SID_LABEL_RANGE: + ri_srgb = (struct ri_sr_tlv_sid_label_range *)tlvh; + srgb.range_size = GET_RANGE_SIZE(ntohl(ri_srgb->size)); + srgb.lower_bound = + GET_LABEL(ntohl(ri_srgb->lower.value)); + sum += TLV_SIZE(tlvh); + break; + case RI_SR_TLV_NODE_MSD: + srn->msd = ((struct ri_sr_tlv_node_msd *)(tlvh))->value; + sum += TLV_SIZE(tlvh); + break; + default: + sum += TLV_SIZE(tlvh); + break; + } + } + + /* Check that we collect mandatory parameters */ + if (srn->algo[0] == SR_ALGORITHM_UNSET || srgb.range_size == 0 + || srgb.lower_bound == 0) { + zlog_warn( + "SR (%s): Missing mandatory parameters. Abort!", + __func__); + hash_release(OspfSR.neighbors, &(srn->adv_router)); + XFREE(MTYPE_OSPF_SR_PARAMS, srn); + return; + } + + /* Check if it is a new SR Node or not */ + if (srn->instance == 0) { + /* update LSA ID */ + srn->instance = ntohl(lsah->id.s_addr); + /* Copy SRGB */ + srn->srgb.range_size = srgb.range_size; + srn->srgb.lower_bound = srgb.lower_bound; + } + + /* Check if SRGB has changed */ + if ((srn->srgb.range_size != srgb.range_size) + || (srn->srgb.lower_bound != srgb.lower_bound)) { + srn->srgb.range_size = srgb.range_size; + srn->srgb.lower_bound = srgb.lower_bound; + /* Update NHLFE if it is a neighbor SR node */ + if (srn->neighbor == OspfSR.self) + hash_iterate(OspfSR.neighbors, + (void (*)(struct hash_backet *, + void *))update_out_nhlfe, + (void *)srn); + } + +} + +/* + * Delete SR Node entry in hash table information corresponding to an expired + * Router Information LSA + */ +void ospf_sr_ri_lsa_delete(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Remove SR node %s from lsa_id 4.0.0.%u", + __func__, inet_ntoa(lsah->adv_router), + GET_OPAQUE_ID(ntohl(lsah->id.s_addr))); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + zlog_err("SR (%s): Abort! no valid SR Data Base", __func__); + return; + } + + /* Release Router ID entry in SRDB hash table */ + srn = hash_release(OspfSR.neighbors, &(lsah->adv_router)); + + /* Sanity check */ + if (srn == NULL) { + zlog_err( + "SR (%s): Abort! no entry in SRDB for SR Node %s", + __func__, inet_ntoa(lsah->adv_router)); + return; + } + + if ((srn->instance != 0) && (srn->instance != ntohl(lsah->id.s_addr))) { + zlog_err( + "SR (%s): Abort! Wrong LSA ID 4.0.0.%u for SR node %s", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + return; + } + + /* Remove SR node */ + sr_node_del(srn); + +} + +/* Update Segment Routing from Extended Link LSA */ +void ospf_sr_ext_link_lsa_update(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct tlv_header *tlvh; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + struct sr_link *srl; + + uint16_t length, sum; + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Process Extended Link LSA 8.0.0.%u from %s", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + zlog_err("SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Get SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_get(OspfSR.neighbors, + (void *)&(lsah->adv_router), + (void *)sr_node_new); + + /* Sanity check */ + if (srn == NULL) { + zlog_err( + "SR (%s): Abort! can't create SR node in hash table", + __func__); + return; + } + + /* Initialize TLV browsing */ + length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + sum = 0; + for (tlvh = TLV_HDR_TOP(lsah); (sum < length) && (tlvh != NULL); + tlvh = TLV_HDR_NEXT(tlvh)) { + if (ntohs(tlvh->type) == EXT_TLV_LINK) { + /* Got Extended Link information */ + srl = get_ext_link_sid(tlvh); + /* Update SID if not null */ + if (srl != NULL) { + srl->instance = ntohl(lsah->id.s_addr); + update_ext_link_sid(srn, srl, lsa->flags); + } + } + sum += TLV_SIZE(tlvh); + } +} + +/* Delete Segment Routing from Extended Link LSA */ +void ospf_sr_ext_link_lsa_delete(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct sr_link *srl; + struct sr_node *srn; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + uint32_t instance = ntohl(lsah->id.s_addr); + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Remove Extended Link LSA 8.0.0.%u from %s", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + zlog_err("SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Search SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + (void *)&(lsah->adv_router)); + + /* + * SR-Node may be NULL if it has been remove previously when + * processing Router Information LSA deletion + */ + if (srn == NULL) { + zlog_warn( + "SR (%s): Stop! no entry in SRDB for SR Node %s", + __func__, inet_ntoa(lsah->adv_router)); + return; + } + + /* Search for corresponding Segment Link */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) + if (srl->instance == instance) + break; + + /* Remove Segment Link if found */ + if ((srl != NULL) && (srl->instance == instance)) { + del_sid_nhlfe(srl->nhlfe[0]); + del_sid_nhlfe(srl->nhlfe[1]); + listnode_delete(srn->ext_link, srl); + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + } else { + zlog_warn( + "SR (%s): Didn't found corresponding SR Link 8.0.0.%u " + "for SR Node %s", __func__, + GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + } + +} + +/* Update Segment Routing from Extended Prefix LSA */ +void ospf_sr_ext_prefix_lsa_update(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct tlv_header *tlvh; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + struct sr_prefix *srp; + + uint16_t length, sum; + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Process Extended Prefix LSA " + "7.0.0.%u from %s", __func__, + GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + zlog_err("SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Get SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_get(OspfSR.neighbors, + (void *)&(lsah->adv_router), + (void *)sr_node_new); + + /* Sanity check */ + if (srn == NULL) { + zlog_err( + "SR (%s): Abort! can't create SR node in hash table", + __func__); + return; + } + + /* Initialize TLV browsing */ + length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + sum = 0; + for (tlvh = TLV_HDR_TOP(lsah); sum < length; + tlvh = TLV_HDR_NEXT(tlvh)) { + if (ntohs(tlvh->type) == EXT_TLV_LINK) { + /* Got Extended Link information */ + srp = get_ext_prefix_sid(tlvh); + /* Update SID if not null */ + if (srp != NULL) { + srp->instance = ntohl(lsah->id.s_addr); + update_ext_prefix_sid(srn, srp); + } + } + sum += TLV_SIZE(tlvh); + } +} + +/* Delete Segment Routing from Extended Prefix LSA */ +void ospf_sr_ext_prefix_lsa_delete(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct sr_prefix *srp; + struct sr_node *srn; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + uint32_t instance = ntohl(lsah->id.s_addr); + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Remove Extended Prefix LSA 7.0.0.%u from %s", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + zlog_err("SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Search SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + (void *)&(lsah->adv_router)); + + /* + * SR-Node may be NULL if it has been remove previously when + * processing Router Information LSA deletion + */ + if (srn == NULL) { + zlog_warn( + "SR (%s): Stop! no entry in SRDB for SR Node %s", + __func__, inet_ntoa(lsah->adv_router)); + return; + } + + /* Search for corresponding Segment Link */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) + if (srp->instance == instance) + break; + + /* Remove Segment Link if found */ + if ((srp != NULL) && (srp->instance == instance)) { + del_sid_nhlfe(srp->nhlfe); + listnode_delete(srn->ext_link, srp); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + } else { + zlog_warn( + "SR (%s): Didn't found corresponding SR Prefix " + "7.0.0.%u for SR Node %s", __func__, + GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + inet_ntoa(lsah->adv_router)); + } + +} + +/* Get Label for Extended Link SID */ +/* TODO: To be replace by Zebra Label Manager */ +uint32_t get_ext_link_label_value(void) +{ + static uint32_t label = ADJ_SID_MIN - 1; + + if (label < ADJ_SID_MAX) + label += 1; + + return label; +} + +/* + * Update Prefix SID. Call by ospf_ext_pref_ism_change to + * complete initial CLI command at startutp. + * + * @param ifp - Loopback interface + * @param pref - Prefix address of this interface + * + * @return - void + */ +void ospf_sr_update_prefix(struct interface *ifp, struct prefix *p) +{ + struct listnode *node; + struct sr_prefix *srp; + + /* Sanity Check */ + if ((ifp == NULL) || (p == NULL)) + return; + + /* + * Search if there is a Segment Prefix that correspond to this + * interface or prefix, and update it if found + */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { + if ((srp->nhlfe.ifindex == ifp->ifindex) + || ((IPV4_ADDR_SAME(&srp->nhlfe.prefv4.prefix, + &p->u.prefix4)) + && (srp->nhlfe.prefv4.prefixlen == p->prefixlen))) { + + /* Update Interface & Prefix info */ + srp->nhlfe.ifindex = ifp->ifindex; + IPV4_ADDR_COPY(&srp->nhlfe.prefv4.prefix, + &p->u.prefix4); + srp->nhlfe.prefv4.prefixlen = p->prefixlen; + srp->nhlfe.prefv4.family = p->family; + IPV4_ADDR_COPY(&srp->nhlfe.nexthop, &p->u.prefix4); + + /* OK. Let's Schedule Extended Prefix LSA */ + srp->instance = ospf_ext_schedule_prefix_index(ifp, + srp->sid, &srp->nhlfe.prefv4, srp->flags); + + /* Install NHLFE if NO-PHP is requested */ + if (CHECK_FLAG(srp->flags, + EXT_SUBTLV_PREFIX_SID_NPFLG)) { + srp->nhlfe.label_in = index2label(srp->sid, + OspfSR.self->srgb); + srp->nhlfe.label_out = MPLS_IMP_NULL_LABEL; + add_sid_nhlfe(srp->nhlfe); + } + } + } +} + +/* + * Following functions are used to update MPLS LFIB after a SPF run + */ + +static void ospf_sr_nhlfe_update(struct hash_backet *backet, void *args) +{ + + struct sr_node *srn = (struct sr_node *)backet->data; + struct listnode *node; + struct sr_prefix *srp; + struct sr_nhlfe old; + int rc; + + /* Sanity Check */ + if (srn == NULL) + return; + + if (IS_DEBUG_OSPF_SR) + zlog_debug(" |- Update Prefix for SR Node %s", + inet_ntoa(srn->adv_router)); + + /* Skip Self SR Node */ + if (srn == OspfSR.self) + return; + + /* Update Extended Prefix */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + + /* Backup current NHLFE */ + memcpy(&old, &srp->nhlfe, sizeof(struct sr_nhlfe)); + + /* Compute the new NHLFE */ + rc = compute_prefix_nhlfe(srp); + + /* Check computation result */ + switch (rc) { + /* next hop is not know, remove old NHLFE to avoid loop */ + case -1: + del_sid_nhlfe(srp->nhlfe); + break; + /* next hop has not changed, skip it */ + case 0: + break; + /* there is a new next hop, update NHLFE */ + case 1: + update_sid_nhlfe(old, srp->nhlfe); + break; + default: + break; + } + } +} + +static int ospf_sr_update_schedule(struct thread *t) +{ + + struct ospf *ospf; + struct timeval start_time, stop_time; + + ospf = THREAD_ARG(t); + ospf->t_sr_update = NULL; + + if (!OspfSR.update) + return 0; + + monotime(&start_time); + + if (IS_DEBUG_OSPF_SR) + zlog_debug("SR (%s): Start SPF update", __func__); + + hash_iterate(OspfSR.neighbors, (void (*)(struct hash_backet *, + void *))ospf_sr_nhlfe_update, + NULL); + + monotime(&stop_time); + + zlog_info( + "SR (%s): SPF Processing Time(usecs): %lld\n", + __func__, + (stop_time.tv_sec - start_time.tv_sec) * 1000000LL + + (stop_time.tv_usec - start_time.tv_usec)); + + OspfSR.update = false; + return 1; +} + +#define OSPF_SR_UPDATE_INTERVAL 1 + +void ospf_sr_update_timer_add(struct ospf *ospf) +{ + + if (ospf == NULL) + return; + + /* Check if an update is not alreday engage */ + if (OspfSR.update) + return; + + OspfSR.update = true; + + thread_add_timer(master, ospf_sr_update_schedule, ospf, + OSPF_SR_UPDATE_INTERVAL, &ospf->t_sr_update); +} + +/* + * -------------------------------------- + * Followings are vty command functions. + * -------------------------------------- + */ + +/* + * Segment Routing Router configuration + * + * Must be centralize as it concerns both Extended Link/Prefix LSA + * and Router Information LSA. Choose to call it from Extended Prefix + * write_config() call back. + * + * @param vty VTY output + * + * @return none + */ +void ospf_sr_config_write_router(struct vty *vty) +{ + struct listnode *node; + struct sr_prefix *srp; + + if (OspfSR.enabled) { + vty_out(vty, " segment-routing on\n"); + + if ((OspfSR.srgb.lower_bound != MPLS_DEFAULT_MIN_SRGB_LABEL) + || (OspfSR.srgb.range_size != MPLS_DEFAULT_MAX_SRGB_SIZE)) { + vty_out(vty, " segment-routing global-block %u %u\n", + OspfSR.srgb.lower_bound, + OspfSR.srgb.lower_bound + + OspfSR.srgb.range_size - 1); + } + if (OspfSR.msd != 0) + vty_out(vty, " segment-routing node-msd %u\n", + OspfSR.msd); + + if (OspfSR.self != NULL) { + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, + srp)) { + vty_out(vty, + " segment-routing prefix %s/%u " + "index %u%s\n", + inet_ntoa(srp->nhlfe.prefv4.prefix), + srp->nhlfe.prefv4.prefixlen, srp->sid, + CHECK_FLAG(srp->flags, + EXT_SUBTLV_PREFIX_SID_NPFLG) ? + " no-php-flag" : ""); + } + } + } +} + +DEFUN(ospf_sr_enable, + ospf_sr_enable_cmd, + "segment-routing on", + SR_STR + "Enable Segment Routing\n") +{ + + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (OspfSR.enabled) + return CMD_SUCCESS; + + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "Segment Routing is only supported in default " + "VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SR: Segment Routing: OFF -> ON"); + + /* Start Segment Routing */ + OspfSR.enabled = true; + if (!ospf_sr_start(ospf)) { + zlog_warn("SR: Unable to start Segment Routing. Abort!"); + return CMD_WARNING; + } + + /* Set Router Information SR parameters */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SR: Activate SR for Router Information LSA"); + + ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); + + /* Update Ext LSA */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SR: Activate SR for Extended Link/Prefix LSA"); + + ospf_ext_update_sr(true); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_sr_enable, + no_ospf_sr_enable_cmd, + "no segment-routing [on]", + NO_STR + SR_STR + "Disable Segment Routing\n") +{ + + if (!OspfSR.enabled) + return CMD_SUCCESS; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SR: Segment Routing: ON -> OFF"); + + /* Start by Disabling Extended Link & Prefix LSA */ + ospf_ext_update_sr(false); + + /* then, disable Router Information SR parameters */ + ospf_router_info_update_sr(false, OspfSR.srgb, OspfSR.msd); + + /* Finally, stop Segment Routing */ + ospf_sr_stop(); + OspfSR.enabled = false; + + return CMD_SUCCESS; +} + +static int ospf_sr_enabled(struct vty *vty) +{ + if (OspfSR.enabled) + return 1; + + if (vty) + vty_out(vty, "%% OSPF SR is not turned on\n"); + + return 0; +} + +DEFUN (sr_sid_label_range, + sr_sid_label_range_cmd, + "segment-routing global-block (0-1048575) (0-1048575)", + SR_STR + "Segment Routing Global Block label range\n" + "Lower-bound range in decimal (0-1048575)\n" + "Upper-bound range in decimal (0-1048575)\n") +{ + uint32_t upper; + uint32_t lower; + uint32_t size; + int idx_low = 2; + int idx_up = 3; + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* Get lower and upper bound */ + lower = strtoul(argv[idx_low]->arg, NULL, 10); + upper = strtoul(argv[idx_up]->arg, NULL, 10); + size = upper - lower + 1; + + if (size > MPLS_DEFAULT_MAX_SRGB_SIZE || size <= 0) { + vty_out(vty, + "Range size cannot be less than 0 or more than %u\n", + MPLS_DEFAULT_MAX_SRGB_SIZE); + return CMD_WARNING_CONFIG_FAILED; + } + + if (upper > MPLS_DEFAULT_MAX_SRGB_LABEL) { + vty_out(vty, "Upper-bound cannot exceed %u\n", + MPLS_DEFAULT_MAX_SRGB_LABEL); + return CMD_WARNING_CONFIG_FAILED; + } + + if (upper < MPLS_DEFAULT_MIN_SRGB_LABEL) { + vty_out(vty, "Upper-bound cannot be lower than %u\n", + MPLS_DEFAULT_MIN_SRGB_LABEL); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check if values have changed */ + if ((OspfSR.srgb.range_size == size) + && (OspfSR.srgb.lower_bound == lower)) + return CMD_SUCCESS; + + /* Set SID/Label range SRGB */ + OspfSR.srgb.range_size = size; + OspfSR.srgb.lower_bound = lower; + if (OspfSR.self != NULL) { + OspfSR.self->srgb.range_size = size; + OspfSR.self->srgb.lower_bound = lower; + } + + /* Set Router Information SR parameters */ + ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); + + /* Update NHLFE entries */ + hash_iterate(OspfSR.neighbors, + (void (*)(struct hash_backet *, void *))update_in_nhlfe, + NULL); + + return CMD_SUCCESS; +} + +DEFUN (no_sr_sid_label_range, + no_sr_sid_label_range_cmd, + "no segment-routing global-block [(0-1048575) (0-1048575)]", + NO_STR + SR_STR + "Segment Routing Global Block label range\n" + "Lower-bound range in decimal (0-1048575)\n" + "Upper-bound range in decimal (0-1048575)\n") +{ + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* Revert to default SRGB value */ + OspfSR.srgb.range_size = MPLS_DEFAULT_MIN_SRGB_SIZE; + OspfSR.srgb.lower_bound = MPLS_DEFAULT_MIN_SRGB_LABEL; + if (OspfSR.self != NULL) { + OspfSR.self->srgb.range_size = OspfSR.srgb.range_size; + OspfSR.self->srgb.lower_bound = OspfSR.srgb.lower_bound; + } + + /* Set Router Information SR parameters */ + ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); + + /* Update NHLFE entries */ + hash_iterate(OspfSR.neighbors, + (void (*)(struct hash_backet *, void *))update_in_nhlfe, + NULL); + + return CMD_SUCCESS; +} + +DEFUN (sr_node_msd, + sr_node_msd_cmd, + "segment-routing node-msd (1-16)", + SR_STR + "Maximum Stack Depth for this router\n" + "Maximum number of label that could be stack (1-16)\n") +{ + uint32_t msd; + int idx = 1; + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* Get MSD */ + argv_find(argv, argc, "(1-16)", &idx); + msd = strtoul(argv[idx]->arg, NULL, 10); + if (msd < 1 || msd > MPLS_MAX_LABELS) { + vty_out(vty, "MSD must be comprise between 1 and %u\n", + MPLS_MAX_LABELS); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check if value has changed */ + if (OspfSR.msd == msd) + return CMD_SUCCESS; + + /* Set this router MSD */ + OspfSR.msd = msd; + if (OspfSR.self != NULL) + OspfSR.self->msd = msd; + + /* Set Router Information SR parameters */ + ospf_router_info_update_sr(true, OspfSR.srgb, OspfSR.msd); + + return CMD_SUCCESS; +} + +DEFUN (no_sr_node_msd, + no_sr_node_msd_cmd, + "no segment-routing node-msd [(1-16)]", + NO_STR + SR_STR + "Maximum Stack Depth for this router\n" + "Maximum number of label that could be stack (1-16)\n") +{ + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* unset this router MSD */ + OspfSR.msd = 0; + if (OspfSR.self != NULL) + OspfSR.self->msd = 0; + + /* Set Router Information SR parameters */ + ospf_router_info_update_sr(true, OspfSR.srgb, 0); + + return CMD_SUCCESS; +} + +DEFUN (sr_prefix_sid, + sr_prefix_sid_cmd, + "segment-routing prefix A.B.C.D/M index (0-65535) [no-php-flag]", + SR_STR + "Prefix SID\n" + "IPv4 Prefix as A.B.C.D/M\n" + "SID index for this prefix in decimal (0-65535)\n" + "Index value inside SRGB (lower_bound < index < upper_bound)\n" + "Don't request Penultimate Hop Popping (PHP)\n") +{ + int idx = 0; + struct prefix p; + uint32_t index; + struct listnode *node; + struct sr_prefix *srp, *new; + struct interface *ifp; + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* Get network prefix */ + argv_find(argv, argc, "A.B.C.D/M", &idx); + if (!str2prefix(argv[idx]->arg, &p)) { + vty_out(vty, "Invalid prefix format %s\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get & verify index value */ + argv_find(argv, argc, "(0-65535)", &idx); + index = strtoul(argv[idx]->arg, NULL, 10); + if (index > OspfSR.srgb.range_size - 1) { + vty_out(vty, "Index %u must be lower than range size %u\n", + index, OspfSR.srgb.range_size); + return CMD_WARNING_CONFIG_FAILED; + } + + /* check that the index is not already used */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { + if (srp->sid == index) { + vty_out(vty, "Index %u is already used\n", index); + return CMD_WARNING_CONFIG_FAILED; + } + } + + /* Create new Extended Prefix to SRDB if not found */ + new = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); + IPV4_ADDR_COPY(&new->nhlfe.prefv4.prefix, &p.u.prefix4); + IPV4_ADDR_COPY(&new->nhlfe.nexthop, &p.u.prefix4); + new->nhlfe.prefv4.prefixlen = p.prefixlen; + new->nhlfe.prefv4.family = p.family; + new->sid = index; + /* Set NO PHP flag if present and compute NHLFE */ + if (argv_find(argv, argc, "no-php-flag", &idx)) { + SET_FLAG(new->flags, EXT_SUBTLV_PREFIX_SID_NPFLG); + new->nhlfe.label_in = index2label(new->sid, OspfSR.self->srgb); + new->nhlfe.label_out = MPLS_IMP_NULL_LABEL; + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Add new index %u to Prefix %s/%u", + __func__, index, inet_ntoa(new->nhlfe.prefv4.prefix), + new->nhlfe.prefv4.prefixlen); + + /* Get Interface and check if it is a Loopback */ + ifp = if_lookup_prefix(&p, VRF_DEFAULT); + if (ifp == NULL) { + /* + * Interface could be not yet available i.e. when this + * command is in the configuration file, OSPF is not yet + * ready. In this case, store the prefix SID for latter + * update of this Extended Prefix + */ + listnode_add(OspfSR.self->ext_prefix, new); + zlog_warn( + "Interface for prefix %s/%u not found. Deferred LSA " + "flooding", inet_ntoa(p.u.prefix4), p.prefixlen); + return CMD_SUCCESS; + } + + if (!if_is_loopback(ifp)) { + vty_out(vty, "interface %s is not a Loopback\n", ifp->name); + XFREE(MTYPE_OSPF_SR_PARAMS, new); + return CMD_WARNING_CONFIG_FAILED; + } + new->nhlfe.ifindex = ifp->ifindex; + + /* Search if this prefix already exist */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { + if ((IPV4_ADDR_SAME(&srp->nhlfe.prefv4.prefix, &p.u.prefix4) + && srp->nhlfe.prefv4.prefixlen == p.prefixlen)) + break; + else + srp = NULL; + } + + /* Update or Add this new SR Prefix */ + if (srp) { + update_sid_nhlfe(srp->nhlfe, new->nhlfe); + listnode_delete(OspfSR.self->ext_prefix, srp); + listnode_add(OspfSR.self->ext_prefix, new); + } else { + listnode_add(OspfSR.self->ext_prefix, new); + add_sid_nhlfe(new->nhlfe); + } + + /* Finally, update Extended Prefix LSA */ + new->instance = ospf_ext_schedule_prefix_index(ifp, new->sid, + &new->nhlfe.prefv4, new->flags); + if (new->instance == 0) { + vty_out(vty, "Unable to set index %u for prefix %s/%u\n", index, + inet_ntoa(p.u.prefix4), p.prefixlen); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN (no_sr_prefix_sid, + no_sr_prefix_sid_cmd, + "no segment-routing prefix A.B.C.D/M [index (0-65535) no-php-flag]", + NO_STR + SR_STR + "Prefix SID\n" + "IPv4 Prefix as A.B.C.D/M\n" + "SID index for this prefix in decimal (0-65535)\n" + "Index value inside SRGB (lower_bound < index < upper_bound)\n" + "Don't request Penultimate Hop Popping (PHP)\n") +{ + int idx = 0; + struct prefix p; + struct listnode *node; + struct sr_prefix *srp; + struct interface *ifp; + bool found = false; + int rc; + + /* Get network prefix */ + argv_find(argv, argc, "A.B.C.D/M", &idx); + rc = str2prefix(argv[idx]->arg, &p); + if (!rc) { + vty_out(vty, "Invalid prefix format %s\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* check that the prefix is already set */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) + if (IPV4_ADDR_SAME(&srp->nhlfe.prefv4.prefix, &p.u.prefix4) + && (srp->nhlfe.prefv4.prefixlen == p.prefixlen)) { + found = true; + break; + } + + if (!found) { + vty_out(vty, "Prefix %s is not found. Abort!\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get Interface */ + ifp = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT); + if (ifp == NULL) { + vty_out(vty, "interface for prefix %s not found.\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Update Extended Prefix LSA */ + if (!ospf_ext_schedule_prefix_index(ifp, 0, NULL, 0)) { + vty_out(vty, "No corresponding loopback interface. Abort!\n"); + return CMD_WARNING; + } + + if (IS_DEBUG_OSPF_SR) + zlog_debug( + "SR (%s): Remove Prefix %s/%u with index %u", + __func__, inet_ntoa(srp->nhlfe.prefv4.prefix), + srp->nhlfe.prefv4.prefixlen, srp->sid); + + /* Delete NHLFE is NO-PHP is set */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) + del_sid_nhlfe(srp->nhlfe); + + /* OK, all is clean, remove SRP from SRDB */ + listnode_delete(OspfSR.self->ext_prefix, srp); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + + return CMD_SUCCESS; +} + + + +static void show_vty_sr_node(struct vty *vty, struct sr_node *srn) +{ + + struct listnode *node; + struct sr_link *srl; + struct sr_prefix *srp; + struct interface *itf; + char pref[16]; + char sid[22]; + char label[8]; + + /* Sanity Check */ + if (srn == NULL) + return; + + vty_out(vty, "SR-Node: %s", inet_ntoa(srn->adv_router)); + vty_out(vty, "\tSRGB (Size/Label): %u/%u", srn->srgb.range_size, + srn->srgb.lower_bound); + vty_out(vty, "\tAlgorithm(s): %s", + srn->algo[0] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF"); + for (int i = 1; i < ALGORITHM_COUNT; i++) { + if (srn->algo[i] == SR_ALGORITHM_UNSET) + continue; + vty_out(vty, "/%s", + srn->algo[i] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF"); + } + if (srn->msd != 0) + vty_out(vty, "\tMSD: %u", srn->msd); + + vty_out(vty, + "\n\n Prefix or Link Label In Label Out " + "Node or Adj. SID Interface Nexthop\n"); + vty_out(vty, + "------------------ -------- --------- " + "--------------------- --------- ---------------\n"); + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + strncpy(pref, inet_ntoa(srp->nhlfe.prefv4.prefix), 16); + snprintf(sid, 22, "SR Pfx (idx %u)", srp->sid); + if (srp->nhlfe.label_out == MPLS_IMP_NULL_LABEL) + sprintf(label, "pop"); + else + sprintf(label, "%u", srp->nhlfe.label_out); + itf = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT); + vty_out(vty, "%15s/%u %8u %9s %21s %9s %15s\n", pref, + srp->nhlfe.prefv4.prefixlen, srp->nhlfe.label_in, label, + sid, itf ? itf->name : "-", + inet_ntoa(srp->nhlfe.nexthop)); + } + + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) { + strncpy(pref, inet_ntoa(srl->nhlfe[0].prefv4.prefix), 16); + snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[0]); + if (srl->nhlfe[0].label_out == MPLS_IMP_NULL_LABEL) + sprintf(label, "pop"); + else + sprintf(label, "%u", srl->nhlfe[0].label_out); + itf = if_lookup_by_index(srl->nhlfe[0].ifindex, VRF_DEFAULT); + vty_out(vty, "%15s/%u %8u %9s %21s %9s %15s\n", pref, + srl->nhlfe[0].prefv4.prefixlen, srl->nhlfe[0].label_in, + label, sid, itf ? itf->name : "-", + inet_ntoa(srl->nhlfe[0].nexthop)); + snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]); + if (srl->nhlfe[1].label_out == MPLS_IMP_NULL_LABEL) + sprintf(label, "pop"); + else + sprintf(label, "%u", srl->nhlfe[0].label_out); + vty_out(vty, "%15s/%u %8u %9s %21s %9s %15s\n", pref, + srl->nhlfe[1].prefv4.prefixlen, srl->nhlfe[1].label_in, + label, sid, itf ? itf->name : "-", + inet_ntoa(srl->nhlfe[1].nexthop)); + } + vty_out(vty, "\n"); +} + +static void show_srdb_entry(struct hash_backet *backet, void *args) +{ + struct vty *vty = (struct vty *)args; + struct sr_node *srn = (struct sr_node *)backet->data; + + show_vty_sr_node(vty, srn); +} + +DEFUN (show_ip_opsf_srdb, + show_ip_ospf_srdb_cmd, + "show ip ospf database segment-routing [adv-router A.B.C.D|self-originate]", + SHOW_STR + IP_STR + OSPF_STR + "Database summary\n" + "Show Segment Routing Data Base\n" + "Advertising SR node\n" + "Advertising SR node ID (as an IP address)\n" + "Self-originated SR node\n") +{ + int idx = 0; + struct in_addr rid; + struct sr_node *srn; + + if (!OspfSR.enabled) { + vty_out(vty, "Segment Routing is disabled on this router\n"); + return CMD_WARNING; + } + + vty_out(vty, "\n OSPF Segment Routing database for ID %s\n\n", + inet_ntoa(OspfSR.self->adv_router)); + + if (argv_find(argv, argc, "self-originate", &idx)) { + srn = OspfSR.self; + show_vty_sr_node(vty, srn); + return CMD_SUCCESS; + } + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &rid)) { + vty_out(vty, + "Specified Router ID %s is invalid\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the SR Node from the SRDB */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + (void *)&rid); + show_vty_sr_node(vty, srn); + return CMD_SUCCESS; + } + + /* No parameters have been provided, Iterate through all the SRDB */ + hash_iterate( + OspfSR.neighbors, + (void (*)(struct hash_backet *, void *))show_srdb_entry, + (void *)vty); + return CMD_SUCCESS; +} + +/* Install new CLI commands */ +void ospf_sr_register_vty(void) +{ + install_element(VIEW_NODE, &show_ip_ospf_srdb_cmd); + + install_element(OSPF_NODE, &ospf_sr_enable_cmd); + install_element(OSPF_NODE, &no_ospf_sr_enable_cmd); + install_element(OSPF_NODE, &sr_sid_label_range_cmd); + install_element(OSPF_NODE, &no_sr_sid_label_range_cmd); + install_element(OSPF_NODE, &sr_node_msd_cmd); + install_element(OSPF_NODE, &no_sr_node_msd_cmd); + install_element(OSPF_NODE, &sr_prefix_sid_cmd); + install_element(OSPF_NODE, &no_sr_prefix_sid_cmd); + +} diff --git a/ospfd/ospf_sr.h b/ospfd/ospf_sr.h new file mode 100644 index 0000000000..cb7d0833ec --- /dev/null +++ b/ospfd/ospf_sr.h @@ -0,0 +1,316 @@ +/* + * This is an implementation of Segment Routing + * as per draft draft-ietf-ospf-segment-routing-extensions-24 + * + * Module name: Segment Routing header definitions + * + * Author: Olivier Dugeon <olivier.dugeon@orange.com> + * Author: Anselme Sawadogo <anselmesawadogo@gmail.com> + * + * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_OSPF_SR_H +#define _FRR_OSPF_SR_H + +/* Default Route priority for OSPF Segment Routing */ +#define OSPF_SR_PRIORITY_DEFAULT 10 + +/* macros and constants for segment routing */ +#define SET_RANGE_SIZE_MASK 0xffffff00 +#define GET_RANGE_SIZE_MASK 0x00ffffff +#define SET_LABEL_MASK 0xffffff00 +#define GET_LABEL_MASK 0x00ffffff +#define SET_RANGE_SIZE(range_size) ((range_size << 8) & SET_RANGE_SIZE_MASK) +#define GET_RANGE_SIZE(range_size) ((range_size >> 8) & GET_RANGE_SIZE_MASK) +#define SET_LABEL(label) ((label << 8) & SET_LABEL_MASK) +#define GET_LABEL(label) ((label >> 8) & GET_LABEL_MASK) + +/* Label range for Adj-SID attribution purpose. Start just right after SRGB */ +#define ADJ_SID_MIN MPLS_DEFAULT_MAX_SRGB_LABEL +#define ADJ_SID_MAX (MPLS_DEFAULT_MAX_SRGB_LABEL + 1000) + +#define OSPF_SR_DEFAULT_METRIC 1 + +/* Segment Routing TLVs as per draft-ietf-ospf-segment-routing-extensions-19 */ + +/* Segment ID could be a Label (3 bytes) or an Index (4 bytes) */ +#define SID_BASE_SIZE 4 +#define SID_LABEL 3 +#define SID_LABEL_SIZE (SID_BASE_SIZE + SID_LABEL) +#define SID_INDEX 4 +#define SID_INDEX_SIZE (SID_BASE_SIZE + SID_INDEX) + +/* SID/Label Sub TLV - section 2.1 */ +#define SUBTLV_SID_LABEL 1 +#define SUBTLV_SID_LABEL_SIZE 8 +struct subtlv_sid_label { + /* Length is 3 (20 rightmost bits MPLS label) or 4 (32 bits SID) */ + struct tlv_header header; + uint32_t value; +}; + +/* + * Following section defines Segment Routing TLV (tag, length, value) + * structures, used in Router Information Opaque LSA. + */ + +/* RI SR-Algorithm TLV - section 3.1 */ +#define RI_SR_TLV_SR_ALGORITHM 8 +struct ri_sr_tlv_sr_algorithm { + struct tlv_header header; +#define SR_ALGORITHM_SPF 0 +#define SR_ALGORITHM_STRICT_SPF 1 +#define SR_ALGORITHM_UNSET 255 +#define ALGORITHM_COUNT 4 + /* Only 4 algorithms supported in this code */ + uint8_t value[ALGORITHM_COUNT]; +}; + +/* RI SID/Label Range TLV - section 3.2 */ +#define RI_SR_TLV_SID_LABEL_RANGE 9 +struct ri_sr_tlv_sid_label_range { + struct tlv_header header; +/* Only 24 upper most bits are significant */ +#define SID_RANGE_LABEL_LENGTH 3 + uint32_t size; + /* A SID/Label sub-TLV will follow. */ + struct subtlv_sid_label lower; +}; + +/* RI Node/MSD TLV as per draft-ietf-ospf-segment-routing-msd-05 */ +#define RI_SR_TLV_NODE_MSD 12 +struct ri_sr_tlv_node_msd { + struct tlv_header header; + uint8_t subtype; /* always = 1 */ + uint8_t value; + uint16_t padding; +}; + +/* + * Following section defines Segment Routing TLV (tag, length, value) + * structures, used in Extended Prefix/Link Opaque LSA. + */ + +/* Adj-SID and LAN-Ajd-SID subtlvs' flags */ +#define EXT_SUBTLV_LINK_ADJ_SID_BFLG 0x80 +#define EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x40 +#define EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x20 +#define EXT_SUBTLV_LINK_ADJ_SID_SFLG 0x10 + +/* Prefix SID subtlv Flags */ +#define EXT_SUBTLV_PREFIX_SID_NPFLG 0x40 +#define EXT_SUBTLV_PREFIX_SID_MFLG 0x20 +#define EXT_SUBTLV_PREFIX_SID_EFLG 0x10 +#define EXT_SUBTLV_PREFIX_SID_VFLG 0x08 +#define EXT_SUBTLV_PREFIX_SID_LFLG 0x04 + +/* SID/Label Binding subtlv Flags */ +#define EXT_SUBTLV_SID_BINDING_MFLG 0x80 + +/* Extended Prefix Range TLV - section 4 */ +#define EXT_TLV_PREF_RANGE 2 +#define EXT_SUBTLV_PREFIX_RANGE_SIZE 12 +struct ext_tlv_prefix_range { + struct tlv_header header; + uint8_t pref_length; + uint8_t af; + uint16_t range_size; + uint8_t flags; + uint8_t reserved[3]; + struct in_addr address; +}; + +/* Prefix SID Sub-TLV - section 5 */ +#define EXT_SUBTLV_PREFIX_SID 2 +#define EXT_SUBTLV_PREFIX_SID_SIZE 8 +struct ext_subtlv_prefix_sid { + struct tlv_header header; + uint8_t flags; + uint8_t reserved; + uint8_t mtid; + uint8_t algorithm; + uint32_t value; +}; + +/* Adj-SID Sub-TLV - section 6.1 */ +#define EXT_SUBTLV_ADJ_SID 2 +#define EXT_SUBTLV_ADJ_SID_SIZE 8 +struct ext_subtlv_adj_sid { + struct tlv_header header; + uint8_t flags; + uint8_t reserved; + uint8_t mtid; + uint8_t weight; + uint32_t value; +}; + +/* LAN Adj-SID Sub-TLV - section 6.2 */ +#define EXT_SUBTLV_LAN_ADJ_SID 3 +#define EXT_SUBTLV_LAN_ADJ_SID_SIZE 12 +struct ext_subtlv_lan_adj_sid { + struct tlv_header header; + uint8_t flags; + uint8_t reserved; + uint8_t mtid; + uint8_t weight; + struct in_addr neighbor_id; + uint32_t value; +}; + +/* + * Following section define structure used to manage Segment Routing + * information and TLVs / SubTLVs + */ + +/* Structure aggregating SRGB info retrieved from an lsa */ +struct sr_srgb { + uint32_t range_size; + uint32_t lower_bound; +}; + +/* SID type to make difference between loopback interfaces and others */ +enum sid_type { PREF_SID, ADJ_SID, LAN_ADJ_SID }; + +/* Structure aggregating all OSPF Segment Routing information for the node */ +struct ospf_sr_db { + /* Status of Segment Routing: enable or disable */ + bool enabled; + + /* Ongoing Update following an OSPF SPF */ + bool update; + + /* Flooding Scope: Area = 10 or AS = 11 */ + uint8_t scope; + + /* FRR SR node */ + struct sr_node *self; + + /* List of neighbour SR nodes */ + struct hash *neighbors; + + /* List of SR prefix */ + struct route_table *prefix; + + /* Local SR info announced in Router Info LSA */ + + /* Algorithms supported by the node */ + uint8_t algo[ALGORITHM_COUNT]; + /* + * Segment Routing Global Block i.e. label range + * Only one range supported in this code + */ + struct sr_srgb srgb; + /* Maximum SID Depth supported by the node */ + uint8_t msd; +}; + +/* Structure aggregating all received SR info from LSAs by node */ +struct sr_node { + struct in_addr adv_router; /* used to identify sender of LSA */ + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + + uint8_t algo[ALGORITHM_COUNT]; /* Algorithms supported by the node */ + /* Segment Routing Global Block i.e. label range */ + struct sr_srgb srgb; + uint8_t msd; /* Maximum SID Depth */ + + /* List of Prefix & Link advertise by this node */ + struct list *ext_prefix; /* For Node SID */ + struct list *ext_link; /* For Adj and LAN SID */ + + /* Pointer to FRR SR-Node or NULL if it is not a neighbor */ + struct sr_node *neighbor; +}; + + +/* Segment Routing - NHLFE info: support IPv4 Only */ +struct sr_nhlfe { + struct prefix_ipv4 prefv4; + struct in_addr nexthop; + ifindex_t ifindex; + mpls_label_t label_in; + mpls_label_t label_out; +}; + +/* Structure aggregating all Segment Routing Link information */ +/* Link are generally advertised by pair: primary + backup */ +struct sr_link { + struct in_addr adv_router; /* used to identify sender of LSA */ + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + + /* Flags to manage this link parameters. */ + uint8_t flags[2]; + + /* Segment Routing ID */ + uint32_t sid[2]; + enum sid_type type; + + /* SR NHLFE for this link */ + struct sr_nhlfe nhlfe[2]; + + /* Back pointer to SR Node which advertise this Link */ + struct sr_node *srn; +}; + +/* Structure aggregating all Segment Routing Prefix information */ +struct sr_prefix { + struct in_addr adv_router; /* used to identify sender of LSA */ + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + + /* Flags to manage this prefix parameters. */ + uint8_t flags; + + /* Segment Routing ID */ + uint32_t sid; + enum sid_type type; + + /* SR NHLFE for this prefix */ + struct sr_nhlfe nhlfe; + + /* Back pointer to SR Node which advertise this Prefix */ + struct sr_node *srn; + + /* + * Pointer to SR Node which is the next hop for this Prefix + * or NULL if next hop is the destination of the prefix + */ + struct sr_node *nexthop; +}; + +/* Prototypes definition */ +/* Segment Routing initialisation functions */ +extern int ospf_sr_init(void); +extern void ospf_sr_term(void); +extern void ospf_sr_finish(void); +/* Segment Routing LSA update & delete functions */ +extern void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa); +extern void ospf_sr_ri_lsa_delete(struct ospf_lsa *lsa); +extern void ospf_sr_ext_link_lsa_update(struct ospf_lsa *lsa); +extern void ospf_sr_ext_link_lsa_delete(struct ospf_lsa *lsa); +extern void ospf_sr_ext_prefix_lsa_update(struct ospf_lsa *lsa); +extern void ospf_sr_ext_prefix_lsa_delete(struct ospf_lsa *lsa); +/* Segment Routing configuration functions */ +extern uint32_t get_ext_link_label_value(void); +extern void ospf_sr_config_write_router(struct vty *vty); +extern void ospf_sr_update_prefix(struct interface *ifp, struct prefix *p); +/* Segment Routing re-routing function */ +extern void ospf_sr_update_timer_add(struct ospf *ospf); +#endif /* _FRR_OSPF_SR_H */ diff --git a/ospfd/ospf_te.c b/ospfd/ospf_te.c index 253b272df6..ab395207b9 100644 --- a/ospfd/ospf_te.c +++ b/ospfd/ospf_te.c @@ -170,6 +170,7 @@ void ospf_mpls_te_term(void) ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA); + OspfMplsTE.enabled = false; ospf_mpls_te_unregister(); @@ -178,6 +179,14 @@ void ospf_mpls_te_term(void) return; } +void ospf_mpls_te_finish(void) +{ + // list_delete_all_node(OspfMplsTE.iflist); + + OspfMplsTE.enabled = false; + OspfMplsTE.inter_as = Off; +} + /*------------------------------------------------------------------------* * Followings are control functions for MPLS-TE parameters management. *------------------------------------------------------------------------*/ diff --git a/ospfd/ospf_te.h b/ospfd/ospf_te.h index 0134214510..ed71e54f54 100644 --- a/ospfd/ospf_te.h +++ b/ospfd/ospf_te.h @@ -406,6 +406,7 @@ struct mpls_te_link { /* Prototypes. */ extern int ospf_mpls_te_init(void); extern void ospf_mpls_te_term(void); +extern void ospf_mpls_te_finish(void); extern struct ospf_mpls_te *get_ospf_mpls_te(void); extern void ospf_mpls_te_update_if(struct interface *); extern void ospf_mpls_te_lsa_schedule(struct mpls_te_link *, enum lsa_opcode); diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c index c923a6f35e..1276f5477c 100644 --- a/ospfd/ospf_vty.c +++ b/ospfd/ospf_vty.c @@ -10239,9 +10239,6 @@ static int config_write_ospf_distribute(struct vty *vty, struct ospf *ospf) if (red->dmetric.type == EXTERNAL_METRIC_TYPE_1) vty_out(vty, " metric-type 1"); - else if (red->dmetric.type == - EXTERNAL_METRIC_TYPE_2) - vty_out(vty, " metric-type 2"); if (ROUTEMAP_NAME(red)) vty_out(vty, " route-map %s", diff --git a/ospfd/ospfd.c b/ospfd/ospfd.c index e6f19369ef..86a3293d71 100644 --- a/ospfd/ospfd.c +++ b/ospfd/ospfd.c @@ -610,6 +610,8 @@ static void ospf_finish_final(struct ospf *ospf) ospf_opaque_type11_lsa_term(ospf); + ospf_opaque_finish(); + ospf_flush_self_originated_lsas_now(ospf); /* Unregister redistribution */ diff --git a/ospfd/ospfd.h b/ospfd/ospfd.h index 5cb8ca85bc..6954660e02 100644 --- a/ospfd/ospfd.h +++ b/ospfd/ospfd.h @@ -240,6 +240,7 @@ struct ospf { struct thread *t_external_lsa; /* AS-external-LSA origin timer. */ struct thread *t_opaque_lsa_self; /* Type-11 Opaque-LSAs origin event. */ + struct thread *t_sr_update; /* Segment Routing update timer */ unsigned int maxage_delay; /* Delay on Maxage remover timer, sec */ struct thread *t_maxage; /* MaxAge LSA remover timer. */ diff --git a/ospfd/subdir.am b/ospfd/subdir.am index e063415fbd..9f04260366 100644 --- a/ospfd/subdir.am +++ b/ospfd/subdir.am @@ -20,6 +20,7 @@ ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_bfd.c \ ospfd/ospf_dump.c \ ospfd/ospf_dump_api.c \ + ospfd/ospf_ext.c \ ospfd/ospf_flood.c \ ospfd/ospf_ia.c \ ospfd/ospf_interface.c \ @@ -36,6 +37,7 @@ ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_route.c \ ospfd/ospf_routemap.c \ ospfd/ospf_spf.c \ + ospfd/ospf_sr.c \ ospfd/ospf_te.c \ ospfd/ospf_vty.c \ ospfd/ospf_zebra.c \ @@ -66,6 +68,7 @@ noinst_HEADERS += \ ospfd/ospf_apiserver.h \ ospfd/ospf_ase.h \ ospfd/ospf_bfd.h \ + ospfd/ospf_ext.h \ ospfd/ospf_flood.h \ ospfd/ospf_ia.h \ ospfd/ospf_interface.h \ @@ -76,6 +79,7 @@ noinst_HEADERS += \ ospfd/ospf_ri.h \ ospfd/ospf_route.h \ ospfd/ospf_spf.h \ + ospfd/ospf_sr.h \ ospfd/ospf_te.h \ ospfd/ospf_vty.h \ ospfd/ospf_zebra.h \ diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index 76ba505ad4..11aeeddf93 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -62,10 +62,6 @@ #include "pim_bfd.h" #include "bfd.h" -static struct cmd_node pim_global_node = { - PIM_NODE, "", 1 /* vtysh ? yes */ -}; - static struct cmd_node interface_node = { INTERFACE_NODE, "%s(config-if)# ", 1 /* vtysh ? yes */ }; @@ -8523,7 +8519,6 @@ DEFUN (show_ip_msdp_sa_sg_vrf_all, void pim_cmd_init(void) { - install_node(&pim_global_node, pim_global_config_write); /* PIM_NODE */ install_node(&interface_node, pim_interface_config_write); /* INTERFACE_NODE */ if_cmd_init(); diff --git a/pimd/pim_instance.c b/pimd/pim_instance.c index 8da610a3a6..9b7ef2e073 100644 --- a/pimd/pim_instance.c +++ b/pimd/pim_instance.c @@ -198,12 +198,13 @@ static int pim_vrf_config_write(struct vty *vty) if (!pim) continue; - if (vrf->vrf_id == VRF_DEFAULT) - continue; + if (vrf->vrf_id != VRF_DEFAULT) + vty_frame(vty, "vrf %s\n", vrf->name); - vty_frame(vty, "vrf %s\n", vrf->name); pim_global_config_write_worker(pim, vty); - vty_endframe(vty, "!\n"); + + if (vrf->vrf_id != VRF_DEFAULT) + vty_endframe(vty, "!\n"); } return 0; diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index 450faf75bb..791680a911 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -232,11 +232,6 @@ int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty) return writes; } -int pim_global_config_write(struct vty *vty) -{ - return pim_global_config_write_worker(pimg, vty); -} - int pim_interface_config_write(struct vty *vty) { struct pim_instance *pim; diff --git a/pimd/pim_vty.h b/pimd/pim_vty.h index aef90cacc3..22ac3333e4 100644 --- a/pimd/pim_vty.h +++ b/pimd/pim_vty.h @@ -23,7 +23,6 @@ #include "vty.h" int pim_debug_config_write(struct vty *vty); -int pim_global_config_write(struct vty *vty); int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty); int pim_interface_config_write(struct vty *vty); diff --git a/tests/bgpd/test_capability.c b/tests/bgpd/test_capability.c index 3d5518f3b7..69f5afb992 100644 --- a/tests/bgpd/test_capability.c +++ b/tests/bgpd/test_capability.c @@ -63,8 +63,8 @@ static struct test_segment { /* AFI/SAFI validation */ int validate_afi; - afi_t afi; - safi_t safi; + iana_afi_t afi; + iana_safi_t safi; #define VALID_AFI 1 #define INVALID_AFI 0 int afi_valid; @@ -113,8 +113,8 @@ static struct test_segment mp_segments[] = { SHOULD_PARSE, 0, 1, - AFI_IP, - SAFI_UNICAST, + IANA_AFI_IPV4, + IANA_SAFI_UNICAST, VALID_AFI, }, { @@ -125,8 +125,8 @@ static struct test_segment mp_segments[] = { SHOULD_PARSE, 0, 1, - AFI_IP6, - SAFI_UNICAST, + IANA_AFI_IPV6, + IANA_SAFI_UNICAST, VALID_AFI, }, /* 5 */ @@ -138,8 +138,8 @@ static struct test_segment mp_segments[] = { SHOULD_PARSE, 0, 1, - AFI_IP, - SAFI_MULTICAST, + IANA_AFI_IPV4, + IANA_SAFI_MULTICAST, VALID_AFI, }, /* 6 */ @@ -151,7 +151,7 @@ static struct test_segment mp_segments[] = { SHOULD_PARSE, 0, 1, - AFI_IP6, + IANA_AFI_IPV6, IANA_SAFI_MPLS_VPN, VALID_AFI, }, @@ -164,7 +164,7 @@ static struct test_segment mp_segments[] = { SHOULD_PARSE, 0, 1, - AFI_IP6, + IANA_AFI_IPV6, IANA_SAFI_MPLS_VPN, VALID_AFI, }, @@ -177,7 +177,7 @@ static struct test_segment mp_segments[] = { SHOULD_PARSE, 0, 1, - AFI_IP, + IANA_AFI_IPV4, IANA_SAFI_MPLS_VPN, VALID_AFI, }, @@ -211,8 +211,8 @@ static struct test_segment mp_segments[] = { SHOULD_ERR, 0, 1, - AFI_IP, - SAFI_UNICAST, + IANA_AFI_IPV4, + IANA_SAFI_UNICAST, VALID_AFI, }, {NULL, NULL, {0}, 0, 0}}; @@ -844,8 +844,7 @@ static void parse_test(struct peer *peer, struct test_segment *t, int type) safi_t safi; /* Convert AFI, SAFI to internal values, check. */ - if (bgp_map_afi_safi_iana2int(afi_int2iana(t->afi), t->safi, - &afi, &safi)) { + if (bgp_map_afi_safi_iana2int(t->afi, t->safi, &afi, &safi)) { if (t->afi_valid == VALID_AFI) failed++; } diff --git a/tests/bgpd/test_mp_attr.c b/tests/bgpd/test_mp_attr.c index 6df784b984..8acb280ed3 100644 --- a/tests/bgpd/test_mp_attr.c +++ b/tests/bgpd/test_mp_attr.c @@ -63,13 +63,6 @@ static struct test_segment { #define SHOULD_PARSE 0 #define SHOULD_ERR -1 int parses; /* whether it should parse or not */ - - /* AFI/SAFI validation */ - afi_t afi; - safi_t safi; -#define VALID_AFI 1 -#define INVALID_AFI 0 - int afi_valid; } mp_reach_segments[] = { { "IPv6", @@ -104,9 +97,6 @@ static struct test_segment { }, (4 + 16 + 1 + 5), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-2", @@ -150,9 +140,6 @@ static struct test_segment { }, (4 + 16 + 1 + 5 + 9), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-default", @@ -197,9 +184,6 @@ static struct test_segment { }, (4 + 16 + 1 + 5 + 9 + 1), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-lnh", @@ -260,9 +244,6 @@ static struct test_segment { }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-nhlen", @@ -323,9 +304,6 @@ static struct test_segment { }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-nhlen2", @@ -386,9 +364,6 @@ static struct test_segment { }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-nhlen3", @@ -417,9 +392,6 @@ static struct test_segment { }, (4 + 16), SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-nhlen4", @@ -480,9 +452,6 @@ static struct test_segment { }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-nlri", @@ -543,9 +512,6 @@ static struct test_segment { }, (4 + 32 + 1 + 5 + 9 + 1), SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4", @@ -561,9 +527,6 @@ static struct test_segment { }, (4 + 4 + 1 + 3 + 4 + 1), SHOULD_PARSE, - AFI_IP, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4-nhlen", @@ -579,9 +542,6 @@ static struct test_segment { }, (4 + 4 + 1 + 3 + 4 + 1), SHOULD_ERR, - AFI_IP, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4-nlrilen", @@ -596,9 +556,6 @@ static struct test_segment { }, (4 + 4 + 1 + 3 + 2 + 1), SHOULD_ERR, - AFI_IP, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4-VPNv4", @@ -623,9 +580,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_PARSE, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-bogus-plen", @@ -659,9 +613,6 @@ static struct test_segment { }, (3 + 1 + 3 * 4 + 1 + 3 + 4 + 1), SHOULD_ERR, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-plen1-short", @@ -686,9 +637,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_ERR, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-plen1-long", @@ -713,9 +661,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_ERR, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-plenn-long", @@ -741,9 +686,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3) + 1), SHOULD_ERR, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-plenn-short", @@ -768,9 +710,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_ERR, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-bogus-rd-type", @@ -795,9 +734,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_PARSE, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, { "IPv4-VPNv4-0-nlri", @@ -823,9 +759,6 @@ static struct test_segment { }, (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3) + 1), SHOULD_ERR, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, /* From bug #385 */ @@ -875,9 +808,6 @@ static struct test_segment { }, 37, SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, {NULL, NULL, {0}, 0, 0}}; @@ -894,9 +824,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + 5), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-unreach2", @@ -910,9 +837,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + 5 + 9), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-unreach-default", @@ -926,9 +850,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + 5 + 9 + 1), SHOULD_PARSE, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv6-unreach-nlri", @@ -942,9 +863,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + 5 + 9 + 1), SHOULD_ERR, - AFI_IP6, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4-unreach", @@ -957,9 +875,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + 3 + 4 + 1), SHOULD_PARSE, - AFI_IP, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4-unreach-nlrilen", @@ -971,9 +886,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + 3 + 2 + 1), SHOULD_ERR, - AFI_IP, - SAFI_UNICAST, - VALID_AFI, }, { "IPv4-unreach-VPNv4", @@ -993,9 +905,6 @@ static struct test_segment mp_unreach_segments[] = { }, (3 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), SHOULD_PARSE, - AFI_IP, - IANA_SAFI_MPLS_VPN, - VALID_AFI, }, {NULL, NULL, {0}, 0, 0}}; diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl new file mode 100755 index 0000000000..8477383469 --- /dev/null +++ b/tools/checkpatch.pl @@ -0,0 +1,6487 @@ +#!/usr/bin/env perl +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp <jschopp@austin.ibm.com> (the ugly bit) +# (c) 2007,2008, Andy Whitcroft <apw@uk.ibm.com> (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft <apw@canonical.com> +# Licensed under the terms of the GNU GPL License version 2 + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $tree = 1; +my $chk_signoff = 1; +my $chk_patch = 1; +my $tst_only; +my $emacs = 0; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $max_line_length = 80; +my $ignore_perl_version = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $typedefsfile = ""; +my $color = "auto"; +my $allow_c99_comments = 1; + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + <rev> + <rev>^ + <rev>~n + multiple git commits with: + <rev1>..<rev2> + <rev1>...<rev2> + <rev>-<count> + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=n set the maximum line length, if exceeded, warn + --min-conf-desc-length=n set the min description length, if shorter, warn + --root=PATH PATH to the kernel tree root + --no-summary suppress the per-file summary + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + "<inputfile>.EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --codespell Use the codespell dictionary for spelling/typos + (default:/usr/share/codespell/dictionary.txt) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + -h, --help, --version display this help and exit + +When FILE is - read standard input. +EOM + + exit($exitcode); +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my @types = (); + # Also catch when type or level is passed through a variable + for ($text =~ /(?:(?:\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + push (@types, $_); + } + @types = sort(uniq(@types)); + print("#\tMessage type\n\n"); + foreach my $type (@types) { + print(++$count . "\t" . $type . "\n"); + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'h|help' => \$help, + 'version' => \$help +) or help(1); + +help(0) if ($help); + +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +if ($^V && $^V lt $minimum_perl_version) { + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + if (!$ignore_perl_version) { + exit(1); + } +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "Invalid color mode: $color\n"; +} + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __init_refok| + __kprobes| + __ref| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __kprobes| + $InitAttribute| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?<!-)>}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + To:| + Cc: +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if ($$wordsRef ne ""); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs = ""; +read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; + +my $typeOtherTypedefs = ""; +if (length($typedefsfile)) { + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; +} +$typeTypedefs .= '|' . $typeOtherTypedefs if ($typeOtherTypedefs ne ""); + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)? + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)? + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:$Storage\s+)?${Type}\s+uninitialized_var\s*\( +)}; + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + my $status = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + + return $status =~ /obsolete/i; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e ".git") { + my $git_last_include_commit = `git log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e ".git") { + $files = `git ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e ".git")); + + my $output = `git log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous\./) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e ".git"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `git log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +for my $filename (@ARGV) { + my $FILE; + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if ($^V lt 5.10.0) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl v5.10.0 is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/$address.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith <joe@smith.com" bad + if ($name ne "" && $address !~ /^<[^>]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + $name = trim($name); + $name =~ s/^\"|\"$//g; + $address = trim($address); + $address =~ s/^\<|\>$//g; + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?<!\\)"/\\"/g; ##escape quotes + $name = "\"$name\""; + } + + return ($name, $address, $comment); +} + +sub format_email { + my ($name, $address) = @_; + + my $formatted_email; + + $name = trim($name); + $name =~ s/^\"|\"$//g; + $address = trim($address); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?<!\\)"/\\"/g; ##escape quotes + $name = "\"$name\""; + } + + if ("$name" eq "") { + $formatted_email = "$address"; + } else { + $formatted_email = "$name <$address>"; + } + + return $formatted_email; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % 8) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are wacking completly including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # Catch a comment on the end of the line itself. + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + $output = (split('\n', $output))[0] . "\n" if ($terse); + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = 8; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub remove_defuns { + my @breakfast = (); + my $milktoast; + for my $tasty (@rawlines) { + $milktoast = $tasty; + if (($tasty =~ /^\+DEFPY/ || + $tasty =~ /^\+DEFUN/ || + $tasty =~ /^\+ALIAS/) .. ($tasty =~ /^\+\{/)) { + $milktoast = "\n"; + } + push(@breakfast, $milktoast); + } + @rawlines = @breakfast; +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + our $clean = 1; + my $signoff = 0; + my $is_patch = 0; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + sanitise_line_reset(); + remove_defuns(); + + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.rst$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in include/asm-<architecture>\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/[\w/]+@ && + $line =~ m@^\s+diff\b.*a/([\w/]+)\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:/i) { + $signoff++; + $in_commit_log = 0; + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + my ($email_name, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $email_address)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" </ </; + # Don't force email to have quotes + # Allow just an angle bracketed address + if ("$dequoted$comment" ne $email && + "<$email_address>$comment" ne $email && + "$suggested_email$comment" ne $email) { + WARN("BAD_SIGN_OFF", + "email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr); + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for old stable address + if ($line =~ /^\s*cc:\s*.*<?\bstable\@kernel\.org\b>?.*$/i) { + ERROR("STABLE_ADDRESS", + "The 'stable' address should be 'stable\@vger.kernel.org'\n" . $herecurr); + } + +# Check for unwanted Gerrit info + if ($in_commit_log && $line =~ /^\s*change-id:/i) { + ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream.\n" . $herecurr); + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/)) { + # stack dump address + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-]+\/)++[\w\.\-]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|Link:)/i || + # A Fixes: or Link: line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Possible unwrapped commit description (prefer a maximum 75 chars per line)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for git id commit length and improperly formed commit descriptions + if ($in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + ($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $hasdesc = 0; + my $hasparens = 0; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + + if ($line =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + } elsif ($line =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + $short = 0 if ($line =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($line =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($line =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($line =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + if ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)"\)/i) { + $orig_desc = $1; + $hasparens = 1; + } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s*$/i && + defined $rawlines[$linenr] && + $rawlines[$linenr] =~ /^\s*\("([^"]+)"\)/) { + $orig_desc = $1; + $hasparens = 1; + } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("[^"]+$/i && + defined $rawlines[$linenr] && + $rawlines[$linenr] =~ /^\s*[^"]+"\)/) { + $line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)$/i; + $orig_desc = $1; + $rawlines[$linenr] =~ /^\s*([^"]+)"\)/; + $orig_desc .= " " . $1; + $hasparens = 1; + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$hasparens)) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herecurr); + } + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + while ($rawline =~ /(?:^|[^a-z@])($misspellings)(?:\b|$|[^a-z@])/gi) { + my $typo = $1; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + $line =~ /^\+\s*config\s+/) { + my $length = 0; + my $cnt = $realcnt; + my $ln = $linenr + 1; + my $f; + my $is_start = 0; + my $is_end = 0; + for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) { + $f = $lines[$ln - 1]; + $cnt-- if ($lines[$ln - 1] !~ /^-/); + $is_end = $lines[$ln - 1] =~ /^\+/; + + next if ($f =~ /^-/); + last if (!$file && $f =~ /^\@\@/); + + if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate)\s*\"/) { + $is_start = 1; + } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) { + $length = -1; + } + + $f =~ s/^.//; + $f =~ s/#.*//; + $f =~ s/^\s+//; + next if ($f =~ /^$/); + if ($f =~ /^\s*config\s/) { + $is_end = 1; + last; + } + $length++; + } + if ($is_start && $is_end && $length < $min_conf_desc_length) { + WARN("CONFIG_DESCRIPTION", + "please write a paragraph that describes the config symbol fully\n" . $herecurr); + } + #print "is_start<$is_start> is_end<$is_end> length<$length>\n"; + } + +# check for MAINTAINERS entries that don't have the right form + if ($realfile =~ /^MAINTAINERS$/ && + $rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } + +# discourage the use of boolean for type definition attributes of Kconfig options + if ($realfile =~ /Kconfig/ && + $line =~ /^\+\s*\bboolean\b/) { + WARN("CONFIG_TYPE_BOOLEAN", + "Use of boolean is deprecated, please use bool instead.\n" . $herecurr); + } + + if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && + ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { + my $flag = $1; + my $replacement = { + 'EXTRA_AFLAGS' => 'asflags-y', + 'EXTRA_CFLAGS' => 'ccflags-y', + 'EXTRA_CPPFLAGS' => 'cppflags-y', + 'EXTRA_LDFLAGS' => 'ldflags-y', + }; + + WARN("DEPRECATED_VARIABLE", + "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.txt"; + + foreach my $compat (@compats) { + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "^$vendor\\b" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|s|S|sh|dtsi|dts)$/); + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + (show_type("LONG_LINE") || show_type($msg_type))) { + WARN($msg_type, + "line over $max_line_length characters\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr); + } + +# Blackfin: use hi/lo macros + if ($realfile =~ m@arch/blackfin/.*\.S$@) { + if ($line =~ /\.[lL][[:space:]]*=.*&[[:space:]]*0x[fF][fF][fF][fF]/) { + my $herevet = "$here\n" . cat_vet($line) . "\n"; + ERROR("LO_MACRO", + "use the LO() macro, not (... & 0xFFFF)\n" . $herevet); + } + if ($line =~ /\.[hH][[:space:]]*=.*>>[[:space:]]*16/) { + my $herevet = "$here\n" . cat_vet($line) . "\n"; + ERROR("HI_MACRO", + "use the HI() macro, not (... >> 16)\n" . $herevet); + } + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than 8 must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {8,8}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev); + } + +# check indentation starts on a tab stop + if ($^V && $^V ge 5.10.0 && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$))/) { + my $indent = length($1); + if ($indent % 8) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/8)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($^V && $^V ge 5.10.0 && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / 8) . + " " x ($pos % 8); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comment styles +# Networking with an initial /* + if ($realfile =~ m@^(drivers/net/|net/)@ && + $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ && + $rawline =~ /^\+[ \t]*\*/ && + $realline > 2) { + WARN("NETWORKING_BLOCK_COMMENT_STYLE", + "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev); + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*EXPORT_SYMBOL/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations + if ($sline =~ /^\+\s+\S/ && #Not at char 1 + # actual declarations + ($prevline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $prevline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $prevline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $prevline =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($prevline =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $prevline =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $prevline =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sline =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sline =~ /^\+\s+(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sline =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sline =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sline =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/) && + # indentation of previous and current line are the same + (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto or return and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(?:goto|return)\b/) { + WARN("UNNECESSARY_BREAK", + "break is not useful after a goto or return\n" . $hereprev); + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# Blackfin: don't use __builtin_bfin_[cs]sync + if ($line =~ /__builtin_bfin_csync/) { + my $herevet = "$here\n" . cat_vet($line) . "\n"; + ERROR("CSYNC", + "use the CSYNC() macro in asm/blackfin.h\n" . $herevet); + } + if ($line =~ /__builtin_bfin_ssync/) { + my $herevet = "$here\n" . cat_vet($line) . "\n"; + ERROR("SSYNC", + "use the SSYNC() macro in asm/blackfin.h\n" . $herevet); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % 8) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + 8))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ || + $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/ || + $line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s+$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# avoid BUG() or BUG_ON() + if ($line =~ /\b(?:BUG|BUG_ON)\b/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("AVOID_BUG", + "Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + + if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to printk(KERN_$orig ...\n" . $herecurr); + } + + if ($line =~ /\bpr_warning\s*\(/) { + if (WARN("PREFER_PR_LEVEL", + "Prefer pr_warn(... to pr_warning(...\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\bpr_warning\b/pr_warn/; + } + } + + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if (($line=~/$Type\s*$Ident\(.*\).*\s*{/) and + !($line=~/\#\s*define.*do\s\{/) and !($line=~/}/)) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function declarations go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*){(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsly report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /do\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# check for unnecessary parentheses around comparisons in if uses + if ($^V && $^V ge 5.10.0 && defined($stat) && + $stat =~ /(^.\s*if\s*($balanced_parens))/) { + my $if_stat = $1; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +#goto labels aren't indented, allow a single space however + if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and + !($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($^V && $^V ge 5.10.0 && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($^V && $^V ge 5.10.0 && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($^V && $^V ge 5.10.0 && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR') { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr); + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real); + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#gcc binary extension + if ($var =~ /^$Binary$/) { + if (WARN("GCC_BINARY_CONSTANT", + "Avoid gcc v4.3+ binary constant extension: <$var>\n" . $herecurr) && + $fix) { + my $hexval = sprintf("0x%x", oct($var)); + $fixed[$fixlinenr] =~ + s/\b$var\b/$hexval/; + } + } + +#CamelCase + if ($var !~ /^$Constant$/ && + $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore Page<foo> variants + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore SI style variants like nS, mV and dB (ie: max_uV, regulator_min_uA_show) + $var !~ /^(?:[a-z_]*?)_?[a-z][A-Z](?:_[a-z_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# multi-statement macros should be enclosed in a do while loop, grab the +# first statement and ensure its the whole macro if its not enclosed +# in a known good container + if ($realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1/ || + $dstat =~ s/\{[^\{\}]*\}/1/ || + $dstat =~ s/.\[[^\[\]]*\]/1/) + { + } + + # Flatten any obvious string concatentation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + my $exceptions = qr{ + $Declare| + module_param_named| + MODULE_PARM_DESC| + DECLARE_PER_CPU| + DEFINE_PER_CPU| + __typeof__\(| + union| + struct| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $herectx = $here . "\n"; + my $stmt_cnt = statement_rawlines($ctx); + + for (my $n = 0; $n < $stmt_cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); + } elsif ($dstat =~ /;/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); + } else { + ERROR("COMPLEX_MACRO", + "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = $tmp_stmt =~ s/\b$arg\b//g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + CHK("MACRO_ARG_PRECEDENCE", + "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); + } + } + +# check for macros with flow control, but without ## concatenation +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $herectx = $here . "\n"; + my $cnt = statement_rawlines($ctx); + + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($^V && $^V ge 5.10.0 && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = $here . "\n"; + + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = $here . "\n"; + + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# make sure symbols are always wrapped with VMLINUX_SYMBOL() ... +# all assignments may have only one of the following with an assignment: +# . +# ALIGN(...) +# VMLINUX_SYMBOL(...) + if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) { + WARN("MISSING_VMLINUX_SYMBOL", + "vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr); + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $herectx = $here . "\n"; + my $cnt = statement_rawlines($block); + + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Z_]/ || $line =~ /[A-Za-z0-9_]$String/) { + CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr); + } + +# uncoalesced string fragments + if ($line =~ /$String\s*"/) { + WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr); + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $rawline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + CHK("REDUNDANT_CODE", + "if this code is redundant consider removing it\n" . + $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*(?:devm_)?(?:[kv][czm]alloc(?:_node|_array)?\b|kstrdup|kmemdup|(?:dev_)?alloc_skb)/) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for mask then right shift without a parentheses + if ($^V && $^V ge 5.10.0 && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($^V && $^V ge 5.10.0) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $herecurr); + } + if ($delay > 2000) { + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb| + read_barrier_depends + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for __attribute__ packed, prefer __packed + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) { + WARN("PREFER_PACKED", + "__packed is preferred over __attribute__((packed))\n" . $herecurr); + } + +# Check for __attribute__ aligned, prefer __aligned + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(.*aligned/) { + WARN("PREFER_ALIGNED", + "__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr); + } + +# Check for __attribute__ format(printf, prefer __printf + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) { + if (WARN("PREFER_PRINTF", + "__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf\s*,\s*(.*)\)\s*\)\s*\)/"__printf(" . trim($1) . ")"/ex; + + } + } + +# Check for __attribute__ format(scanf, prefer __scanf + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) { + if (WARN("PREFER_SCANF", + "__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\s*,\s*(.*)\)\s*\)\s*\)/"__scanf(" . trim($1) . ")"/ex; + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($^V && $^V ge 5.10.0 && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant\n" . $herecurr) && + $fix) { + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + + # check for vsprintf extension %p<foo> misuses + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $bad_extension = ""; + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + if ($fmt =~ /(\%[\*\d\.]*p(?![\WFfSsBKRraEhMmIiUDdgVCbGNOx]).)/) { + $bad_extension = $1; + last; + } + } + if ($bad_extension ne "") { + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + WARN("VSPRINTF_POINTER_EXTENSION", + "Invalid vsprintf pointer extension '$bad_extension'\n" . "$here\n$stat_real\n"); + } + } + +# Check for misused memsets + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($^V && $^V ge 5.10.0 && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($^V && $^V ge 5.10.0 && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($^V && $^V ge 5.10.0 && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } + +# typecasts on min/max could be min_t/max_t + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "usleep_range should not use min == max args; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n"); + } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && + $min > $max) { + WARN("USLEEP_RANGE", + "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($^V && $^V ge 5.10.0 && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($^V && $^V ge 5.10.0 && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/ && + $function_name ne 'uninitialized_var') + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.rst\n" . $herecurr); + } + } + +# check for pointless casting of kmalloc return + if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($^V && $^V ge 5.10.0 && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*([kv][mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for k[mz]alloc with multiplies that could be kmalloc_array/kcalloc + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_array"; + $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $ctx = ''; + my $herectx = $here . "\n"; + my $cnt = statement_rawlines($stat); + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; + } + } + } + +# check for krealloc arg reuse + if ($^V && $^V ge 5.10.0 && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*\1\s*,/) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b(kcalloc|kmalloc_array)\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(CONFIG_[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to CONFIG_<FOO> || CONFIG_<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for case / default statements not preceded by break/fallthrough/switch + if ($line =~ /^.\s*(?:case\s+(?:$Ident|$Constant)\s*|default):/) { + my $has_break = 0; + my $has_statement = 0; + my $count = 0; + my $prevline = $linenr; + while ($prevline > 1 && ($file || $count < 3) && !$has_break) { + $prevline--; + my $rline = $rawlines[$prevline - 1]; + my $fline = $lines[$prevline - 1]; + last if ($fline =~ /^\@\@/); + next if ($fline =~ /^\-/); + next if ($fline =~ /^.(?:\s*(?:case\s+(?:$Ident|$Constant)[\s$;]*|default):[\s$;]*)*$/); + $has_break = 1 if ($rline =~ /fall[\s_-]*(through|thru)/i); + next if ($fline =~ /^.[\s$;]*$/); + $has_statement = 1; + $count++; + $has_break = 1 if ($fline =~ /\bswitch\b|\b(?:break\s*;[\s$;]*$|exit\s*\(\b|return\b|goto\b|continue\b)/); + } + if (!$has_break && $has_statement) { + WARN("MISSING_BREAK", + "Possible switch case/default not preceded by break or fallthrough comment\n" . $herecurr); + } + } + +# check for switch/default statements without a break; + if ($^V && $^V ge 5.10.0 && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $ctx = ''; + my $herectx = $here . "\n"; + my $cnt = statement_rawlines($stat); + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' + if ($line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($^V && $^V ge 5.10.0 && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# whine about ACCESS_ONCE + if ($^V && $^V ge 5.10.0 && + $line =~ /\bACCESS_ONCE\s*$balanced_parens\s*(=(?!=))?\s*($FuncArg)?/) { + my $par = $1; + my $eq = $2; + my $fun = $3; + $par =~ s/^\(\s*(.*)\s*\)$/$1/; + if (defined($eq)) { + if (WARN("PREFER_WRITE_ONCE", + "Prefer WRITE_ONCE(<FOO>, <BAR>) over ACCESS_ONCE(<FOO>) = <BAR>\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bACCESS_ONCE\s*\(\s*\Q$par\E\s*\)\s*$eq\s*\Q$fun\E/WRITE_ONCE($par, $fun)/; + } + } else { + if (WARN("PREFER_READ_ONCE", + "Prefer READ_ONCE(<FOO>) over ACCESS_ONCE(<FOO>)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bACCESS_ONCE\s*\(\s*\Q$par\E\s*\)/READ_ONCE($par)/; + } + } + } + +# check for mutex_trylock_recursive usage + if ($line =~ /mutex_trylock_recursive/) { + ERROR("LOCKING", + "recursive locking is bad, do not use this ever.\n" . $herecurr); + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop + if ($^V && $^V ge 5.10.0 && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4)) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + if ($line =~ /\b$mode_perms_string_search\b/) { + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($line =~ /\b(($mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($line); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + my $octal = sprintf("%04o", $to); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$val/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n"); + } + if ($is_patch && $has_commit_log && $chk_signoff && $signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} diff --git a/tools/checkpatch.sh b/tools/checkpatch.sh new file mode 100755 index 0000000000..3536df79a9 --- /dev/null +++ b/tools/checkpatch.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Check a patch for style errors. +usage="./checkpatch.sh <patch> <tree>" +patch=$1 +tree=$2 +checkpatch="$tree/tools/checkpatch.pl --no-tree -f" +ignore="ldpd\|babeld" +cwd=${PWD##*/} +dirty=0 +stat=0 + +if [[ -z "$1" || -z "$2" ]]; then + echo "$usage" + exit 0 +fi + +# remove temp directories +rm -rf /tmp/f1 /tmp/f2 + +# save working tree +if git -C $tree status --porcelain | egrep --silent '^(\?\?|.[DM])'; then + echo "Detected dirty tree, caching state..." + dirty=1 + git -C $tree config gc.auto 0; + td=$(git -C $tree status -z | grep -z "^[ARM]D" | cut -z -d' ' -f2- | tr '\0' '\n') + INDEX=$(git -C $tree write-tree) + git -C $tree add -f . + WORKTREE=$(git -C $tree write-tree) + echo "Saved index to $INDEX" + echo "Saved working tree to $WORKTREE" +fi + +# double check +if git -C $tree status --porcelain | egrep --silent '^(\?\?|.[DM])'; then + echo "[!] git working directory must be clean." + exit 1 +fi + +git -C $tree reset --hard +git -C $tree apply < $patch +mkdir -p /tmp/f1 /tmp/f2 +mod=$(git -C $tree ls-files -m | grep ".*\.[ch]" | grep -v $ignore) +mod+=" $(git -C $tree ls-files --others --exclude-standard | grep '.*\.[ch]' | grep -v $ignore)" +echo $mod +if [ -z "$mod" ]; then + echo "There doesn't seem to be any changes." +else + cp $tree/$mod /tmp/f1/ + git -C $tree reset --hard + git -C $tree clean -fd + cp $tree/$mod /tmp/f2/ + echo "Running style checks..." + for file in /tmp/f1/*; do + echo "$checkpatch $file > $file _cp" + $checkpatch $file > "$file"_cp 2> /dev/null + done + for file in /tmp/f2/*; do + echo "$checkpatch $file > $file _cp" + $checkpatch $file > "$file"_cp 2> /dev/null + done + echo "Done." + for file in /tmp/f1/*_cp; do + echo "Report for $(basename $file _cp)" + echo "===============================================" + if [ -a /tmp/f2/$(basename $file) ]; then + diff $file /tmp/f2/$(basename $file) | grep -v "normally be const" | grep -A3 "ERROR\|WARNING" + else + cat $file | grep -v "normally be const" | grep -A3 "ERROR\|WARNING" + fi + if [ "$?" -eq "0" ]; then + stat=1 + fi + done +fi + +# restore working tree +if [ $dirty -eq 1 ]; then + git -C $tree read-tree $WORKTREE; + git -C $tree checkout-index -af; + git -C $tree read-tree $INDEX; + if [ -n "$td" ]; then + rm $td + fi + git -C $tree config --unset gc.auto; +fi + +exit $stat diff --git a/tools/start-stop-daemon.c b/tools/start-stop-daemon.c index 8dc16f4209..6bf55b7740 100644 --- a/tools/start-stop-daemon.c +++ b/tools/start-stop-daemon.c @@ -396,7 +396,7 @@ static void parse_schedule_item(const char *string, struct schedule_item *item) if (!strcmp(string, "forever")) { item->type = sched_forever; - } else if (isdigit(string[0])) { + } else if (isdigit((int)string[0])) { item->type = sched_timeout; if (parse_integer(string, &item->value) != 0) badusage("invalid timeout value in schedule"); diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am index 3ddb6aba54..c9b6f50160 100644 --- a/vtysh/Makefile.am +++ b/vtysh/Makefile.am @@ -75,6 +75,7 @@ vtysh_scan += $(top_srcdir)/ospfd/ospf_opaque.c vtysh_scan += $(top_srcdir)/ospfd/ospf_ri.c vtysh_scan += $(top_srcdir)/ospfd/ospf_routemap.c vtysh_scan += $(top_srcdir)/ospfd/ospf_te.c +vtysh_scan += $(top_srcdir)/ospfd/ospf_sr.c vtysh_scan += $(top_srcdir)/ospfd/ospf_vty.c endif diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 097f39fcf0..94c4ba4330 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -504,11 +504,11 @@ static char *trim(char *s) return s; end = s + size - 1; - while (end >= s && isspace(*end)) + while (end >= s && isspace((int)*end)) end--; *(end + 1) = '\0'; - while (*s && isspace(*s)) + while (*s && isspace((int)*s)) s++; return s; diff --git a/zebra/kernel_socket.c b/zebra/kernel_socket.c index ba028ed09c..2a3b95058e 100644 --- a/zebra/kernel_socket.c +++ b/zebra/kernel_socket.c @@ -40,6 +40,7 @@ #include "privs.h" #include "vrf.h" +#include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/zserv.h" #include "zebra/debug.h" diff --git a/zebra/zebra_mpls.h b/zebra/zebra_mpls.h index 22c771c348..9d8ca34f82 100644 --- a/zebra/zebra_mpls.h +++ b/zebra/zebra_mpls.h @@ -316,6 +316,12 @@ void mpls_ldp_lsp_uninstall_all(struct hash_backet *backet, void *ctxt); */ void mpls_ldp_ftn_uninstall_all(struct zebra_vrf *zvrf, int afi); +/* + * Uninstall all Segment Routing NHLFEs for a particular LSP forwarding entry. + * If no other NHLFEs exist, the entry would be deleted. + */ +void mpls_sr_lsp_uninstall_all(struct hash_backet *backet, void *ctxt); + #if defined(HAVE_CUMULUS) /* * Check that the label values used in LSP creation are consistent. The @@ -448,6 +454,8 @@ static inline int re_type_from_lsp_type(enum lsp_types_t lsp_type) return ZEBRA_ROUTE_LDP; case ZEBRA_LSP_BGP: return ZEBRA_ROUTE_BGP; + case ZEBRA_LSP_SR: + return ZEBRA_ROUTE_OSPF; case ZEBRA_LSP_NONE: default: return ZEBRA_ROUTE_KERNEL; @@ -464,6 +472,8 @@ static inline const char *nhlfe_type2str(enum lsp_types_t lsp_type) return "LDP"; case ZEBRA_LSP_BGP: return "BGP"; + case ZEBRA_LSP_SR: + return "SR"; default: return "Unknown"; } diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c index c200e2dbb3..b7b4a159da 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c @@ -2098,6 +2098,9 @@ void rib_unlink(struct route_node *rn, struct route_entry *re) dest->routes = re->next; } + if (dest->selected_fib == re) + dest->selected_fib = NULL; + /* free RE and nexthops */ zebra_deregister_rnh_static_nexthops(re->vrf_id, re->nexthop, rn); nexthops_free(re->nexthop); diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index f0be862221..62869b7eec 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -2013,17 +2013,21 @@ DEFPY(ipv6_route_address_interface, struct zebra_vrf *zvrf; struct zebra_vrf *nh_zvrf; - nh_zvrf = zebra_vrf_lookup_by_name(nexthop_vrf); - if (!nh_zvrf) { - vty_out(vty, "%% nexthop vrf %s is not defined\n", - nexthop_vrf); + zvrf = zebra_vrf_lookup_by_name(vrf); + if (!zvrf) { + vty_out(vty, "%% vrf %s is not defined\n", + vrf); return CMD_WARNING_CONFIG_FAILED; } - zvrf = zebra_vrf_lookup_by_name(vrf); + if (nexthop_vrf) + nh_zvrf = zebra_vrf_lookup_by_name(nexthop_vrf); + else + nh_zvrf = zvrf; + if (!nh_zvrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", - vrf); + nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } @@ -2104,17 +2108,21 @@ DEFPY(ipv6_route, struct zebra_vrf *zvrf; struct zebra_vrf *nh_zvrf; - nh_zvrf = zebra_vrf_lookup_by_name(nexthop_vrf); - if (!nh_zvrf) { - vty_out(vty, "%% nexthop vrf %s is not defined\n", - nexthop_vrf); + zvrf = zebra_vrf_lookup_by_name(vrf); + if (!zvrf) { + vty_out(vty, "%% vrf %s is not defined\n", + vrf); return CMD_WARNING_CONFIG_FAILED; } - zvrf = zebra_vrf_lookup_by_name(vrf); + if (nexthop_vrf) + nh_zvrf = zebra_vrf_lookup_by_name(nexthop_vrf); + else + nh_zvrf = zvrf; + if (!nh_zvrf) { vty_out(vty, "%% nexthop vrf %s is not defined\n", - vrf); + nexthop_vrf); return CMD_WARNING_CONFIG_FAILED; } |
