Monday, February 27

2017 香港特首选举 3:四个半候选人

介绍了前世和昨天,顺便提了几个名词和花絮,终于该看看候选人了。

重新说一下规矩:在现今1194人的选举委员会中,需要有150选委提名才可以成为候选人;候选人报名截止日期是3月1日。3月26日选举委员会将进行投票,最多投两轮,获得601票者胜出。

背景:在选举委员会的1194人中,泛民拥有326席。建制派依然拥有话事权。


现任特首梁振英(Leung CY),5年前以689票当选,在往届特首选举中是得票最少的,所以民众常以“689”称呼他,提醒他没有得到足够认同。当政几年来没有取得特别的成就,却出现社会动荡、民众分裂的局面。例如2014年雨伞运动引发港独团体浮出水面、2015政改方案被立法会驳回、2016新年旺角骚乱。梁还在2016年4月爆出滥权事件,以身份向机场职员施压,绕过机场安全措施,为女儿取得方便。

2016年12月,选举委员会的选举期间,泛民以“ABC”(Anyone But CY)为号召,呼吁各选民避免梁振英连任特首,尽量投票给与梁相左的选委。在各方压力下,在选委会选举前两天,梁振英宣布因家庭原因,不寻求连任。

所以,梁振英并不在四个半候选人之列。


“长毛”梁国雄:



2017 香港特首选举 2:名词和七警

名词解释:


入闸:被提名,进入候选人名单中。现行选举方式需要八分之一选委提名,方可“入闸”。“831决定”里要求候选人要得到提名委员会半数以上支持。

袋住先:港府让大家先接受“831决定”,先按照其方法选举2017年特首,暗示以后再作修改。
泛民认为若接受了这个选举方法,就永远没有“真普选”的机会。
立法会投票决议前,人大副主任李飞到深圳说831决定“對以後各屆特首普選都有效”,啪啪地打港府的脸。


白票守尾门:如果选举时,如果多数人投弃权票(白票),则本次选举无效。如果所有候选人都没有民望,无法以简单多数取胜,选举重新开始。现在选举委员会运作是用这种方式:如果第一轮没有票数过半的,对第一、第二名进行第二轮投票;如果第二轮还没有票数过半(有人投废票、弃权票),则本次选举无效。20年来这种情况还没有出现过。

对普选,李飞明确反对这种方式。

西环:中央人民政府驻特别行政区联络办公室(简称中联办)设立在香港西环区内西港中心。现在报刊常用“西环”指代中联办,比如叶刘淑仪在立法会选举结束后被发现其座车在中联办内,因此报章指其“到西环谢票”。叶刘说自己不在车内,当时是司机送书给中联办;与此同时,人称“西环契仔”的何君尧在选后很大方地说“要多谢中联办”,毫不掩饰中联办对香港选举的插手,所以4天之后,叶刘主动致电传媒,承认当时是自己访问中联办的朋友,并对误导记者而道歉。


建制派/泛民/本土:一般认为,“建制派”是“爱国爱港”的既得利益者;泛民是反应民意,支持在香港全面实行民主的派别;本土派是自雨伞运动之后浮出水面的年青派别,追求香港自主决定未来,认为泛民自缚手脚,为了取得建制派、中央的合作而委曲求全。

暗角七警事件:

在2014年雨伞运动期间,警民对立,9月28日警察使用催泪弹驱赶民众后更是情绪高涨。10月15日凌晨,在一次冲突中,曾某在高处向下面的警察淋液体,被警察扣住后抬到龙和道暗角殴打,过程被路人用手机拍摄。两年后的判决书如此描述:
2.案件的全部細節在2017年2月14日公布的裁判理由中已詳細闡述。概括如下:2014年10月15日凌晨2點45分許,警方在對「佔中運動」的示威者進行清場,當警察抵達龍和道地下通道時,曾健超被看到在龍和道上方的花槽上,向警員淋潑液體。

3.曾健超被几个军装警员从花槽上拽下,至人行道并被制服。警员使用塑料带将曾健超双手绑在背后,然后将他移交给第一至第六被告,各被告护送曾健超沿龙和道方向离开。一路上,曾健超脸部朝下被抬着。

4.示威者们会被带至龙和道上的旅游巴和车辆上,送往中区警署。第一至第六被告未有把曾健超带到巴士上,而是将他带至龙汇道政府大楼泵房东变电站的北侧进行袭击。

5.到达变电站后,第七被告加入前六位被告,协助将曾健超带至变电站北侧。曾健超被扔在地上,立即遭到各被告的殴打。

2016年6月,曾某被判一项袭警及两项拒捕罪成,判囚五星期。
2017年2月,七警察被判“袭击致造成身体受伤”罪名成立,各判囚两年。

支持七警判决者认为七警是该判决维护了香港司法的声誉,对警队里滥权的害群之马做了清理;
反对判决者认为这是政治迫害,是对民众暴力的纵容。

资深泛民黄毓民议员在议会中讨论七警事件: 面對極權,我們旗幟鮮明、義無反顧、堅定不移站在抗爭者這一邊!

2017 香港特首选举: 前世和昨天


前世:

1984年,中英通过联合声明,香港于1997年回归中国,一国两制,“舞照跳,马照跑”,五十年不变。回归后以《基本法》为宪制文件。
《基本法》规定:
第四十五條 香港特別行政區行政長官在當地通過選舉或協商產生,由中央人民政府任命。
行政長官的產生辦法根據香港特別行政區的實際情況和循序漸進的原則而規定,最終達至由一個有廣泛代表性的提名委員會按民主程序提名後普選產生的目標。
行政長官產生的具體辦法由附件一《香港特別行政區行政長官的產生辦法》規定。

《附件一》规定了一个800人(后来扩充到1200人)的“具有廣泛代表性的選舉委員會”,候选人需要八分之一选委的提名才可以进入选举(入闸),然后由选委一人一票决定胜者:如果第一次投票没有人超过一半,就对第一名、第二名进行第二次投票,超过一半者胜。
附件一还确认这只是临时方法,预计在几年后修改:
二○○七年以後各任行政長官的產生辦法如需修改,須經立法會全體議員三分之二多數通過,行政長官同意,並報全國人民代表大會常務委員會批准。

在中央的特别操持下,这个选举委员会不一定代表广大香港民意。以有代表性的“渔农界”为例,在现在1200人的选举委员会中,渔农界有60个席位,占5%;其实渔农界人口只有1.2万人,不到劳动人口的1%;GDP比例甚至小于0.1%。而且,这个界别由香港漁民團體聯會及香港農業聯合會协商后推出 60人来参加选举竞选60个席位,自然是人人中奖;这60人里是否真的从事渔农业,很存疑:至少有15%在5年前上一次的选举中填写的职业跟渔农业无关。
(参看 媒体报道

所以,在2003年七一游行、2004年元旦游行里,香港居民打出了2007年普选的旗号,美国、英国政府也表态支持香港政制改革。在这个背景下,2004年4月、2007年12月,中国人大常委会分别发布了两个决定,规定2007年不采用普选、2012年也不采用普选,但是确认“2017年香港特別行政區第五任行政長官的選舉可以實行由普選產生的辦法”,并督促港府提出普选的具体办法。


昨天:

在中央确认2017可以普选的前提下,香港各界纷纷讨论如何提名、如何选举,等到的是2014年8月31日人大常委会的决定:
(一)須組成一個有廣泛代表性的提名委員會。提名委員會的人數、構成和委員產生辦法按照第四任行政長官選舉委員會的人數、構成和委員產生辦法而規定。
(二)提名委員會按民主程序提名產生二至三名行政長官候選人。每名候選人均須獲得提名委員會全體委員半數以上的支持。

换而言之,这样的“普选”还是由先前的选举委员会决定2、3名人选后由大众投票,而且这个人选必须获得“提名委员会”超过半数以上的支持,就可以筛掉任何中央不喜欢的选举人,无论民意多高。

在这个无法让香港居民接受的831决定面前,香港的回答是公民抗命,和平占中,要求真普选,最后演变成香港警察发射胡椒喷雾、催泪弹,示威人士只能用雨伞遮挡的“雨伞革命”。

2015年6月18日,港府强行推出根据“831决定”做出的选举办法,交由立法会审议;立法会中建制派(支持该办法)未能在表决前取得三分之二强的优势以通过这个办法,退出会场,程序失误,造成这个议案以8票支持、28票反对大比数否决。

由此,选举改革没有通过,2017年的特首选举方式default到旧方式,依然由选举委员会决定:150个选委提名,然后投票。









Tuesday, October 11

Secure delete (Windows)

在操作系统里,文件删除后,并且从垃圾桶里清空之后,文件所在的页面被标为“可用”(unused),待下次有文件写入需要这个空间的时候,这个页面才会被改写:所以说,这时候这“已删除文件”并没有真正被删除。

好处:如果你反悔,有可能能把这些文件内容找回来。


如果你真的要删除这些文件,把这些文件内容彻底改写,在Windows环境可以使用微软出品的 sdelete (Secure Delete). 它把所有unused的页面都写上0,那些“已删除文件”被彻底抹掉:
 sdelete -cz C:  
在Linux 环境下可以用dd生成极大的文件(内容都是0)充满整个硬盘,把所有“可用”(unused)页面都用起来,然后把这个文件删除掉(scrub能做完这个事情)。

好处:1,硬盘这些空间都写上电荷0,可以减轻重量,减少环境污染。
2,如果你这个环境是一个虚拟环境(virutal machine),比如VirtualBox 或者Vmware, 而且你的虚拟硬盘是动态分布的,那么在做完这个操作,把未用空间都填0之后,在host环境可以把这些未用空间收回来,缩小这个虚拟硬盘的实际大小(它的最大空间不变,随着你以后写入文件,它的实际尺寸以后还会长大)。

用VirtualBox的话,命令是:
VBoxManage modifyvdi xxxx.vdi --compact


附注:FBI有一些硬盘探测工具能够读取硬盘磁道的残留信息,找出该扇区在被改写之前的字节,以及在那之前的字节,以及...。即使在用sdelete的时候可以用 -p 来指定写入的次数(比如说,一百次?),也不能保证你的数据百分百没有被读出的可能。

Labels: ,

Saturday, October 1

士人政府的由来

昨天提到士人政府,有必要写详细一些。

人类在进步,部落在发展,在中原(现河南一带)名为夏/商的部落的文明发展得最快,或许是因为在黄河边上,耕种容易,衣廪实则知礼节?或许是周围有一些(恰好数量的)蛮、戎的入侵,足以刺激它的发展又不至于灭族?又或许是它的位置与周围其他部落的交通方便,交流(侵略也是交流的一种)取得最多知识?总之,它最先发展了文字、礼仪,建立了一整套管理机制:一个国家诞生了。

别的部落过来取经(不排除有被夏/商打残了的部落),也纷纷成立自己的国家,并尊称夏/商为共主,文字、文化得以传播天下。在部落时期这个部落就已经是周围部落的领头羊,现在有了文字、礼仪,它的地位更尊崇,文化传播业更广远:山东山西、湖北、陕西、河北都包括进来了。

那时候的一个“国”就是一个城,本部落的人就是领导阶层,每一房有自己的分工:专事祭祀的、专事生产的、专以武力保护国家的;有一房(大房)是专门当皇帝的。俘虏、部落败坏子弟是奴隶。城外就是“野”。

这就是当时“中华”的拓扑结构:每个城邦管理着自己的子民,防范别国和蛮、戎的入侵,同时向夏/商称臣进贡,以换取文化的传播和强大的中央之国的保护(以及“不侵略”的承诺),各国之间也有一些联姻的交流。这时候的中国就是个城邦制,或者是邦联制。

这里我把夏和商合并在一起,是因为夏朝的存在还是一个假设。我们在殷墟挖到了商朝的国都,通过对其中龙骨(用于占卜的龟壳)上甲骨文的解读,以后世的记录相对印,我们知道商的确切存在,对它的发展有个大概的了解;可是我们从来没有找到商朝之前的文化古迹:所找到的商朝之前的,都是一些部落的痕迹,不足以支撑一个国家的存在。

商朝末期,纣王无道。以血缘或小圈子传承的体制一定会碰到这个问题,接位的可能有好人,但是一定会有坏人;没有监督机制的制衡,接位坏人的只会更坏,更坏,越来越坏,不能再坏。纣王就是这样一个不能更坏的接位者。陕西一个发明了易经的周王国认为自己的文化并不比商差,有心做全中华的共主,就起兵伐纣。中华当时所有诸侯国都被卷入了这场战争,有的助周,有的助商。《封神演义》很详细地记载了这场战争的各路诸侯。这本书我很多年没看了,很想念啊。

大家都知道,周赢了这场战争。镇守在西北的部队总是能从与西北蛮戎的战争中获得更多的战争经验,以及更强壮的血缘和基因。后世的秦、唐都证明了这一点。战争结束,周王朝正式开始,在灵界封神的同时(详见《封神演义》),在人世间也封诸侯国,重新洗牌,把亲属和在战争中做成贡献的将领分封到各个诸侯国,强化周在各地的统治,“封建王朝”正式开始。

周王朝共800余年,包括西周和东周;东周又分为春秋时期和战国时期。每一次分段都是周王室对各诸侯国的统治力量削弱的标志。在最后的战国时期,各国之间互相争斗,已经完全不考虑周王室的存在了。可见封建王朝分封制的失败。

在这期间一定要提的是孔子把王室专有的知识传到民间。前面说到,王室里专门有一房是专管宗庙祭祀的,他们是真正的知识分子,把握着所有的知识,父传子受,别人不得与闻。皇帝有什么问题都要跟他们商量。而孔子把他学到的知识传授给民间,“有教无类”,这是值得大书特书的事情,从此所有人都可以努力学得知识、礼仪。更多的可参看《孔子的教育家地位》。士人阶层从此出现。

秦灭诸国,合并天下之后,没有再采用分封制,而是采用中央集权的郡县制,中央政府确切地行使管理全国的职责。这是前所未有的事情:商、周都是把诸侯国里的事情丢给各诸侯管理,而现在每个郡、每个县里的情况都由中央派人管理。这是前所未有的事情。这是前所未有的事情。重要的事情要说三遍。然后书同文,车同轨,秦王政是不愧于“始皇帝”这个称呼的。

汉朝建立后,同样采用郡县制,法规也大多遵循秦制。可是,有一个东西一定要改变:
以前的诸侯国很小,本部落的人(后来就是贵族)作为领导就足够管理,后来任用士人,也是把他们当做家臣来使用,这些士人的忠诚就是为贵族、为王上的忠诚。现在采用中央集权,难度大大提高,况且疆界也比以前大了好几倍。因此大批平民出身的士人被作为职业公务员招进了政府机构,他们不再成为“家臣”的一员,而是为“天下”而工作。在朝廷上,宰相带领百官成为“外朝”,以天下为己任,与外戚、宦官所组成的“内朝”相制衡。

对不起前面说了那么长的历史,就是要介绍清楚这一点:以前的朝廷是贵族(统治部落)的“家事”,宰相(家臣)为了让君上解忧而努力;现在的朝廷终于第一次由平民的精英士人掌管外朝的权利,是"of the people, by the people, for the people"。

王莽就在这么一个恰当的位置出现了:他既是外戚,又是苦日子出身(父兄皆死),饱读经书,礼贤下士,也是士人的一员。因此内朝和外朝都把他当做自己人。


Labels: ,

王莽

秦灭诸国,合并天下之后,法规严苛,而且战争后遗症发作:上层相信军队能征服一切,下层受苦于严重的军费支出。所以由陈胜吴广在鱼肚里放入“大楚兴 陈胜王”的字样,掀起反抗暴秦的浪潮,在推翻秦朝统治后循例以内战来决定政权得主(其实是后世抗日成功后国共两党“循例”以内战决定政权),最后以流氓刘邦胜了楚国贵族项羽为终,建立了汉朝。

汉朝为了革除以前各国(战国各国,包括秦)以传统贵族和军人为统治力量的弊病,大力提拔出色的平民(当然是文人/士人),当时的宰相大多为世间大儒来担当;这些书生得了势,竟然培养出了尊重三代古制而叫嚣变法让贤的王莽。

我们知道商、周以来除了改朝换代之外,皇位都是父传子受。尧舜禹是传说中的古代部落里的英明领袖,以将大禅之位让于后辈贤者而著称。现代还没有确切的考古证据来确认他们的存在,但孔子、周子经常在著作中提及他们,羡慕他们那个时代的清明。 西汉司马迁写《史记》的时候,就用“能让天下,贤哉二君!”(指尧与舜)来结束第一章《五帝本纪》。在士人的理想社会里,君子领天命而传天下,在年老体衰时就择贤而禅让,让下一个(年轻的、贤明的)君子得以延续自己的政治生命,管理天下以达大同世界。与血缘无关。

有学者认为这些禅让制度是书生臆想出来的传承方式,比如韩非子在《说疑》一文中说:“舜逼尧,禹逼舜,汤放桀,武王伐纣,此四人者,人臣弑其君者也。”而顾颉刚指出:“禅让之说乃是战国学者受了时势的刺激,在想象中构成的乌托邦。”说这是墨家为了宣传主义而造出来的。总之,这是士人希望的理想社会的传承。

王莽是外戚,姑姑是皇后,伯父是大司马,但父兄皆死,家庭俭朴。他没有官二代的毛病,饱读诗书,礼贤下士,当官之后也善待穷人,深得民众爱戴;在汉哀帝执政期间他被迫下野,期间儿子犯了事,他逼着儿子自杀以谢天下。这时是他名声最盛的时期。后代唐朝白居易说:
周公恐惧流言日,王莽谦恭未篡时。
向使当初身便死,一生真伪复谁知?
指的就是这个时候。

公元前1年,汉哀帝死而无后,皇权回到他奶奶王莽的姑姑手上。这个奶奶把王莽召了回来,立了另一个8岁的孙子作为汉平帝。王莽当政,权倾天下,在公元5年被众政府官员向太皇太后(他姑姑)提议加九锡并接受了。这个加九锡的仪式是周代传下来的由皇帝表彰大臣的最隆重的礼节:当时历史上只有齐恒公接受了两锡。王莽是有史以来第一个九锡大臣,可见他多么深受同事、上级的信任!

同一年,14岁的汉平帝病死(或说是因反抗王莽的操弄而被害死),王莽协同他姑姑立了一个两岁的孩童为皇太子(世称“孺子婴”),而自称为“摄皇帝”。

三年之后,他根据“陈胜王”籤語的传统,收集了一些民间祥瑞,比如深井里发现石牛,或者什么地方发现什么石文,向他姑姑上了一个表章(并随后迅速得到批准),说是根据这些祥瑞和预测,我们应该改变一下。从此王莽面见太皇太后及皇太子的时候,自称“假皇帝”,而别人对王莽的称呼,就是“皇帝”。

在他收集民间祥瑞的时候,一个游手好闲的大学生看到了机会:"Opportunity is knocking!" 他造了两个铜匮,一个里面刻着说这两个金匮是汉高祖刘邦传下来的,王莽应该是真皇帝,另一个说上天派了11个大臣来辅助王莽,这十一个大臣分别是八个当前的王莽的党羽,两个他生造的名字,一个是他自己的名字“哀章”。在王莽成了“假皇帝”之后,这两个“金匮”就被进贡了,把王莽吓一跳:我还没准备好呢,这么快?想了两天后,他还是决定接受了, It's a shame to let it wasted。因此在他上一个表章的4天之后,他来到汉高祖的祠堂拜受了金匮,然后带着王冠发了诏书,受汉高祖之命接受禅让,并变法以强国富民。西汉正式结束,新的朝代叫做“新朝”,从公元8年至23年。

柏杨与钱穆认为王莽的受禅和变法,是士人要缔造强大的理想社会的伟大尝试。在受禅前,王莽已经位居高位,权倾天下,可以为所欲为,如果只是为了私欲,他不需要做这个政治冒险的,甚至在新朝之后,他的生活依然俭朴。但是为了心目中的士人理想社会,他需要这样的受禅来向天下证明“让贤”的可行。跟皇室打了这么多年交道,他很清楚皇室的肮脏:我这么贤良的人,为什么要受汉哀帝的气而下野隐居7年?为什么要把8岁的汉平帝养大来反抗自己?现在还要捧着个不懂世事的孺子婴?他们凭什么因为血缘就能比我高一级?把王位早给我,我早就把这个天下治理好了!

王莽变法的政治主要有:
1,王田,把天下田地收归国有,而平均租给耕者。我可以说在1940年后共产党就是这么做的么?
2,废奴,把奴仆解放出来。王莽这个做法也是顺应当时解放生产力的要求,打击大奴隶主、大地主。
3,盐铁专卖,从大商贾手上把这些与民众生活紧密相关的贸易抢过来。人民政府为人民啊。
4,均输,类似于平抑市价,控制市场的方法。

这些措施没有得到当时民间的拥护,钱穆称之为“书生的政治”,15年后被以刘秀为首的反动地主阶级疯狂推翻了,东汉开始。历史是由胜利者书写的,所以东汉(以及后来的统治者)把王莽称为篡位的叛臣逆子,在上位前是善于伪装的伪君子,因为这些统治者都害怕后人以王莽为榜样,把不称职的皇帝擅自改换掉。虽然《吕氏春秋》和《六韬》就有“天下非一人之天下,天下之天下也”的说法,但是统治者绝不会赞成这样的说法,而努力培养大众的奴性,加强自己的统治。

禅让在中国并没有真正地实行过,直到现代。历史上除了皇权父传子受,都是血腥的改朝换代。不过在1877年,一些华人在南洋站稳了脚跟,成立了亚洲的第一个共和国:兰芳共和国,就是以民主选举和禅让的方式传承,延续了107年后被荷兰入侵而灭亡。

清帝退位勉强算一个:“特率皇帝将统治权公诸全国,定为共和立宪国体,近慰海内厌乱望治之心,远协古圣天下为公之义。”皇室和袁世凯之间没有刀戈相见,算和平退位。但是天下没有大平,袁世凯、张勋、和各军阀随后掀起的混战,让这次“禅让”丢了分。

前面说到哀章进贡了11个大臣的名字,王莽真的接受了:那8个党羽自然顺理成章地提拔了上来,哀章也如愿以偿地进了朝廷。捏造的两个名字,也被寻访到了:一个叫做王兴的城门令史,和一个叫做王盛的街头卖饼儿,就因为名字正确 ,“两人容貌应卜相,径从布衣登用,以示神焉”,平步青云。

Labels: ,

Monday, August 1

每当变幻时


作詞:盧國沾

怀缅过去常陶醉 一半乐事 一半令人流泪
梦如人生 快乐永记取 悲苦深刻藏骨髓

韶华去 四季暗中追随 逝去了的都已逝去
啊 常见明月挂天边 每 当变幻时 便知时光去


怀缅过去常陶醉 想到旧事 欢笑面常流泪
梦如人生 试问谁能料 石头他朝成翡翠

如情侣 你我有心追随 遇到半点风雨便思退
啊 常见红日照东方 每当见夕阳 便知时光去



这首歌由1977年熏妮首唱,街知巷闻,次年1月即获得第一届香港十大中文金曲荣誉。老年南蛮都应该听过啊,这是标准的年龄暴露歌曲。

这首歌是香港的集体回忆,虽然没有别的歌星为它录音,但是许冠杰、温拿乐队、梅艳芳等都在演唱会上唱过,引起全场附和。

要是你不懂粤语,也听着这歌觉得好听,绝不奇怪!这首歌的国语版是《月光小夜曲》:


作詞:周藍萍

月亮在我窗前徜徉 透进了爱的光芒
我低头静静的想一想 猜不透你心肠
好像今晚月亮一样 忽明忽暗又忽亮
啊 到底是爱还是心慌啊 月光
月夜情境像梦一样 那甜蜜怎能相忘
细语又在耳边荡漾 怎不叫我回想
我怕见那月亮光 抬头把窗帘关上
啊 我心儿醉 我心儿慌

这首歌是台湾歌手紫薇(绿岛小夜曲的原唱)在196x年首唱,后来徐小凤、蔡琴都有唱,都是我的菜。

李克勤的粤语“月光小夜曲”跟这首歌完全无关,略过。

上面我只写了这两首歌的词作者。曲作者是古賀政男。这首曲子原是1943年在台湾出品的电影《莎韻之钟》的主题曲,而电影是关于一个名叫莎韻的土著少女,帮忙日籍老师搬行李,遇到台风失足落水。

走过这片芦苇坡,你可曾听说,有一位女孩,她留下一首歌。

For the sake of completeness, 这首《莎韻之钟》的主题曲:
西条八十作詞
古賀政男作曲
渡邊濱子演唱
歌词 (中文大意):
狂風吹落花一枝
哀哉消逝水煙中
舊社森林鳥照啼
為何妳不歸,啊啊,沙韻。


虽然李香兰在电影里饰演主角莎韻,但是这首称颂台湾皇民化的电影,还是从日本请来最出名的女星渡边滨子演唱。


光阴似箭,日月如梭,到了2007年,香港回归10年,香港连拍了三个献礼片:梁咏琪的《女人本色》,黄秋生《老港正传》,和 杨千嬅的《每当变幻时》。第一个千万别看,说的是一个成功职场女士在1997-2007这10年里如何努力工作,老公死了,孩子死了,还是相信香港明天会更好。第二个很闷,说一个左派的生活,我看不懂。第三个才是我要介绍的:它以盧國沾的《每当变幻时》作为主题曲,这就已经很够味道了。杨千嬅、 陈奕迅都是本色演出:市井生活。1997年,他们在菜市场奋斗;2007年,杨成了成功职场女性,可是,情已经不在。





每当变幻时,便知时光去。

Labels:

Thursday, October 22

Show the complete apache config file

In the Apache config file, you can use "Include" or "IncludeOptional" to include other config files. A lot of the Linux variants take advantage of that to organize the config files. For example, the default congif file of Ubuntu is in /etc/apache2/apache.conf, and it includes enabled modules, enabled sites, and configuration files this way:

IncludeOptional mods-enabled/*.conf
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf

If you really want to see all the complete config settings, there is no existing tool for that. This Stack Overflow page  answered this question pretty well: You can use apachectl -S to see the settings of Virtual Host, or apachectl -M to see the loaded modules, but to see all settings, there is no such tool, you will have to go through all the files , starting from familiar yourself with the  general structure of the httpd config files. 

So I created this python program to generate the complete apache config file:

#!/usr/bin/python2.7
# CombineApacheConfig.py 
#!/usr/bin/python2.7
# CombineApacheConfig.py 
__author__ = 'ben'
import sys, os, os.path, logging, fnmatch, re


def Help():
    print("Usage: python CombineApacheConfig.py inputfile[default:/etc/apache2/apache2.conf] outputfile[default:/tmp/apache2.combined.conf")


def InputParameter():
    if len(sys.argv) <> 3:
        Help()
        return "/etc/apache2/apache2.conf", "/tmp/apache2.combined.conf"
    return sys.argv[1], sys.argv[2]


def ProcessMultipleFiles(InputFiles):
    if InputFiles.endswith('/'):              #Updated as Pierrick's comment
        InputFiles = InputFiles + "*"
    Content = ''
    LocalFolder = os.path.dirname(InputFiles)
    basenamePattern = os.path.basename(InputFiles)
    for root, dirs, files in os.walk(LocalFolder):
        for filename in fnmatch.filter(files, basenamePattern):
            Content += ProcessInput(os.path.join(root, filename))
    return Content


def RemoveExcessiveLinebreak(s):
    Length = len(s)
    s = s.replace(os.linesep + os.linesep + os.linesep, os.linesep + os.linesep)
    NewLength = len(s)
    if NewLength < Length:
        s = RemoveExcessiveLinebreak(s)
    return s


def ProcessInput(InputFile):
    global ServerRoot

    Content = ''
    if logging.root.isEnabledFor(logging.DEBUG):
        Content = '# Start of ' + InputFile + os.linesep
    with open(InputFile, 'r') as infile:
        for line in infile:
            stripline = line.strip(' \t')
            if stripline.startswith('#'):
                continue
            searchroot = re.search(r'ServerRoot\s+(\S+)', stripline, re.I)      #search for ServerRoot
            if searchroot:
                ServerRoot = searchroot.group(1).strip('"')
                logging.info("ServerRoot: " + ServerRoot)
            if stripline.lower().startswith('include'):
                match = stripline.split()
                if len(match) == 2:
                    IncludeFiles = match[1]
                    IncludeFiles = IncludeFiles.strip('"') #Inserted according to V's comment.
                    if not IncludeFiles.startswith('/'):
                        IncludeFiles = os.path.join(ServerRoot, IncludeFiles)

                    Content += ProcessMultipleFiles(IncludeFiles) + os.linesep
                else:
                    Content += line     # if it is not pattern of 'include(optional) path', then continue.
            else:
                Content += line
    Content = RemoveExcessiveLinebreak(Content)
    if logging.root.isEnabledFor(logging.DEBUG):
        Content += '# End of ' + InputFile + os.linesep + os.linesep
    return Content


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s][%(levelname)s]:%(message)s')
    InputFile, OutputFile = InputParameter()
    try:
        ServerRoot = os.path.dirname(InputFile)
        Content = ProcessInput(InputFile)
    except Exception as e:
        logging.error("Failed to process " + InputFile,  exc_info=True)
        exit(1)

    try:
        with open(OutputFile, 'w') as outfile:
            outfile.write(Content)
    except Exception as e:
        logging.error("Failed to write to " + outfile,  exc_info=True)
        exit(1)

    logging.info("Done writing " + OutputFile)

The usage is simple: Run it as "python  CombineApacheConfig.py ". Since there is no additional parameters given, it will retrieve the default Ubuntu apache config file from  /etc/apache2/apache2.conf and generate the result complete config file in /tmp/apache2.combined.conf. If your config file is in different location, then give the input file and output file location. For example, RHEL has the config file in /etc/httpd/conf/httpd.conf, then you can run "python CombineApacheConfig.py /etc/httpd/conf/httpd.conf /tmp/apache2.combined.conf ".

Note: Apache server-info page http://127.0.0.1/server-info also provide similar information, but not in the config file format. It is in human readable format. The page works only when it is open from the same computer.


Labels:

Nice, but is there a way to dump the configs if macro_module is being used.

Thanks
 
It's a nice looking idea, but doesn't seem to be including any included files... is this working for other people?
 
Let me correct/clarify that a bit... It seems to work if the include is a full path and not a relative path to the httpd config root (which works for apache and is allowed as far as I know).

This works
Include /etc/httpd/conf/vhosts.d/*.conf

This does not work (although apache is OK with it)
Include vhosts.d/*.conf

For some reason, this does not work (from a whm/cpanel httpd.conf)
Include "/etc/apache2/conf.d/userdata/std/2_4/username/*.conf"

I'm guessing the issue in the cpanel example might be the quotations around the string

 
Hi V,

Confirmed that path with quotations will not work. But the relative path definitely works well for me.

I will update the program to deal with quotations later. With source code, you should be able to do that as well :)

 
Inserted one line "IncludeFiles = IncludeFiles.strip('"')" to deal with the config file with quotation marks according to V's comment.

Thanks, V.
 
It does not work with folder includes. Like "Include conf.d/"
As workaround, I added the next lines in ProcessMultipleFiles:

if InputFiles.endswith('/'):
InputFiles = InputFiles + "*"

Also, the script may need a supplementary optional argument to specify relative path root. The script does not work on RHEL based systems. There, the base config path is /etc/httpd/conf/httpd.conf and root is /etc/httpd/.

Regards,
 
Thank you, Pierrick. Confirmed that my version did not work with "Include conf.d/" scenario and your update was good, so I updated the post accordingly.

About the RHEL and optional relative path root, I will need that environment to try it out. Thanks.
 
Pierrick, after looking into an RHEL server, I found the reason was that my code ignored "ServerRoot" that Apache used to identify the root of config file ("relative path root" of your comment).

By adding the logic of ServerRoot (making a global variable, make it default as the folder of httpd.conf but available to be modified as reading the config file), the code is working well in both RHEL environment which has ServerRoot specified, and Debian environment.

Thanks for pointing out the issue!

 
After revisiting the previous comment, I think maybe Verdon's frustration come from the same source: the ServerRoot in RHEL environment was not handled properly so the relative path was not correctly processed.

Please use the new version!
 

Thursday, October 15

Python logging OnMarkRotatingFileHandler

When there is a need, there is a solution.

As explained in the previous post "The TimedRotatingFileHandler of python logging system", the handler is not doing what I am thinking to do. So I made this new handler OnMarkRotatingFileHandler to fulfill my need. For example, assuming the Interval setting is "Hour":

1, If the program starts at 8:20AM, the TimedRotatingFileHandler will restart a new log file at 9:20AM, but my OnMarkRotatingFileHandler will restart a new log file at 9:00AM.

2, If the previous log file was last modified at 8:58 AM, if you start the program with old handler after 9:58, it will rotate the log; if you start the program before 9:58, say 9:55 the program will just append the log entries into the existing log until 10:55, if your program runs for that long.
With my new handler, now at 9:00AM, when the program starts, the new handler will rotate the log file to generate new log file.


Source code:
#filename: logHandler.py
__author__ = 'ben'

import logging.handlers

class OnMarkRotatingFileHandler(logging.handlers.TimedRotatingFileHandler):

        def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
            #super().__init__() # in Python 2 use super(D, self).__init__()
            super(OnMarkRotatingFileHandler, self).__init__(filename, when, interval, backupCount, encoding, delay, utc)


        def floor_to(self, num, scale):
            return int(num/scale) * scale


        def computeRollover(self, currentTime):
            temp_result = super(OnMarkRotatingFileHandler, self).computeRollover(currentTime)
            if not self.when.startswith('W'):
                result = self.floor_to(temp_result, self.interval)
            else:
                result = temp_result    # need to find out the first date of time (is it 1970/1/1?), what weekday that is.

            return result


Most methods are inherited from the TimedRotatingFileHandler. The W0/W1.../W6  options are not implemented yet. But you get the ideal.

To use it, place the logHandler.py with your code, then you can either import it and load it as

    import logHandler
    h = logHandler.OnMarkRotatingFileHandler ("filename")
    logger=logging.getLogger('app')
    logger.addHandler(h)


or you can use it in the logging.ini:
    class=logHandler.OnMarkRotatingFileHandler

then load it in logging.config like every other handler does:
    logging.config.fileConfig('logging.ini')


Have fun hacking!


Labels:

The TimedRotatingFileHandler of python logging system

The name of TimedRotatingFileHandler is like the best for my need: rotate every day, keep 30 days of copy. I know the logrotate of Linux system provides the same service, but I prefer to make the program easy to install and run without System Admin (root) involving in the process. Another reason I am not using logrotate is that when my program is writing into log at midnight, logrotate would perform "mv myprogram.log myprogram.log.20151015", and my program is still writing into that file which has a new name now: myprogram.log.20151015, while I still have another program trying to access myprogram.log for display purpose, then that program will fail.


So I want to have a native logging handler to do log rotating, and change to a new file of "myprogram.log" at midnight. The TimedRotatingFileHandler looks perfect to me.


But it is not.


The TimedRotatingFileHandler only rotate the log when the program has been running for more than 24 hours (Assuming the interval setting is 1 Day ), or if the log file has not been modified (before loading) for more than 24 hours. You can check the code, but that is what it is. If your program runs every day from 1AM to 11PM, then the log is never rotate, it keep appending forever. It is using the last modified time of the log file , or current time when the file doesn't exist, to decide when to rotate.


One limitation is that in Linux there is not "file creation time" or "birth time". Most file system don't support this file attribute. I guess ext4 does, under some configuration, but we can't rely on it.





Labels: