1 #if (!defined(lint) && !defined(SABER))
2 static char rcsid_module_c[] = "$Header$";
5 /* This is the file cluster.c for the SMS Client, which allows a nieve
6 * user to quickly and easily maintain most parts of the SMS database.
10 * By: Chris D. Peterson
16 * Copyright 1988 by the Massachusetts Institute of Technology.
18 * For further information on copyright and distribution
19 * see the file mit-copyright.h
22 /* BTW: for anyone who cares MCD is short for Machine, Cluster, Data. */
29 #include "mit-copyright.h"
40 #define M_DEFAULT_TYPE DEFAULT_NONE
42 #define C_DEFAULT_DESCRIPT DEFAULT_NONE
43 #define C_DEFAULT_LOCATION DEFAULT_NONE
45 #define CD_DEFAULT_LABEL DEFAULT_NONE
46 #define CD_DEFAULT_DATA DEFAULT_NONE
48 /* -------------------- Set Defaults -------------------- */
50 /* Function Name: SetMachineDefaults
51 * Description: sets machine defaults.
52 * Arguments: info - an array to put the defaults into.
53 * name - Canonacalized name of the machine.
54 * Returns: info - the array.
58 SetMachineDefaults(info, name)
61 info[M_NAME] = Strsave(name);
62 info[M_TYPE] = Strsave(M_DEFAULT_TYPE);
63 info[M_MODBY] = info[M_MODTIME] = info[M_MODWITH] = info[M_END] = NULL;
67 /* Function Name: SetClusterDefaults
68 * Description: sets Cluster defaults.
69 * Arguments: info - an array to put the defaults into.
70 * name - name of the Cluster.
71 * Returns: info - the array.
75 SetClusterDefaults(info, name)
78 info[C_NAME] = Strsave(name);
79 info[C_DESCRIPT] = Strsave(C_DEFAULT_DESCRIPT);
80 info[C_LOCATION] = Strsave(C_DEFAULT_LOCATION);
81 info[C_MODBY] = info[C_MODTIME] = info[C_MODWITH] = info[C_END] = NULL;
85 /* -------------------- General Functions -------------------- */
87 /* Function Name: PrintMachInfo
88 * Description: This function Prints out the Machine info in
90 * Arguments: info - array of information about a machine.
91 * Returns: The name of the Machine
101 sprintf(buf, "Machine: %-30s Type: %s", info[M_NAME], info[M_TYPE]);
103 sprintf(buf, MOD_FORMAT, info[M_MODBY], info[M_MODTIME], info[M_MODWITH]);
105 return(info[M_NAME]);
108 /* Function Name: PrintClusterInfo
109 * Description: This function Prints out the cluster info
110 * in a coherent form.
111 * Arguments: info - array of information about a cluster.
112 * Returns: The name of the cluster.
116 PrintClusterInfo(info)
122 sprintf(buf, "Cluster: %s", info[C_NAME]);
124 sprintf(buf, "Description: %s", info[C_DESCRIPT]);
126 sprintf(buf, "Location: %s", info[C_LOCATION]);
128 sprintf(buf, MOD_FORMAT, info[C_MODBY], info[C_MODTIME], info[C_MODWITH]);
130 return(info[C_NAME]);
133 /* Function Name: PrintClusterData
134 * Description: Prints the Data on a cluster
135 * Arguments: info a pointer to the data array.
136 * Returns: The name of the cluster.
140 PrintClusterData(info)
146 sprintf(buf, "Cluster: %-20s Label: %-15s Data: %s",
147 info[CD_NAME], info[CD_LABEL], info[CD_DATA]);
149 return(info[CD_NAME]);
152 /* Function Name: PrintMCMap
153 * Description: Prints the data about a machine to cluster mapping.
154 * Arguments: info a pointer to the data array.
163 sprintf(buf, "Cluster: %-30s Machine: %-20s",
164 info[MAP_CLUSTER], info[MAP_MACHINE]);
166 return(""); /* Used by QueryLoop(). */
169 /* Function Name: GetMCInfo.
170 * Description: This function stores info about a machine.
171 * type - type of data we are trying to retrieve.
172 * name1 - the name of argv[0] for the call.
173 * name2 - the name of argv[1] for the call.
174 * Returns: the top element of a queue containing the data or NULL.
178 GetMCInfo(type, name1, name2)
180 char * name1, *name2;
184 struct qelem * elem = NULL;
189 if ( (stat = sms_query("get_machine", 1, &name1,
190 StoreInfo, &elem)) != 0) {
191 com_err(program_name, stat, " in get_machine.");
196 if ( (stat = sms_query("get_cluster", 1, &name1,
197 StoreInfo, &elem)) != 0) {
198 com_err(program_name, stat, " in get_cluster.");
203 args[MAP_MACHINE] = name1;
204 args[MAP_CLUSTER] = name2;
205 if ( (stat = sms_query("get_machine_to_cluster_map", 2, args,
206 StoreInfo, &elem)) != 0) {
207 com_err(program_name, stat, " in get_machine_to_cluster_map.");
212 args[CD_NAME] = name1;
213 args[CD_LABEL] = name2;
214 if ( (stat = sms_query("get_cluster_data", 2, args,
215 StoreInfo, &elem)) != 0) {
216 com_err(program_name, stat, " in get_cluster_data.");
220 return(QueueTop(elem));
223 /* Function Name: AskMCDInfo.
224 * Description: This function askes the user for information about a
225 * machine and saves it into a structure.
226 * Arguments: info - a pointer the information to ask about
227 * type - type of information - MACHINE
230 * name - T/F : change the name of this type.
235 AskMCDInfo(info, type, name)
240 char temp_buf[BUFSIZ], *newname;
244 sprintf(temp_buf, "Setting the information for the Machine %s.",
248 sprintf(temp_buf, "Setting the information for the Cluster %s.",
252 sprintf(temp_buf, "Setting the Data for the Cluster %s.",
256 Put_message(temp_buf);
261 newname = Strsave(info[M_NAME]);
262 GetValueFromUser("The new name for this machine? ", &newname);
263 strcpy(temp_buf, CanonicalizeHostname(newname));
265 newname = Strsave(temp_buf);
268 newname = Strsave(info[C_NAME]);
269 GetValueFromUser("The new name for this cluster? ",
273 Put_message("Unknown type in AskMCDInfo, programmer botch");
280 GetValueFromUser("Machine's Type:", &info[M_TYPE]);
281 FreeAndClear(&info[M_MODTIME], TRUE);
282 FreeAndClear(&info[M_MODBY], TRUE);
283 FreeAndClear(&info[M_MODWITH], TRUE);
286 GetValueFromUser("Cluster's Description:", &info[C_DESCRIPT]);
287 GetValueFromUser("Cluster's Location:", &info[C_LOCATION]);
288 FreeAndClear(&info[C_MODTIME], TRUE);
289 FreeAndClear(&info[C_MODBY], TRUE);
290 FreeAndClear(&info[C_MODWITH], TRUE);
293 GetValueFromUser("Label defining this data?", &info[CD_LABEL]);
294 GetValueFromUser("The data itself ? ", &info[CD_DATA]);
299 * Slide the newname into the #2 slot, this screws up all future references
303 SlipInNewName(info, newname);
308 /* ----------- Machine Menu ----------- */
310 /* Function Name: ShowMachineInfo
311 * Description: This function shows the information about a machine.
312 * Arguments: argc, argv - the name of the machine in argv[1].
313 * Returns: DM_NORMAL.
318 ShowMachineInfo(argc, argv)
324 top = GetMCInfo(MACHINE, CanonicalizeHostname(argv[1]), (char *) NULL);
325 Loop(top, ( (void *) PrintMachInfo) );
330 /* Function Name: AddMachine
331 * Description: This function adds a new machine to the database.
332 * Arguments: argc, argv - the name of the machine in argv[1].
333 * Returns: DM_NORMAL.
338 AddMachine(argc, argv)
342 char **args, *info[MAX_ARGS_SIZE], *name;
345 if (!ValidName(argv[1])) /* Checks for wildcards. */
348 * Check to see if this machine already exists.
350 name = CanonicalizeHostname(argv[1]);
352 if ( (stat = sms_query("get_machine", 1, &name, NullFunc, NULL)) == 0) {
353 Put_message("This machine already exists.");
356 else if (stat != SMS_NO_MATCH) {
357 com_err(program_name, stat, " in AddMachine.");
361 args = AskMCDInfo(SetMachineDefaults(info, name), MACHINE, FALSE);
364 * Actually create the new Machine.
367 if ( (stat = sms_query("add_machine", CountArgs(args),
368 args, Scream, NULL)) != 0)
369 com_err(program_name, stat, " in AddMachine.");
375 /* Function Name: RealUpdateMachine
376 * Description: Performs the actual update of the machine data.
377 * Arguments: info - the information on the machine to update.
378 * junk - an UNUSED Boolean.
384 RealUpdateMachine(info, junk)
389 char ** args = AskMCDInfo(info, MACHINE, TRUE);
390 if ( (stat = sms_query("update_machine", CountArgs(args),
391 args, Scream, NULL)) != 0)
392 com_err(program_name, stat, " in UpdateMachine.");
394 Put_message("Machine sucessfully updated.");
397 /* Function Name: UpdateMachine
398 * Description: This function adds a new machine to the database.
399 * Arguments: argc, argv - the name of the machine in argv[1].
400 * Returns: DM_NORMAL.
405 UpdateMachine(argc, argv)
409 struct qelem *top = GetMCInfo( MACHINE, CanonicalizeHostname(argv[1]),
411 QueryLoop(top, NullPrint, RealUpdateMachine, "Update the machine");
417 /* Function Name: CheckAndRemoveFromCluster
418 * Description: This func tests to see if a machine is in a cluster.
419 * and if so then removes it
420 * Arguments: name - name of the machine (already Canonocalized).
421 * ask_user- query the user before removing if from clusters?
422 * Returns: SMS_ERROR if machine left in a cluster, or sms_error.
426 CheckAndRemoveFromCluster(name, ask_user)
430 register int stat, ret_value;
432 char *args[10], temp_buf[BUFSIZ], *ptr;
433 struct qelem *top, *elem = NULL;
435 ret_value = SUB_NORMAL; /* initialize ret_value. */
438 stat = sms_query("get_machine_to_cluster_map", 2, args,
440 if (stat && stat != SMS_NO_MATCH) {
441 com_err(program_name, stat, " in get_machine_to_cluster_map.");
444 if (stat == SMS_SUCCESS) {
445 elem = top = QueueTop(elem);
447 sprintf(temp_buf, "%s is assigned to the following clusters.",
449 Put_message(temp_buf);
450 Loop(top, (void *) PrintMCMap);
451 ptr = "Remove this machine from ** ALL ** these clusters?";
452 if (YesNoQuestion(ptr, FALSE) == TRUE) /* may return -1. */
455 Put_message("Aborting...");
464 while (elem != NULL) {
465 char **info = (char **) elem->q_data;
466 if ( (stat = sms_query( "delete_machine_from_cluster",
467 2, info, Scream, NULL)) != 0) {
468 ret_value = SUB_ERROR;
469 com_err(program_name, stat,
470 " in delete_machine_from_cluster.");
472 "Machine %s ** NOT ** removed from cluster %s.",
473 info[MAP_MACHINE], info[MAP_CLUSTER]);
474 Put_message(temp_buf);
483 /* Function Name: RealDeleteMachine
484 * Description: Actually Deletes the Machine.
485 * Arguments: info - nescessary information stored as an array of char *'s
486 * one_machine - a boolean, true if there is only one item in
492 RealDeleteMachine(info, one_machine)
497 char temp_buf[BUFSIZ];
499 sprintf(temp_buf, "Are you sure you want to delete the machine %s (y/n)? ",
501 if(!one_machine || Confirm(temp_buf)) {
502 if (CheckAndRemoveFromCluster(info[M_NAME], TRUE) != SUB_ERROR) {
503 if ( (stat = sms_query("delete_machine", 1,
504 &info[M_NAME], Scream, NULL)) != 0) {
505 com_err(program_name, stat, " in DeleteMachine.");
506 sprintf(temp_buf, "%s ** NOT ** deleted.",
508 Put_message(temp_buf);
511 sprintf(temp_buf, "%s successfully Deleted.", info[M_NAME]);
512 Put_message(temp_buf);
518 /* Function Name: DeleteMachine
519 * Description: This function removes a machine from the data base.
520 * Arguments: argc, argv - the machines name int argv[1].
521 * Returns: DM_NORMAL.
524 /* Perhaps we should remove the cluster if it has no machine now. */
528 DeleteMachine(argc, argv)
534 top = GetMCInfo(MACHINE, CanonicalizeHostname(argv[1]), (char *) NULL);
535 QueryLoop(top, PrintMachInfo, RealDeleteMachine, "Delete the machine");
540 /* Function Name: AddMachineToCluster
541 * Description: This function adds a machine to a cluster
542 * Arguments: argc, argv - The machine name is argv[1].
543 * The cluster name in argv[2].
544 * Returns: DM_NORMAL.
549 AddMachineToCluster(argc, argv)
554 char *machine, *cluster, temp_buf[BUFSIZ], *args[10];
555 Bool add_it, one_machine, one_cluster;
556 struct qelem * melem, *mtop, *celem, *ctop;
558 machine = CanonicalizeHostname(argv[1]);
561 celem = ctop = GetMCInfo(CLUSTER, cluster, (char *) NULL);
562 melem = mtop = GetMCInfo(MACHINE, machine, (char *) NULL);
564 one_machine = (QueueCount(mtop) == 1);
565 one_cluster = (QueueCount(ctop) == 1);
567 /* No good way to use QueryLoop() here, sigh */
569 while (melem != NULL) {
570 char ** minfo = (char **) melem->q_data;
571 while (celem != NULL) {
572 char ** cinfo = (char **) celem->q_data;
573 if (one_machine && one_cluster)
576 sprintf(temp_buf,"Add machine %s to cluster %s (y/n/q) ?",
577 minfo[M_NAME], cinfo[C_NAME]);
578 switch (YesNoQuitQuestion(temp_buf, FALSE)) {
586 Put_message("Aborting...");
593 args[0] = minfo[M_NAME];
594 args[1] = cinfo[C_NAME];
595 stat = sms_query("add_machine_to_cluster", 2, args,
601 sprintf(temp_buf, "%s is already in cluster %s",
602 minfo[M_NAME], cinfo[C_NAME]);
603 Put_message(temp_buf);
606 com_err(program_name, stat, " in AddMachineToCluster.");
610 celem = celem->q_forw;
612 celem = ctop; /* reset cluster element. */
613 melem = melem->q_forw;
620 /* Function Name: RealRemoveMachineFromCluster
621 * Description: This function actually removes the machine from its
623 * Arguments: info - all information nescessary to perform the removal.
624 * one_map - True if there is only one case, and we should
630 RealRemoveMachineFromCluster(info, one_map)
634 char temp_buf[BUFSIZ];
637 sprintf(temp_buf, "Remove %s from the cluster %s",
638 info[MAP_MACHINE], info[MAP_MACHINE]);
639 if (!one_map || Confirm(temp_buf)) {
640 if ( (stat = sms_query("delete_machine_from_cluster", 2,
641 info, Scream, NULL)) != 0 )
642 com_err(program_name, stat, " in delete_machine_from_cluster");
644 sprintf(temp_buf, "%s has been removed from the cluster %s.",
645 info[MAP_MACHINE], info[MAP_CLUSTER]);
646 Put_message(temp_buf);
650 Put_message("Machine not removed.");
653 /* Function Name: RemoveMachineFromCluster
654 * Description: Removes this machine form a specific cluster.
655 * Arguments: argc, argv - Name of machine in argv[1].
656 * Name of cluster in argv[2].
662 RemoveMachineFromCluster(argc, argv)
666 struct qelem *elem = NULL;
667 char buf[BUFSIZ], * args[10];
670 args[MAP_MACHINE] = CanonicalizeHostname(argv[1]);
671 args[MAP_CLUSTER] = argv[2];
672 args[MAP_END] = NULL;
674 stat = sms_query("get_machine_to_cluster_map", CountArgs(args), args,
676 if (stat == SMS_NO_MATCH) {
677 sprintf(buf, "The machine %s is not is the cluster %s.",
678 args[MAP_MACHINE], args[MAP_CLUSTER]);
682 if (stat != SMS_SUCCESS)
683 com_err(program_name, stat, " in delete_machine_from_cluster");
685 elem = QueueTop(elem);
686 QueryLoop(elem, PrintMCMap, RealRemoveMachineFromCluster,
687 "Remove this machine from this cluster");
693 /* ---------- Cluster Menu -------- */
695 /* Function Name: ShowClusterInfo
696 * Description: Gets information about a cluser given its name.
697 * Arguments: argc, argc - the name of the cluster in in argv[1].
698 * Returns: DM_NORMAL.
703 ShowClusterInfo(argc, argv)
709 top = GetMCInfo(CLUSTER, CanonicalizeHostname(argv[1]), (char *) NULL);
710 Loop(top, (void *) PrintClusterInfo);
715 /* Function Name: AddCluster
716 * Description: Creates a new cluster.
717 * Arguments: argc, argv - the name of the new cluster is argv[1].
718 * Returns: DM_NORMAL.
723 AddCluster(argc, argv)
727 char **args, *info[MAX_ARGS_SIZE], *name = argv[1];
730 * Check to see if this cluster already exists.
732 if (!ValidName(name))
735 if ( (stat = sms_query("get_cluster", 1, &name,
736 NullFunc, NULL)) == SMS_SUCCESS) {
737 Put_message("This cluster already exists.");
740 else if (stat != SMS_NO_MATCH) {
741 com_err(program_name, stat, " in AddCluster.");
744 args = AskMCDInfo(SetClusterDefaults(info, name), CLUSTER, FALSE);
746 * Actually create the new Cluster.
748 if ( (stat = sms_query("add_cluster", CountArgs(args),
749 args, Scream, NULL)) != 0)
750 com_err(program_name, stat, " in AddCluster.");
756 /* Function Name: RealUpdateCluster
757 * Description: This function actually performs the cluster update.
758 * Arguments: info - all information nesc. for updating the cluster.
759 * junk - an UNUSED boolean.
765 RealUpdateCluster(info, junk)
770 char ** args = AskMCDInfo(info, CLUSTER, TRUE);
771 if ( (stat = sms_query("update_cluster", CountArgs(args),
772 args, Scream, NULL)) != 0)
773 com_err(program_name, stat, " in UpdateCluster.");
775 Put_message("Cluster successfully updated.");
778 /* Function Name: UpdateCluster
779 * Description: This Function Updates a cluster
780 * Arguments: name of the cluster in argv[1].
781 * Returns: DM_NORMAL.
786 UpdateCluster(argc, argv)
791 top = GetMCInfo( CLUSTER, argv[1], (char *) NULL );
792 QueryLoop(top, NullPrint, RealUpdateCluster, "Update the cluster");
798 /* Function Name: CheckAndRemoveMachine
799 * Description: This function checks and removes all machines from a
801 * Arguments: name - name of the cluster.
802 * ask_first - if TRUE, then we will query the user, before
804 * Returns: SUB_ERROR if all machines not removed.
808 CheckAndRemoveMachines(name, ask_first)
812 register int stat, ret_value;
814 char *args[10], temp_buf[BUFSIZ], *ptr;
815 struct qelem *top, *elem = NULL;
817 ret_value = SUB_NORMAL;
818 args[MAP_MACHINE] = "*";
819 args[MAP_CLUSTER] = name;
820 stat = sms_query("get_machine_to_cluster_map", 2, args,
822 if (stat && stat != SMS_NO_MATCH) {
823 com_err(program_name, stat, " in get_machine_to_cluster_map.");
827 elem = top = QueueTop(elem);
830 "The cluster %s has the following machines in it:",
832 Put_message(temp_buf);
833 while (elem != NULL) {
834 char **info = (char **) elem->q_data;
835 Print(1, &info[MAP_MACHINE], (char *) NULL);
838 ptr = "Remove ** ALL ** these machines from this cluster?";
840 if (YesNoQuestion(ptr, FALSE) == TRUE) /* may return -1. */
843 Put_message("Aborting...");
854 char **info = (char **) elem->q_data;
855 if ( (stat = sms_query( "delete_machine_from_cluster",
856 2, info, Scream, NULL)) != 0) {
857 ret_value = SUB_ERROR;
858 com_err(program_name, stat,
859 " in delete_machine_from_cluster.");
861 "Machine %s ** NOT ** removed from cluster %s.",
862 info[MAP_MACHINE], info[MAP_CLUSTER]);
863 Put_message(temp_buf);
872 /* Function Name: RealDeleteCluster
873 * Description: Actually performs the cluster deletion.
874 * Arguments: info - all information about this cluster.
875 * one_cluster - If true then there was only one cluster in
876 * the queue, and we should confirm.
881 RealDeleteCluster(info, one_cluster)
886 char temp_buf[BUFSIZ];
889 "Are you sure the you want to delete the cluster %s (y/n) ?",
891 if (!one_cluster || Confirm(temp_buf)) {
892 if (CheckAndRemoveMachines(info[C_NAME], TRUE) != SUB_ERROR) {
893 if ( (stat = sms_query("delete_cluster", 1,
894 &info[C_NAME], Scream, NULL)) != 0) {
895 com_err(program_name, stat, " in delete_cluster.");
896 sprintf(temp_buf, "Cluster %s ** NOT ** deleted.",
898 Put_message(temp_buf);
901 sprintf(temp_buf, "cluster %s sucesfully deleted.",
903 Put_message(temp_buf);
909 /* Function Name: DeleteCluster
910 * Description: This function removes a cluster from the database.
911 * Arguments: argc, argv - the name of the cluster is stored in argv[1].
912 * Returns: DM_NORMAL.
917 DeleteCluster(argc, argv)
923 top = GetMCInfo( CLUSTER, argv[1], (char *) NULL );
924 QueryLoop(top, PrintClusterInfo, RealDeleteCluster, "Delete the cluster");
930 /* ----------- Cluster Data Menu -------------- */
932 /* Function Name: ShowClusterData
933 * Description: This function shows the services for one cluster.
934 * Arguments: argc, argv - The name of the cluster is argv[1].
935 * The label of the data in argv[2].
936 * Returns: DM_NORMAL.
941 ShowClusterData(argc, argv)
945 struct qelem *elem, *top;
948 top = elem = GetMCInfo(DATA, argv[1], argv[2]);
949 while (elem != NULL) {
950 info = (char **) elem->q_data;
951 PrintClusterData(info);
958 /* Function Name: AddClusterData
959 * Description: This function adds some data to the cluster.
960 * Arguments: argv, argc: argv[1] - the name of the cluster.
961 * argv[2] - the label of the data.
962 * argv[3] - the data.
963 * Returns: DM_NORMAL.
968 AddClusterData(argc, argv)
974 if( (stat = sms_query("add_cluster_data", 3, argv + 1,
975 Scream, (char *) NULL)) != 0)
976 com_err(program_name, stat, " in AddClusterData.");
980 /* Function Name: RealRemoveClusterData
981 * Description: actually removes the cluster data.
982 * Arguments: info - all info necessary to remove the cluster, in an array
984 * one_item - if true then the queue has only one elem and we
990 RealRemoveClusterData(info, one_item)
998 temp_ptr = "Are you sure that you want to remove this cluster (y/n) ?";
999 if (!one_item) PrintClusterData(info);
1000 if (!one_item || Confirm(temp_ptr)) {
1001 if( (stat = sms_query("delete_cluster_data", 3, info,
1002 Scream, (char *) NULL)) != 0) {
1003 com_err(program_name, stat, " in DeleteClusterData.");
1004 Put_message("Data not removed.");
1007 Put_message("Removal sucessful.");
1011 /* Function Name: RemoveClusterData
1012 * Description: This function removes data on a given cluster.
1013 * Arguments: argv, argc: argv[1] - the name of the cluster.
1014 * argv[2] - the label of the data.
1015 * argv[3] - the data.
1016 * Returns: DM_NORMAL.
1021 RemoveClusterData(argc, argv)
1027 top = GetMCInfo(DATA, argv[1], argv[2]);
1028 QueryLoop(top, PrintClusterData, RealRemoveClusterData,
1029 "Remove this cluster data");
1035 /* Function Name: MachineToClusterMap
1036 * Description: This Retrieves the mapping between machine and cluster
1037 * Arguments: argc, argv - argv[1] -> machine name or wildcard.
1038 * argv[2] -> cluster name or wildcard.
1044 MachineToClusterMap(argc,argv)
1048 struct qelem *elem, *top;
1050 top = elem = GetMCInfo(MAP, CanonicalizeHostname(argv[1]), argv[2]);
1052 Put_message(""); /* blank line on screen */
1053 while (elem != NULL) {
1054 char ** info = (char **) elem->q_data;
1056 elem = elem->q_forw;
1067 * c-continued-statement-offset: 4
1068 * c-brace-offset: -4
1069 * c-argdecl-indent: 4
1070 * c-label-offset: -4